Using OR-TOOLS CP-SAT I found a problem with intruding tasks between other tasks. In my model there are machines working on 1,2,3 shifts and not working on weekends.
#intervals for non-working time depending on shift model:
#1 shifts 2 PM - 6 & weekends, 2 shifts 10 PM - 6 AM & weekends, 3 shifts weekends
i = 0
for machine in machine_data:
for mach_id, mach in enumerate(machine):
if mach[1] == 0 and mach[3] == 1:
if mach[2] == 1:
for cal_id, cal in enumerate(cal_data):
for shift_id, shift in enumerate(cal):
start = int(shift[0])
size = int(shift[1]) - int(shift[0])
end = int(shift[1])
i = i + 1
name = str(mach[0]) + '_' + str(mach[2]) + '_' + str(i) + '_weekend'
machine_to_intervals[mach[0]].append(model.NewIntervalVar(start, size, end, name))
elif mach[2] == 2:
#print('2:')
for cal_id, cal in enumerate(cal_data):
for shift_id, shift in enumerate(cal):
start = int(shift[2])
size = int(shift[3]) - int(shift[2])
end = int(shift[3])
i = i + 1
name = str(mach[0]) + '_' + str(mach[2]) + '_' + str(i) + '_weekend'
machine_to_intervals[mach[0]].append(model.NewIntervalVar(start, size, end, name))
elif mach[2] == 3:
for cal in cal_data:
for shift_id, shift in enumerate(cal):
if shift[4] > 0:
start = int(shift[4])
size = int(shift[5]) - int(shift[4])
end = int(shift[5])
i = i + 1
name = str(mach[0]) + '_' + str(mach[2]) + '_' + str(i) + '_weekend'
machine_to_intervals[mach[0]].append(model.NewIntervalVar(start, size, end, name))
Some of the tasks are longer than 1 or even 2 shifts, so I decided to divide them to setup time and every 1 piece as separate task, e.g. one operation in work order with 15 pcs = 16 tasks. Then I expect that each task from this operation would be scheduled one after another without intruding by other tasks from other work orders.
To achieve this, I've used OnlyEnforceIf
, to queue all tasks, that do not belong this to operation (activity), before start of the first task in this activity or after end of the last task in this activity. I've also defined objective model.Minimize(splitMin)
to minimize distance between first and the last task in split activity.
split_minimize = []
for i in range(len(splitdf)):
order = splitdf.iloc[i]['WorkOrder']
activity = splitdf.iloc[i]['Activity']
wc = splitdf.iloc[i]['WC_INT']
order_id = splitdf.iloc[i]['WO_INT']
#first and last row with the same split activity
ord_pl_min = splitdf.iloc[i]['MinROW#']
ord_pl_max = splitdf.iloc[i]['MaxROW#']
spltMin = all_tasks[order_id, ord_pl_max].end - all_tasks[order_id, ord_pl_min].start
split_minimize.append(spltMin)
for job_id, job in enumerate(jobs_data):
for task_id, task in enumerate(job):
zName = "z%i_%i_%i" % (i, job_id, task_id)
z = model.NewBoolVar(zName)
model.Add(all_tasks[job_id, task_id].end < all_tasks[order_id, ord_pl_min].start).OnlyEnforceIf(z)
model.Add(all_tasks[job_id, task_id].start > all_tasks[order_id, ord_pl_max].end).OnlyEnforceIf(z.Not())
In most of the situations it works fine and solver queue tasks without inputting other tasks between. But there are also situations like this:
Schedule for one machine working in 2 shifts model
I imagine that OnlyEnforceIf
is a "soft" constraint. I tried to use AddCircuit
, because I thought it can force the right queue, but I failed. Could you please advise me, what should I do to keep these tasks together?
I recommend another approach which is to compute the length of the interval depending on the start, and actually do not create breaks.