# 17 employees
num_of_staff = 17
# contains off-shift
num_shifts = 2
# o-off, w-work
shifts = ['O', 'W']
# June has 30 days
june_days = 30
# except first four days, June has four weeks
num_of_weeks = 4
# Because the last five days of June and the first two days of July form a week
# so the total days is 32
days = 32
# weekend in these days, days begin with 0 index
weekends = [
2, 3, 9, 10, 16, 17, 22, 23, 30, 31
]
# Fixed assignment: (employee, shift, day), preset value
fixed_assignments = [
(0, 1, 0),
(0, 1, 1),
(0, 0, 2),
(0, 1, 3),
(1, 1, 0),
(1, 1, 1),
(1, 0, 2),
(1, 1, 3),
(2, 1, 0),
(2, 1, 1),
(2, 1, 2),
(2, 0, 3),
(3, 1, 0),
(3, 1, 1),
(3, 1, 2),
(3, 0, 3),
(4, 1, 0),
(4, 1, 1),
(4, 1, 2),
(4, 0, 3),
(5, 1, 0),
(5, 1, 1),
(5, 0, 2),
(5, 1, 3),
(6, 1, 0),
(6, 0, 1),
(6, 1, 2),
(6, 1, 3),
(7, 1, 0),
(7, 1, 1),
(7, 1, 2),
(7, 0, 3),
(8, 0, 0),
(8, 0, 1),
(8, 1, 2),
(8, 1, 3),
(9, 1, 0),
(9, 0, 1),
(9, 0, 2),
(9, 1, 3),
(10, 1, 0),
(10, 1, 1),
(10, 0, 2),
(10, 1, 3),
(11, 1, 0),
(11, 1, 1),
(11, 1, 2),
(11, 0, 3),
(12, 0, 0),
(12, 1, 1),
(12, 1, 2),
(12, 1, 3),
(13, 1, 0),
(13, 1, 1),
(13, 0, 2),
(13, 1, 3),
(14, 1, 0),
(14, 1, 1),
(14, 0, 2),
(14, 0, 3),
(15, 1, 0),
(15, 1, 1),
(15, 1, 2),
(15, 0, 3),
(16, 0, 0),
(16, 0, 1),
(16, 1, 2),
(16, 1, 3),
(0, 0, 21),
(1, 0, 21),
(2, 0, 21),
(3, 0, 21),
(4, 1, 21),
(5, 1, 21),
(6, 0, 21),
(7, 1, 21),
(8, 0, 21),
(9, 1, 21),
(10, 1, 21),
(11, 0, 21),
(12, 0, 21),
(13, 1, 21),
(14, 1, 21),
(15, 1, 21),
(16, 0, 21),
(0, 0, 22),
(1, 0, 22),
(2, 0, 22),
(3, 0, 22),
(4, 1, 22),
(5, 1, 22),
(6, 0, 22),
(7, 1, 22),
(8, 0, 22),
(9, 1, 22),
(10, 1, 22),
(11, 0, 22),
(12, 0, 22),
(13, 1, 22),
(14, 1, 22),
(15, 1, 22),
(16, 0, 22),
(0, 0, 23),
(1, 0, 23),
(2, 0, 23),
(3, 0, 23),
(4, 1, 23),
(5, 1, 23),
(6, 0, 23),
(7, 1, 23),
(8, 0, 23),
(9, 1, 23),
(10, 1, 23),
(11, 0, 23),
(12, 0, 23),
(13, 1, 23),
(14, 1, 23),
(15, 1, 23),
(16, 0, 23),
]
shift_constrains = [
# rest continuous two days is preferred
(0, 1, 2, 5, 2, 3, 10),
]
model = cp_model.CpModel()
work = {}
# init work
for e in range(num_of_staff):
for s in range(num_shifts):
for d in range(days):
work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))
# each staff has one shift each day
for e in range(num_of_staff):
for d in range(days):
model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)
# init preset
for e, s, d in fixed_assignments:
model.Add(work[e, s, d] == 1)
# each employee works maximum of 7 days followed by rest
max_seq_length = 7
for e in range(num_of_staff):
works = [work[e, 0, d] for d in range(days)]
for start in range(len(works) - max_seq_length):
model.AddBoolOr([works[i] for i in range(start, start + max_seq_length + 1)])
# except 22 June, each employee work 21 days in June
for e in range(num_of_staff):
sum_of_shifts_worked = [work[(e, 0, d)] for d in range(june_days) if d != 21]
model.Add(sum(sum_of_shifts_worked) == 8)
# Linear terms of the objective in a minimization context.
obj_int_vars = []
obj_int_coeffs = []
obj_bool_vars = []
obj_bool_coeffs = []
for e in range(num_of_staff):
for w in range(num_of_weeks):
# Special handling due to holidays during the week, except w = 2, d = 3, each employee rest two days
if w == 2:
# plus 4 is because of first four days didn't consider into this condition
works = [work[e, 0, d + w * 7 + 4] for d in range(7) if d != 3]
model.Add(sum(works) == 2)
else:
# plus 4 is because of first four days didn't consider into this condition
works = [work[e, 0, d + w * 7 + 4] for d in range(7)]
model.Add(sum(works) == 2)
# shift constrains
for ct in shift_constrains:
shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
for e in range(num_of_staff):
works = [work[e, shift, d] for d in range(days)]
variables, coeffs = add_soft_sequence_constraint(
model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
max_cost,
'shift_constraint(employee %i, shift %i)' % (e, shift))
obj_bool_vars.extend(variables)
obj_bool_coeffs.extend(coeffs)
# number of employees worked
for d in range(days):
# work[e, 1, d] represent employee worked in day d
works = [work[e, 1, d] for e in range(num_of_staff)]
# 周末特殊限制
if d in weekends:
model.Add(sum(works) == model.NewIntVar(8, 10, ''))
# 正常工作日限制, 冗余人力全都分配到正常班
else:
model.Add(sum(works) == model.NewIntVar(13, 15, ''))
# find best solution
model.Minimize(
sum(obj_bool_vars[i] * obj_bool_coeffs[i]
for i in range(len(obj_bool_vars))) +
sum(obj_int_vars[i] * obj_int_coeffs[i]
for i in range(len(obj_int_vars))))
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 16
solution_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.Solve(model, solution_printer)