简体   繁体   中英

Google ortools CP-SAT maximize takes too long

I am trying to solve a scheduling problem. As input I have the number of workers and the number of shifts.

After that I place all the constraints, including:

  • everyone has to work around the same amount of shifts,
  • everyone has to take all the given holiday days,
  • ...etc.

I then try to maximize a given sequence of shifts.

For example, I can receive the sequence: 123BBBB. This means that I must maximize the appearance of this sequence for each worker in their calendar.

  • 1 means the worker works shift 1,
  • 2 for shift 2,
  • 3 for shift 3, and
  • B for break.

I calculate the schedule for the whole year (so let's say 365 days) for each worker. I tried to make bools for each possible sequence: so for the days in range(1, length(sequence)), in range(2, length(sequence))), etc.

And then I add as a constraint that the sum of shifts from today to len(sequence) is len(sequence) . So I have the shifts represented as bools too ( shifts[(w, d, s)] ) meaning for shifts[(0, 3, 0)] that worker 1 in day 3 works shift 1.

The constraint for the given sequence is OnlyEnforcedIf the bool created for that sequence is true. And then I try to maximize the sum of the bools created for each sequence.

The problem: I tried running this and after 8 hours of running I gave up. It found me about 62 sequences, but then took too long to stop or find another one. My question is: how to do this more efficiently?

The code:


shift_requests = []
requiredShift = "111222333BBBBBB"
appearences_for_1 = 0
appearences_for_2 = 0
appearences_for_3 = 0
appearences_for_L = 0
for i in requiredShift:
    if i == '1':
        appearences_for_1 += 1

for i in requiredShift:
    if i == '2':
        appearences_for_2 += 1

for i in requiredShift:
    if i == '3':
        appearences_for_3 += 1

for i in requiredShift:
    if i == 'B':
        appearences_for_B += 1

print(appearences_for_1, appearences_for_2, appearences_for_3, appearences_for_B)

for w in range(worker):
    shift_requests.append([])
    poz = 0
    dayz = 1
    while dayz + len(requiredShift) <= 365:
        shift_requests[w].append(model.NewBoolVar(f'{w}_{dayz}_{dayz + len(requiredShift)}'))

        first_range = dayz + appearences_for_1
        second_range = first_range + appearences_for_2
        third_range = second_range + appearences_for_3
        fourth_range = third_range + appearences_for_B
        
        #shift = 5 ( 0 is shift 1, 1 is shift 2, 2 is shift 3, 3 is break, 4 is holiday)
       

        model.Add(sum(shifts[(w, d, shift - 2 - 3)] for d in range(dayz, first_range))+
                  sum(shifts[(w, d, shift - 2 - 2)] for d in range(first_range, second_range))+
                  sum(shifts[(w, d, shift - 2 - 1)] for d in range(second_range, third_range))+
                  sum(shifts[(w, d, shift - 2)] for d in range(third_range, fourth_range)) == len(requiredShift))\
             .OnlyEnforceIf(shift_requests[w][poz])
        dayz += 1
        poz += 1

model.Maximize(sum(shift_requests[w][poz] for w in range(worker) for poz in range(len(shift_requests[w]))))

For each start date d , you can have one Boolean variable s_d .

Imagine the pattern is '123BBB', you can have

s_d implies that the sequence starts at this date:

model.Add(x[d] == 1).OnlyEnforceIf(s_d)
model.Add(x[d + 1] == 2).OnlyEnforceIf(s_d)
model.Add(x[d + 2] == 3).OnlyEnforceIf(s_d)
model.Add(x[d + 3] == 0).OnlyEnforceIf(s_d)  # 0 == Break
model.Add(x[d + 4] == 0).OnlyEnforceIf(s_d)  # 0 == Break
model.Add(x[d + 5] == 0).OnlyEnforceIf(s_d)  # 0 == Break

Then maximize the number of positive s_d .

worker = 5
days = 365
required_sequence_bools = []
required_sequence = "111222333BBBBBB"
for w in range(worker):
    required_sequence_bools.append([])
    for d in range(1, days - len(required_sequence)):
        required_sequence_bools[w].append(model.NewBoolVar(f"{w}_{d}"))

for w in range(worker):
    for d in range(0, days - len(required_sequence) - 1):
        day = d + 1
        for letter in required_sequence:
            if letter == '1':
                model.Add(shifts[(w, day, 0)] == 1).OnlyEnforceIf(required_sequence_bools[w][d])
            elif letter == '2':
                model.Add(shifts[(w, day, 1)] == 1).OnlyEnforceIf(required_sequence_bools[w][d])
            elif letter == '3':
                model.Add(shifts[(w, day, 2)] == 1).OnlyEnforceIf(required_sequence_bools[w][d])
            elif letter == 'B':
                model.Add(shifts[(w, day, 3)] == 1).OnlyEnforceIf(required_sequence_bools[w][d])
            day += 1
model.Maximize(sum(required_sequence_bools[w][d] for w in range(worker) for d in range(0, days - len(required_sequence) - 1)))

This is what I understood from what you said and I think I didn't understand correctly because it's kind of what I did previously. I have 5 workers and try to start in each day with the sequence for each worker. Most likely I didn't understand what you tried to say since this will most likely take more than one day to solve on 8 cores. Also sorry for posting this as an answear but I didn't know how to post code in the comment section. PS: by worker I mean a person that works shifts in my algorithm not a processor core.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM