Python shift planning optimization

I am currently working on a shift planning algorithm for work. Our shift graphs consist exclusively of 4-3 (4 days, 3 days) and 4-3 rotation (Example: Sun, Mon, Tue, from one week and next week and Sun, Fri, Sat next week) - weeks from Sunday on saturday.

There are 49 possible options for a “direct” 4-3 shift or 4-3 rotation. Since this is round-the-clock work, it is necessary to consider all 7 days of the week. At the moment I am using this for a small working group with 11 people on an early shift and 12 people at the end of the shift, but there are other working groups that work for up to 200 people, and I would like to expand this algorithm.

The basic premise is that the management group has the required workforce on a daily basis, and the algorithm only returns a set of early and late shifts that give them the necessary workforce.

It became very painfully obvious very quickly that 49 possible shifts for 11 people (with repetitions) and sorting by all possible combinations would take thousands of years. As a result, I was able to drop the list of 49 shifts to about 16-20 of the most frequently used shifts.

This makes it manageable, but only for 11 or 12 people. Obviously, the number of possible combinations grows exponentially every time a person is added. At the moment, my algorithm performs the following actions:

  • I have a group of shifts from 1 and 0 as variables, representing "On Shift" and "Off Shift" for each day of the week, when they are, for two weeks. So far I am using only a sample ... For example:

    h = [0,1,1,1,1,0,0,0,1,1,1,1,0,0]
    e = [0,0,1,1,1,1,0,0,0,1,1,1,1,0]
    d = [0,0,0,1,1,1,1,0,0,0,1,1,1,1]
    c = [1,1,1,1,0,0,0,1,1,1,1,0,0,0]
    m = [0,1,1,0,1,1,0,0,1,1,0,1,1,0]
    p = [1,1,0,0,0,1,1,1,1,0,0,0,1,1]
    q1 = [1,1,1,0,0,0,1,1,1,1,0,0,0,1]
    a = [1,0,0,0,1,1,1,1,0,0,0,1,1,1]
    

Then I have a list of about 16-20 (I will use only 8 for this example) shifts that are most often used, if not used exclusively, plus a variable for the number of people it calculates for and a variable for manager requirements (early_count) :

shifts = [h,e,c,d,m,p,q1, a]
early_bid_personnel = 11
early_count = [5,6,7,7,6,8,5]

, , (5). , , mon, , Tue. - :

sun = (combs for combs in combinations_with_replacement(shifts,early_bid_personnel) if (sum(combs[i][0] for i in range(0,early_bid_personnel)) == early_count[0]))
mon = (mon for mon in sun if (sum(mon[i][1] for i in range(0,early_bid_personnel)) == early_count[1]))
tue = (tue for tue in mon if (sum(tue[i][2] for i in range(0,early_bid_personnel)) == early_count[2]))
wed = (wed for wed in tue if (sum(wed[i][3] for i in range(0,early_bid_personnel)) == early_count[3]))
thu = (thu for thu in wed if (sum(thu[i][4] for i in range(0,early_bid_personnel)) == early_count[4]))
fri = (fri for fri in thu if (sum(fri[i][5] for i in range(0,early_bid_personnel)) == early_count[5]))
sat = (sat for sat in fri if (sum(sat[i][6] for i in range(0,early_bid_personnel)) == early_count[6]))
sec_sun = (sec_sun for sec_sun in sat if (sum(sec_sun[i][7] for i in range(0,early_bid_personnel)) == early_count[0]))
sec_mon = (sec_mon for sec_mon in sec_sun if (sum(sec_mon[i][8] for i in range(0,early_bid_personnel)) == early_count[1]))
sec_tue = (sec_tue for sec_tue in sec_mon if (sum(sec_tue[i][9] for i in range(0,early_bid_personnel)) == early_count[2]))
sec_wed = (sec_wed for sec_wed in sec_tue if (sum(sec_wed[i][10] for i in range(0,early_bid_personnel)) == early_count[3]))
sec_thu = (sec_thu for sec_thu in sec_wed if (sum(sec_thu[i][11] for i in range(0,early_bid_personnel)) == early_count[4]))
sec_fri = (sec_fri for sec_fri in sec_thu if (sum(sec_fri[i][12] for i in range(0,early_bid_personnel)) == early_count[5]))
sec_sat = (sec_sat for sec_sat in sec_fri if (sum(sec_sat[i][13] for i in range(0,early_bid_personnel)) == early_count[6]))

sec_sat, , 1 0 . . , . , , 11 , 8 , 12 8 . , , 20 , 12,14,16 49 , , .

, , , . , , , O (n ^ 2) .

? , , , 5 'a' shifts:

sun = (combs for combs in combinations_with_replacement(shifts,early_bid_personnel) if (sum(combs[i][0] for i in range(0,early_bid_personnel)) == early_count[0]) and combs.count(a) <= 5)

STILL - , , 5 "a" , , ?

+4
1

- . . , , . . [sun, mon, tue,..., sec_sat] . [q1, q2,..., q49] 49 . :

s1[0]  s1[1] ... s1[13]
s2[0]  s2[1] ... s2[13]
....................
s49[0] s49[1] ... s49[13]

/ . : s1 [0] 1, - 1 . s3 [7] 1, - 3 . . :

sun       <=  q1*s1[0] + ... +  q49*s49[0]
mon       <=  q1*s1[1] + ... + q49*s49[1]
tue       <=  q1*s1[2] + ... + q49*s49[2]
wed       <=  q1*s1[3] + ... + q49*s49[3]
thu       <=  q1*s1[4] + ... + q49*s49[4]
fri       <=  q1*s1[5] + ... + q49*s49[5]
sat       <=  q1*s1[6] + ... + q49*s49[6]
sec_sun   <=  q1*s1[7] + ... + q49*s49[7]
sec_mon   <=  q1*s1[8] + ... + q49*s49[8]
sec_tue   <=  q1*s1[9] + ... + q49*s49[9]
sec_wed   <=  q1*s1[10] + ... + q49*s49[10]
sec_thu   <=  q1*s1[11] + ... + q49*s49[11]
sec_fri   <=  q1*s1[12] + ... + q49*s49[12]
sec_sat   <=  q1*s1[13] + ... + q49*s49[13]

[q1, q2,..., q49]. . ? [q1,..., q49] , . , . ( ):

import random
# Define your restrictions
mon = 
tue = 
.........
sec_sat = 
# Define your shifts
s1 = [1,1,1,1,0,0,0,1,1,1,1,0,0,0]
s2 = [0,1,1,1,1,0,0,0,1,1,1,1,0,0]
...................................
s49 = [...]
while True:
    q = [0]*49
    # We place workers in random shifts
    for i in range(number_of_workers):
        q[random.randint(0,len(q)-1)] += 1

    if (mon <= q[0]*s1[0] + q[1]*s2[0] + ... + q[48]*s49[0]) and
       (tue <= q[0]*s1[1] + q[1]*s2[1] + ... + q[48]*s49[1]) and         
       .........................................................
       (sec_sat <= q[0]*s1[13] + q[1]*s2[13] + ... + q[48]*s49[13]):
       # Condition met, return result
       return q

:

import random
# Restrictions
r = [5, 6, 7, 7, 6, 8, 5, 5, 6, 7, 7, 6, 8, 5]
# Shifts
s = []
s.append([0,1,1,1,1,0,0,0,1,1,1,1,0,0])
s.append([0,0,1,1,1,1,0,0,0,1,1,1,1,0])
s.append([0,0,0,1,1,1,1,0,0,0,1,1,1,1])
s.append([1,1,1,1,0,0,0,1,1,1,1,0,0,0])
s.append([0,1,1,0,1,1,0,0,1,1,0,1,1,0])
s.append([1,1,0,0,0,1,1,1,1,0,0,0,1,1])
s.append([1,1,1,0,0,0,1,1,1,1,0,0,0,1])
s.append([1,0,0,0,1,1,1,1,0,0,0,1,1,1])

number_of_shifts = len(s)
number_of_workers = 11
number_of_days = len(s[0])

while True:
    q = [0]*number_of_shifts
    for i in range(number_of_workers):
        q[random.randint(0,len(q)-1)] += 1
    t = [sum([q[j]*s[j][i] for j in range(number_of_shifts)]) for i in range(number_of_days)]
    if sum([r[i] <= t[i] for i in range(number_of_days)]) == number_of_days:
        print q
        break

. : [0, 3, 2, 2, 1, 2, 1, 0]

+3

All Articles