def optimize(instance, max_time=10000, time_limit=100, threads=1): model = cp_model.CpModel() start_vars = dict() end_vars = dict() durations = dict() interval_vars = dict() # Create variables for task in instance.tasks: start_vars[task] = model.NewIntVar(0, max_time, 'start' + task.name) end_vars[task] = model.NewIntVar(0, max_time, 'end' + task.name) durations[task] = task.length interval_vars[task] = model.NewIntervalVar(start_vars[task], durations[task], end_vars[task], 'interval' + task.name) # Precedence and blocking constraints for task in instance.tasks: if task.next_task: model.Add(start_vars[task.next_task] >= end_vars[task]) # No overlap constraints for machine in instance.machines: for task in machine.tasks: model.AddNoOverlap([interval_vars[task] for task in machine.tasks]) # Minimize the makespan obj_var = model.NewIntVar(0, max_time, 'makespan') model.AddMaxEquality( obj_var, [end_vars[task] for job in instance.jobs for task in job.tasks]) model.Minimize(obj_var) # Define solver and solve cb = cp_model.ObjectiveSolutionPrinter() solver = cp_model.CpSolver() #print(dir(solver.parameters)) # I just used this to find the names of the parameters solver.parameters.max_time_in_seconds = time_limit solver.parameters.num_search_workers = threads status = solver.SolveWithSolutionCallback(model, cb) solution = Solution(instance) # Print out solution and return it for job in instance.jobs: for task in job.tasks: print(task.name) print('Start: %f' % solver.Value(start_vars[task])) print('End: %f' % solver.Value(end_vars[task])) solution.add(task, solver.Value(start_vars[task]), solver.Value(end_vars[task])) return solution
def solve_reified(demands, capacities, cost): # solving the problem by applying reified constraints # About reified constaints in OR Tools: # https://www.cis.upenn.edu/~cis189/files/Lecture8.pdf M = len(demands) # number of customers N = len(capacities) # number of warehouses model = cp_model.CpModel() # solver = pywraplp.Solver.CreateSolver('SCIP') solver = cp_model.CpSolver() # define variables x = {} for i in range(M): # x_i = warehouse number from ith customer is served x[i] = model.NewIntVar(0, N - 1, f'x_{i}') z = {} for i in range(M): for j in range(N): z[i, j] = model.NewBoolVar(f'z_{{{i},{j}}}') # model.Add(x[i] > -) # z := x_i == j for i in range(M): for j in range(N): model.Add(x[i] == j).OnlyEnforceIf(z[i, j]) model.Add(x[i] != j).OnlyEnforceIf(z[i, j].Not()) # capacities cannot been outgrown by demands for j in range(N): model.Add(sum(demands[i] * z[i, j] for i in range(M)) <= capacities[j]) model.Minimize( sum(cost[i, j] * z[i, j] for j in range(N) for i in range(M))) solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) print(status) assignment_vector = np.zeros((M), dtype=int) if status == cp_model.OPTIMAL: print('Solution:') print('Objective value =', solution_printer.ObjectiveValue()) for i in range(M): assignment_vector[i] = solver.Value(x[i]) print('Assignment vector:', assignment_vector, sep='\n') else: print('The problem does not have an optimal solution.') print() return assignment_vector
def solve_shift_scheduling(params, output_proto): """Solves the shift scheduling problem.""" # Data num_employees = 8 num_weeks = 3 shifts = ['O', 'M', 'A', 'N'] # Fixed assignment: (employee, shift, day). # This fixes the first 2 days of the schedule. fixed_assignments = [ (0, 0, 0), (1, 0, 0), (2, 1, 0), (3, 1, 0), (4, 2, 0), (5, 2, 0), (6, 2, 3), (7, 3, 0), (0, 1, 1), (1, 1, 1), (2, 2, 1), (3, 2, 1), (4, 2, 1), (5, 0, 1), (6, 0, 1), (7, 3, 1), ] # Request: (employee, shift, day, weight) # A negative weight indicates that the employee desire this assignment. requests = [ # Employee 3 wants the first Saturday off. (3, 0, 5, -2), # Employee 5 wants a night shift on the second Thursday. (5, 3, 10, -2), # Employee 2 does not want a night shift on the first Friday. (2, 3, 4, 4) ] # Shift constraints on continuous sequence : # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) shift_constraints = [ # One or two consecutive days of rest, this is a hard constraint. (0, 1, 1, 0, 2, 2, 0), # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are # possible but penalized. (3, 1, 2, 20, 3, 4, 5), ] # Weekly sum constraints on shifts days: # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) weekly_sum_constraints = [ # Constraints on rests per week. (0, 1, 2, 7, 2, 3, 4), # At least 1 night shift per week (penalized). At most 4 (hard). (3, 0, 1, 3, 4, 4, 0), ] # Penalized transitions: # (previous_shift, next_shift, penalty (0 means forbidden)) penalized_transitions = [ # Afternoon to night has a penalty of 4. (2, 3, 4), # Night to morning is forbidden. (3, 1, 0), ] # daily demands for work shifts (morning, afternon, night) for each day # of the week starting on Monday. weekly_cover_demands = [ (2, 3, 1), # Monday (2, 3, 1), # Tuesday (2, 2, 2), # Wednesday (2, 3, 1), # Thursday (2, 2, 2), # Friday (1, 2, 3), # Saturday (1, 3, 1), # Sunday ] # Penalty for exceeding the cover constraint per shift type. excess_cover_penalties = (2, 2, 5) num_days = num_weeks * 7 num_shifts = len(shifts) model = cp_model.CpModel() work = {} for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d)) # Linear terms of the objective in a minimization context. obj_int_vars = [] obj_int_coeffs = [] obj_bool_vars = [] obj_bool_coeffs = [] # Exactly one shift per day. for e in range(num_employees): for d in range(num_days): model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1) # Fixed assignments. for e, s, d in fixed_assignments: model.Add(work[e, s, d] == 1) # Employee requests for e, s, d, w in requests: obj_bool_vars.append(work[e, s, d]) obj_bool_coeffs.append(w) # Shift constraints for ct in shift_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): works = [work[e, shift, d] for d in range(num_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) # Weekly sum constraints for ct in weekly_sum_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): for w in range(num_weeks): works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, 'weekly_sum_constraint(employee %i, shift %i, week %i)' % (e, shift, w)) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Penalized transitions for previous_shift, next_shift, cost in penalized_transitions: for e in range(num_employees): for d in range(num_days - 1): transition = [ work[e, previous_shift, d].Not(), work[e, next_shift, d + 1].Not() ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( 'transition (employee=%i, day=%i)' % (e, d)) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) # Cover constraints for s in range(1, num_shifts): for w in range(num_weeks): for d in range(7): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] worked = model.NewIntVar(min_demand, num_employees, '') model.Add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w, d) excess = model.NewIntVar(0, num_employees - min_demand, name) model.Add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective 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)))) if output_proto: print('Writing proto to %s' % output_proto) with open(output_proto, 'w') as text_file: text_file.write(str(model)) # Solve the model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 if params: text_format.Merge(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print() header = ' ' for w in range(num_weeks): header += 'M T W T F S S ' print(header) for e in range(num_employees): schedule = '' for d in range(num_days): for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): schedule += shifts[s] + ' ' print('worker %i: %s' % (e, schedule)) print() print('Penalties:') for i, var in enumerate(obj_bool_vars): if solver.BooleanValue(var): penalty = obj_bool_coeffs[i] if penalty > 0: print(' %s violated, penalty=%i' % (var.Name(), penalty)) else: print(' %s fulfilled, gain=%i' % (var.Name(), -penalty)) for i, var in enumerate(obj_int_vars): if solver.Value(var) > 0: print(' %s violated by %i, linear penalty=%i' % (var.Name(), solver.Value(var), obj_int_coeffs[i])) print() print('Statistics') print(' - status : %s' % solver.StatusName(status)) print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def solve_shift_scheduling(params, output_proto, num_employees, num_weeks, shifts, fixed_assignments, requests, shift_constraints, weekly_sum_constraints, penalized_transitions, weekly_cover_demands, excess_cover_penalties): """Solves the shift scheduling problem.""" num_days = num_weeks * 7 num_shifts = len(shifts) # Solver in the OR-Tools library. This is the key to the whole thing. model = cp_model.CpModel() # Sets up the data typing and initialization for the matrix used in the solver. work = {} for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d)) # Linear terms of the objective in a minimization context. obj_int_vars = [] obj_int_coeffs = [] obj_bool_vars = [] obj_bool_coeffs = [] # Exactly one shift per day. This ensures every employee is assigned one shift per day (including 'off' as a shift). for e in range(num_employees): for d in range(num_days): model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1) # Fixed assignments. This parses the shifts already scheduled as a base to work off of while generating the schedule. for e, s, d in fixed_assignments: model.Add(work[e, s, d] == 1) # Parses employee requests to pass into the solver. for e, s, d, w in requests: try: obj_bool_vars.append(work[e, s, d]) obj_bool_coeffs.append(w) except: continue # Takes in the shift constraints and parses them. To be passed into the solver. for ct in shift_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): works = [work[e, shift, d] for d in range(num_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) # Parses the weekly sum constraints to pass into the solver. for ct in weekly_sum_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): for w in range(num_weeks): works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, 'weekly_sum_constraint(employee %i, shift %i, week %i)' % (e, shift, w)) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Parses the penalized transitions to pass into the solver. for previous_shift, next_shift, cost in penalized_transitions: for e in range(num_employees): for d in range(num_days - 1): transition = [ work[e, previous_shift, d].Not(), work[e, next_shift, d + 1].Not() ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( 'transition (employee=%i, day=%i)' % (e, d)) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) # Parses the cover constraints to pass them into the solver. for s in range(1, num_shifts): for w in range(num_weeks): for d in range(7): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] worked = model.NewIntVar(min_demand, num_employees, '') model.Add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w, d) excess = model.NewIntVar(0, num_employees - min_demand, name) model.Add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Optimizes the model object in preparation for calculation. 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)))) if output_proto: print('Writing proto to %s' % output_proto) with open(output_proto, 'w') as text_file: text_file.write(str(model)) # Solve the model. solver = cp_model.CpSolver() if params: text_format.Merge(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) shifts[s] # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print() header = ' ' for w in range(num_weeks): header += 'M T W T F S S ' print(header) for e in range(num_employees): schedule = '' for d in range(num_days): for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): schedule += shifts[s] + ' ' print('worker %i: %s' % (e, schedule)) print() print('Penalties:') for i, var in enumerate(obj_bool_vars): if solver.BooleanValue(var): penalty = obj_bool_coeffs[i] if penalty > 0: print(' %s violated, penalty=%i' % (var.Name(), penalty)) else: print(' %s fulfilled, gain=%i' % (var.Name(), -penalty)) for i, var in enumerate(obj_int_vars): if solver.Value(var) > 0: print(' %s violated by %i, linear penalty=%i' % (var.Name(), solver.Value(var), obj_int_coeffs[i])) print() print('Statistics') print(' - status : %s' % solver.StatusName(status)) print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def solve_shift_scheduling(params, output_proto): """Solves the shift scheduling problem.""" # Data num_employees = 5 num_weeks = 1 shifts = ["R", "Manager", "Bar", "Restaurant"] shift_to_hour = [0, 8, 7, 6] tolerated_delta_contract_hours = 15 employee_to_hour = [40, 30, 35, 26, 16] # Fixed assignment: (employee, shift, day). # This fixes the first 2 days of the schedule. fixed_assignments = [] # Request: (employee, shift, day, weight) # A negative weight indicates that the employee desire this assignment. requests = [ # Employee 3 wants the first Saturday off. # (1, 0, 0, -2), # (1, 0, 1, -2), # (0, 1, 1, 2), # Employee 5 wants a night shift on the second Thursday. # (5, 3, 10, -2), # Employee 2 does not want a night shift on the first Friday. # (1, 3, 4, -4), ] # Shift constraints on continuous sequence : # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) shift_constraints = [ # One or two consecutive days of rest, this is a hard constraint. # (0, 1, 1, 0, 3, 3, 0), # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are # possible but penalized. # (3, 1, 2, 20, 3, 4, 5), ] # Weekly sum constraints on shifts days: # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) weekly_sum_constraints = [ # Constraints on rests per week. # (0, 1, 2, 7, 2, 3, 4), # At least 1 night shift per week (penalized). At most 4 (hard). # (3, 0, 1, 3, 2, 2, 0), ] # Penalized transitions: # (previous_shift, next_shift, penalty (0 means forbidden)) penalized_transitions = [ # Afternoon to night has a penalty of 4. # (2, 3, 4), # Night to morning is forbidden. # (1, 3, 0), ] # daily demands for work shifts (Manager, Bar, Restaurant) for each day # of the week starting on Monday. weekly_cover_demands = [ (1, 1, 1), # Monday (1, 1, 1), # Tuesday (1, 1, 1), # Wednesday (1, 1, 1), # Thursday (1, 1, 1), # Friday (1, 1, 1), # Saturday (1, 1, 1), # Sunday ] # Employee mastery level for each shift (Manager, Bar, Restaurant) # 0 = No ability 1 = Training for this position 2 = Ability employee_shift_mastery = [ (2, 0, 0), (0, 0, 2), (0, 2, 0), (0, 2, 2), (2, 0, 0), ] # Penalty for exceeding the cover constraint per shift type. excess_cover_penalties = (10, 5, 1) num_days = num_weeks * 7 num_shifts = len(shifts) model = cp_model.CpModel() work = {} # Create work binary matrice for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): work[e, s, d] = model.NewBoolVar("work%i_%i_%i" % (e, s, d)) # Linear terms of the objective in a minimization context. obj_int_vars = [] obj_int_coeffs = [] obj_bool_vars = [] obj_bool_coeffs = [] # Maximum one shift per day. for e in range(num_employees): for d in range(num_days): model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1) # Fixed assignments. for e, s, d in fixed_assignments: model.Add(work[e, s, d] == 1) # Employee requests for e, s, d, w in requests: obj_bool_vars.append(work[e, s, d]) obj_bool_coeffs.append(w) # Shift constraints for ct in shift_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): works = [work[e, shift, d] for d in range(num_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) # Weekly sum constraints for ct in weekly_sum_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): for w in range(num_weeks): works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, "weekly_sum_constraint(employee %i, shift %i, week %i)" % (e, shift, w), ) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Penalized transitions for previous_shift, next_shift, cost in penalized_transitions: for e in range(num_employees): for d in range(num_days - 1): transition = [ work[e, previous_shift, d].Not(), work[e, next_shift, d + 1].Not(), ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( "transition (employee=%i, day=%i)" % (e, d)) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) # Cover constraints for s in range(1, num_shifts): for w in range(num_weeks): for d in range(7): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] worked = model.NewIntVar(min_demand, num_employees, "") model.Add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = "excess_demand(shift=%i, week=%i, day=%i)" % (s, w, d) excess = model.NewIntVar(0, num_employees - min_demand, name) model.Add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Employee hour contract constraints for e in range(num_employees): works = [ sum(work[e, s, d] * shift_to_hour[s] for d in range(num_days)) for s in range(num_shifts) ] variables, coeffs = add_soft_sum_constraint( model, works, -tolerated_delta_contract_hours + employee_to_hour[e], employee_to_hour[e], 4, employee_to_hour[e], tolerated_delta_contract_hours + employee_to_hour[e], 4, "diff_sum_hours_contract(employee %i, week %i)" % (e, w), ) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Employee shift mastery constraints for e in range(num_employees): for s in range(1, num_shifts): coef_mastery = employee_shift_mastery[e][s - 1] if coef_mastery < 2: num_shifts_worked = model.NewIntVar(0, num_days, "") model.Add(num_shifts_worked == sum(work[e, s, d] for d in range(num_days))) if coef_mastery == 0: # Employee cannot fulfill this role model.Add(num_shifts_worked == 0) elif coef_mastery == 1: # Employee can fulfill this role with a penalty variables, coeffs = add_soft_sum_constraint( model, num_shifts_worked, 0, 0, 0, 0, num_days, 2, "mastery_constraints(employee %i, shift %i)" % (e, shift), ) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Objective 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))) # + sum(hours_per_employee) ) # if output_proto: # print("Writing proto to %s" % output_proto) # with open(output_proto, "w") as text_file: # text_file.write(str(model)) # Solve the model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 if params: text_format.Merge(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print() header = " " for w in range(num_weeks): header += "M T W T F S S " print(header) for e in range(num_employees): schedule = "" for d in range(num_days): shift_worked = "-" for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): shift_worked = shifts[s] + " " schedule += shift_worked + " " total_hours = sum( sum( solver.Value(work[e, s, d]) * shift_to_hour[s] for s in range(num_shifts)) for d in range(num_days)) print("worker %i: %s - %i" % (e, schedule, total_hours)) print() print("Penalties:") for i, var in enumerate(obj_bool_vars): if solver.BooleanValue(var): penalty = obj_bool_coeffs[i] if penalty > 0: print(" %s violated, penalty=%i" % (var.Name(), penalty)) else: print(" %s fulfilled, gain=%i" % (var.Name(), -penalty)) for i, var in enumerate(obj_int_vars): if solver.Value(var) > 0: print(" %s violated by %i, linear penalty=%i" % (var.Name(), solver.Value(var), obj_int_coeffs[i])) print() print(solver.ResponseStats())
def steel_mill_slab_with_column_generation(problem, output_proto): """Solves the Steel Mill Slab Problem.""" ### Load problem. (num_slabs, capacities, _, orders) = build_problem(problem) num_orders = len(orders) num_capacities = len(capacities) all_orders = range(len(orders)) print('Solving steel mill with %i orders, %i slabs, and %i capacities' % (num_orders, num_slabs, num_capacities - 1)) # Compute auxilliary data. widths = [x[0] for x in orders] colors = [x[1] for x in orders] max_capacity = max(capacities) loss_array = [ min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1) ] ### Model problem. # Generate all valid slabs (columns) unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths, loss_array) # Sort slab by descending load/loss. Remove duplicates. valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2]) all_valid_slabs = range(len(valid_slabs)) # create model and decision variables. model = cp_model.CpModel() selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs] for order_id in all_orders: model.Add( sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id]) == 1) # Redundant constraint (sum of loads == sum of widths). model.Add( sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)) # Objective. model.Minimize( sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs)) print('Model created') # Output model proto to file. if output_proto: output_file = open(output_proto, 'w') output_file.write(str(model.ModelProto())) output_file.close() ### Solve model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print('Loss = %i, time = %.2f s, %i conflicts' % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())) else: print('No solution')
def steel_mill_slab(problem, break_symmetries, output_proto): """Solves the Steel Mill Slab Problem.""" ### Load problem. (num_slabs, capacities, num_colors, orders) = build_problem(problem) num_orders = len(orders) num_capacities = len(capacities) all_slabs = range(num_slabs) all_colors = range(num_colors) all_orders = range(len(orders)) print('Solving steel mill with %i orders, %i slabs, and %i capacities' % (num_orders, num_slabs, num_capacities - 1)) # Compute auxilliary data. widths = [x[0] for x in orders] colors = [x[1] for x in orders] max_capacity = max(capacities) loss_array = [ min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1) ] max_loss = max(loss_array) orders_per_color = [[o for o in all_orders if colors[o] == c + 1] for c in all_colors] unique_color_orders = [ o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1 ] ### Model problem. # Create the model and the decision variables. model = cp_model.CpModel() assign = [[ model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs ] for o in all_orders] loads = [ model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s) for s in all_slabs ] color_is_in_slab = [[ model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s)) for c in all_colors ] for s in all_slabs] # Compute load of all slabs. for s in all_slabs: model.Add( sum(assign[o][s] * widths[o] for o in all_orders) == loads[s]) # Orders are assigned to one slab. for o in all_orders: model.Add(sum(assign[o]) == 1) # Redundant constraint (sum of loads == sum of widths). model.Add(sum(loads) == sum(widths)) # Link present_colors and assign. for c in all_colors: for s in all_slabs: for o in orders_per_color[c]: model.AddImplication(assign[o][s], color_is_in_slab[s][c]) model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not()) # At most two colors per slab. for s in all_slabs: model.Add(sum(color_is_in_slab[s]) <= 2) # Project previous constraint on unique_color_orders for s in all_slabs: model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2) # Symmetry breaking. for s in range(num_slabs - 1): model.Add(loads[s] >= loads[s + 1]) # Collect equivalent orders. width_to_unique_color_order = {} ordered_equivalent_orders = [] for c in all_colors: colored_orders = orders_per_color[c] if not colored_orders: continue if len(colored_orders) == 1: o = colored_orders[0] w = widths[o] if w not in width_to_unique_color_order: width_to_unique_color_order[w] = [o] else: width_to_unique_color_order[w].append(o) else: local_width_to_order = {} for o in colored_orders: w = widths[o] if w not in local_width_to_order: local_width_to_order[w] = [] local_width_to_order[w].append(o) for w, os in local_width_to_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) for w, os in width_to_unique_color_order.items(): if len(os) > 1: for p in range(len(os) - 1): ordered_equivalent_orders.append((os[p], os[p + 1])) # Create position variables if there are symmetries to be broken. if break_symmetries and ordered_equivalent_orders: print(' - creating %i symmetry breaking constraints' % len(ordered_equivalent_orders)) positions = {} for p in ordered_equivalent_orders: if p[0] not in positions: positions[p[0]] = model.NewIntVar(0, num_slabs - 1, 'position_of_slab_%i' % p[0]) model.AddMapDomain(positions[p[0]], assign[p[0]]) if p[1] not in positions: positions[p[1]] = model.NewIntVar(0, num_slabs - 1, 'position_of_slab_%i' % p[1]) model.AddMapDomain(positions[p[1]], assign[p[1]]) # Finally add the symmetry breaking constraint. model.Add(positions[p[0]] <= positions[p[1]]) # Objective. obj = model.NewIntVar(0, num_slabs * max_loss, 'obj') losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs] for s in all_slabs: model.AddElement(loads[s], loss_array, losses[s]) model.Add(obj == sum(losses)) model.Minimize(obj) ### Solve model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 objective_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, objective_printer) ### Output the solution. if status in (cp_model.OPTIMAL, cp_model.FEASIBLE): print('Loss = %i, time = %f s, %i conflicts' % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())) else: print('No solution')
def solve_shift_scheduling(params, output_proto): """Solves the shift scheduling problem.""" # Data num_employees = 8 num_weeks = 3 shifts = ['O', 'M', 'A', 'N'] # Fixed assignment: (employee, shift, day). # This fixes the first 2 days of the schedule. ## 部分已经固定的排班计划表 ## 一天有四个班次分别为 ['O', 'M', 'A', 'N'],O代表休息,morning,afternoon,night ## 8个员工一共需要排三周 ## 部分员工已经锁定了前三天的部分班次 fixed_assignments = [ (0, 0, 0), (1, 0, 0), (2, 1, 0), (3, 1, 0), (4, 2, 0), (5, 2, 0), (6, 2, 3), (7, 3, 0), (0, 1, 1), (1, 1, 1), (2, 2, 1), (3, 2, 1), (4, 2, 1), (5, 0, 1), (6, 0, 1), (7, 3, 1), ] ## 员工偏好 # Request: (employee, shift, day, weight) ## 需求(员工号,需求班次,哪天需求,喜好) # A negative weight indicates that the employee desire this assignment. ## 负数代表员工偏好某一天的某个班次,负值越大,代表该员工越喜好这个这个班次 requests = [ # Employee 3 wants the first Saturday off. ## 员工3好这口上班 (3, 0, 5, -2), # Employee 5 wants a night shift on the second Thursday. ## 员工5好这口上班 (5, 3, 10, -2), # Employee 2 does not want a night shift on the first Friday. ## 员工2不好这口上班 (2, 3, 4, 4) ] # Shift constraints on continuous sequence : # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) shift_constraints = [ # One or two consecutive days of rest, this is a hard constraint. ## 惩罚是0,乃是硬约束 (0, 1, 1, 0, 2, 2, 0), # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are # possible but penalized. ## 惩罚是大小的惩罚正数,乃是软约束 (3, 1, 2, 20, 3, 4, 5), ] # 每周轮班天数的总和限制 # Weekly sum constraints on shifts days: # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) weekly_sum_constraints = [ # Constraints on rests per week. ## 每周休息两天是可以的,休息三天就要给4的惩罚,休息1天,就要给7的惩罚 (0, 1, 2, 7, 2, 3, 4), # At least 1 night shift per week (penalized). At most 4 (hard). ## 夜班的约束,三连夜班可以,但是一次夜班都没有是有惩罚的,四连夜班也是不行 ( 3, 0, 1, 3, 3, 5, 0 ), # (0, 1, 2, 7, 2, 3, 4), (3, 0, 1, 3, 4, 4, 0), 原版,4是4似乎有问题,可到达四,到四了也不允许 ] # Penalized transitions: # (previous_shift, next_shift, penalty (0 means forbidden)) # 前一个班次,后一个班次,惩罚 penalized_transitions = [ # Afternoon to night has a penalty of 4. ## 如果2~3班次连上,就给惩罚 (2, 3, 4), # Night to morning is forbidden. ## 如果夜班3到白班0,就给硬约束0 (3, 1, 0), ] ## O 代表无排班计划,属于休息 ## M早班,A午班,N晚班 ## 每一日的排班需求 # daily demands for work shifts (morning, afternon, night) for each day # of the week starting on Monday. weekly_cover_demands = [ (2, 3, 1), # Monday (2, 3, 1), # Tuesday (2, 2, 2), # Wednesday (2, 3, 1), # Thursday (2, 2, 2), # Friday (1, 2, 3), # Saturday (1, 3, 1), # Sunday ] # Penalty for exceeding the cover constraint per shift type. ## 早中晚超过覆盖限制的惩罚 excess_cover_penalties = (2, 2, 5) num_days = num_weeks * 7 num_shifts = len(shifts) model = cp_model.CpModel() ## 工作任务分配的笛卡尔集 work = {} for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d)) # Linear terms of the objective in a minimization context. ## 线性目标? obj_int_vars = [] obj_int_coeffs = [] obj_bool_vars = [] obj_bool_coeffs = [] # Exactly one shift per day. ## 每一个员工,每个班次,最多参与一次 for e in range(num_employees): for d in range(num_days): model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1) # Fixed assignments. ## 部门员工的排班已经固定,输入就好 for e, s, d in fixed_assignments: model.Add(work[e, s, d] == 1) # Employee requests ## 把员工需求添加值obj_bool_vars ## 同时把员工需求的衡量值添加至obj_bool_coeffs for e, s, d, w in requests: obj_bool_vars.append(work[e, s, d]) obj_bool_coeffs.append(w) # Shift constraints ## 班次的约束 # ---> 前一个班次,后一个班次,惩罚 shift_constraints for ct in shift_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): works = [work[e, shift, d] for d in range(num_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) print(("----obj_bool----" * 10 + "\n") * 3) ## 前三个是员工偏好,喜欢2个不喜欢1个。 print("obj_bool_vars:\n", obj_bool_vars) # 分别是任务 员工做或者不做, print("obj_bool_coeffs:\n", obj_bool_coeffs) # Weekly sum constraints ## 周总和约束 for ct in weekly_sum_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): for w in range(num_weeks): # works,是所有work集中,所有指定日期的周,某个员工在某个班次的所有任务量 works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, 'weekly_sum_constraint(employee %i, shift %i, week %i)' % (e, shift, w)) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) print(("----obj_int----" * 10 + "\n") * 3) print("obj_int_vars:\n", obj_int_vars) print("obj_int_coeffs:\n", obj_int_coeffs) # Penalized transitions ## 类似于换模约束 for previous_shift, next_shift, cost in penalized_transitions: for e in range(num_employees): for d in range(num_days - 1): # 总的来说最后一天不存在连续夜白的可能 transition = [ work[e, previous_shift, d].Not(), work[e, next_shift, d + 1].Not() ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( 'transition (employee=%i, day=%i)' % (e, d)) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) # Cover constraints for s in range(1, num_shifts): for w in range(num_weeks): for d in range(7): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] worked = model.NewIntVar(min_demand, num_employees, '') model.Add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w, d) excess = model.NewIntVar(0, num_employees - min_demand, name) model.Add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective 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)))) if output_proto: print('Writing proto to %s' % output_proto) with open(output_proto, 'w') as text_file: text_file.write(str(model)) # Solve the model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 if params: text_format.Merge(params, solver.parameters) solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Print solution. if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print() header = ' ' for w in range(num_weeks): header += 'M T W T F S S ' print(header) for e in range(num_employees): schedule = '' for d in range(num_days): for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): schedule += shifts[s] + ' ' print('worker %i: %s' % (e, schedule)) print() print('Penalties:') for i, var in enumerate(obj_bool_vars): if solver.BooleanValue(var): penalty = obj_bool_coeffs[i] if penalty > 0: print(' %s violated, penalty=%i' % (var.Name(), penalty)) else: print(' %s fulfilled, gain=%i' % (var.Name(), -penalty)) for i, var in enumerate(obj_int_vars): if solver.Value(var) > 0: print(' %s violated by %i, linear penalty=%i' % (var.Name(), solver.Value(var), obj_int_coeffs[i])) print() print(solver.ResponseStats())
def solve_shift_scheduling(id_list,weeks): # Data employees = id_list num_employees = len(employees) num_weeks = weeks # shifts = ['O', 'M', 'A', 'N'] shifts = ['0', '1', '2', '3'] # Fixed assignment: (employee, shift, day). fixed_assignments = [] # Request: (employee, shift, day, weight) # A negative weight indicates that the employee desire this assignment. # ex. # Employee 3 wants the first Saturday off, (3, 0, 5, -2) # Employee 5 wants a night shift on the second Thursday, (5, 3, 10, -2) # Employee 2 does not want a night shift on the third Friday, (2, 3, 4, 4) requests = [] # Shift constraints on continuous sequence : # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) shift_constraints = [ # One or two consecutive days of rest, this is a hard constraint. (0, 1, 1, 0, 2, 2, 0), # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are # possible but penalized. (3, 1, 2, 20, 3, 4, 5), #! ] # Weekly sum constraints on shifts days: # (shift, hard_min, soft_min, min_penalty, # soft_max, hard_max, max_penalty) weekly_sum_constraints = [ # Constraints on rests per week. (0, 1, 1, 7, 2, 2, 4), # At least 1 night shift per week (penalized). At most 4 (hard). (3, 0, 1, 3, 4, 4, 0), ] # Penalized transitions: # (previous_shift, next_shift, penalty (0 means forbidden)) penalized_transitions = [ # Afternoon to night has a penalty of 4. (2, 3, 4), # Night to morning is forbidden. (3, 1, 0), ] # daily demands for work shifts (morning, afternon, night) for each day # of the week starting on Monday. weekly_cover_demands = [ (1, 1, 1), # Monday (1, 1, 1), # Tuesday (1, 1, 1), # Wednesday (1, 1, 1), # Thursday (1, 1, 1), # Friday (1, 1, 1), # Saturday (1, 1, 1), # Sunday ] # Penalty for exceeding the cover constraint per shift type. excess_cover_penalties = (2, 2, 5) num_days = num_weeks * 7 num_shifts = len(shifts) model = cp_model.CpModel() work = {} for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d)) # Linear terms of the objective in a minimization context. obj_int_vars = [] obj_int_coeffs = [] obj_bool_vars = [] obj_bool_coeffs = [] # Exactly one shift per day. for e in range(num_employees): for d in range(num_days): model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1) # Fixed assignments. for e, s, d in fixed_assignments: model.Add(work[e, s, d] == 1) # Employee requests for e, s, d, w in requests: obj_bool_vars.append(work[e, s, d]) obj_bool_coeffs.append(w) # Shift constraints for ct in shift_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): works = [work[e, shift, d] for d in range(num_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) # Weekly sum constraints for ct in weekly_sum_constraints: shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct for e in range(num_employees): for w in range(num_weeks): works = [work[e, shift, d + w * 7] for d in range(7)] variables, coeffs = add_soft_sum_constraint( model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, 'weekly_sum_constraint(employee %i, shift %i, week %i)' % (e, shift, w)) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Penalized transitions for previous_shift, next_shift, cost in penalized_transitions: for e in range(num_employees): for d in range(num_days - 1): transition = [ work[e, previous_shift, d].Not(), work[e, next_shift, d + 1].Not() ] if cost == 0: model.AddBoolOr(transition) else: trans_var = model.NewBoolVar( 'transition (employee=%i, day=%i)' % (e, d)) transition.append(trans_var) model.AddBoolOr(transition) obj_bool_vars.append(trans_var) obj_bool_coeffs.append(cost) # Cover constraints for s in range(1, num_shifts): for w in range(num_weeks): for d in range(7): works = [work[e, s, w * 7 + d] for e in range(num_employees)] # Ignore Off shift. min_demand = weekly_cover_demands[d][s - 1] worked = model.NewIntVar(min_demand, num_employees, '') model.Add(worked == sum(works)) over_penalty = excess_cover_penalties[s - 1] if over_penalty > 0: name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w, d) excess = model.NewIntVar(0, num_employees - min_demand, name) model.Add(excess == worked - min_demand) obj_int_vars.append(excess) obj_int_coeffs.append(over_penalty) # Objective 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)))) # Solve the model. solver = cp_model.CpSolver() solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Print solution. # if status == cp_model.OPTIMAL: # print('Status: %s' % ("OPTIMAL")) # str1 = '' # for e in range(num_employees): # str2 = '' # for d in range(num_days): # for s in range(num_shifts): # if solver.BooleanValue(work[e, s, d]): # str2 += '{"day":"%s", "shift":"%s"},' % (str(d),shifts[s]) # str2 = '[%s]' % str2[:-1] # str1 += '{"employee":"%s","shifts":%s},' % (employees[e],str2) # json_str = '[%s]' % (str1[:-1]) # # TODO: Add penalties to json. # return json_str if status == cp_model.OPTIMAL: print('Status: %s' % ("OPTIMAL")) str1 = '' for d in range(num_days): str2 = '' for s in range(num_shifts): for e in range(num_employees): if solver.BooleanValue(work[e, s, d]): str2 += '"%s",' % (employees[e]) str2 = '[%s]' % (str2[:-1]) str1 += '{"day":"%s","shifts":%s},' % (d, str2) json_str = '[%s]' % (str1[:-1]) # TODO: Add penalties to json. return json_str if status == cp_model.INFEASIBLE: return json.dumps({"message":"INFEASIBLE"})
num_shifts_per_employee += work[e, h, d] * work_time[h] model.Add(num_shifts_per_employee <= max_shifts_per_employee) model.Add(num_shifts_per_employee >= min_shifts_per_employee) # Step 7: Define the objective model.Minimize( sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars))) + sum(obj_bool_opd_vars[i] * obj_bool_opd_coeffs[i] for i in range(len(obj_bool_opd_vars)))) # Step 8: Create a solver and invoke solve solver = cp_model.CpSolver() solution_printer = cp_model.ObjectiveSolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Step 9: Call a printer and display the results if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print('worker 0(徐), worker 1(謝), worker 2(胡), worker 3(杰)') print() different_header = [['S ', 'M ', 'T ', 'W ', 'T ', 'F ', 'S '], ['M ', 'T ', 'W ', 'T ', 'F ', 'S ', 'S '], ['T ', 'W ', 'T ', 'F ', 'S ', 'S ', 'M '], ['W ', 'T ', 'F ', 'S ', 'S ', 'M ', 'T '], ['T ', 'F ', 'S ', 'S ', 'M ', 'T ', 'W '], ['F ', 'S ', 'S ', 'M ', 'T ', 'W ', 'T '], ['S ', 'S ', 'M ', 'T ', 'W ', 'T ', 'F ']] header = ' ' for i in range(num_days - 1):
def solve_shift_scheduling(livreurs, Creneaux, num_weeks, zones, max_shift_per_day, min_shift_per_day, contrainte_tp, daily_cover_demands, occ_rues, waiting_time, shift_hours): num_employees = len(livreurs.keys()) num_shifts = len(Creneaux) num_days = num_weeks * 7 model = cp_model.CpModel() work = {} for e in range(num_employees): for s in range(num_shifts): for d in range(num_days): for zone in zones: work[e, s, d, zone] = model.NewBoolVar('work%i_%i_%i_%r' % (e, s, d, zone)) work_wt = {} for e in range(num_employees): for d in range(num_days): for s in range(num_shifts): work_wt[e, s, d] = model.NewBoolVar('work_wt%i_%i_%i' % (e, s, d)) model.Add(sum(work[e, s, d, zone] for zone in zones) == 1).OnlyEnforceIf( work_wt[e, s, d]) model.Add(sum(work[e, s, d, zone] for zone in zones) == 0).OnlyEnforceIf( work_wt[e, s, d].Not()) for d in range(num_days): for s in range(1, num_shifts): for zone in zones: model.Add( sum(work[e, s, d, zone] for e in range(num_employees)) <= 1) # nb min and max of shift per day. # for e in range(num_employees): # for d in range(num_days): # model.Add(sum(work_wt[e, s, d] for s in range(1,num_shifts)) <= max_shift_per_day) # model.Add(sum(work_wt[e, s, d] for s in range(1,num_shifts)) >= min_shift_per_day) obj_bool_vars = [] obj_bool_coeffs = [] obj_int_vars = [] obj_int_coeffs = [] # total pourcentage work constraint if contrainte_tp != []: for e in range(num_employees): for w in range(num_weeks): works = [ work[e, shift, d + w * 7, zone] for zone in contrainte_tp[0] for shift in contrainte_tp[1] for d in range(7) ] weights = [ shift_hours[Creneaux[shift + 1]] for zone in contrainte_tp[0] for shift in contrainte_tp[1] for d in range(7) ] variables, coeffs = add_soft_scalar_sum_constraint( model, works, weights, int(contrainte_tp[3]), int(contrainte_tp[2]), contrainte_tp[5], int(contrainte_tp[2]), int(contrainte_tp[4]), contrainte_tp[6], 'weekly_pourc_work_constraint(employee %i, week %i)' % (e, w)) obj_int_vars.extend(variables) obj_int_coeffs.extend(coeffs) # Cover constraints for zone in zones: for d in range(num_days): works = [ work[e, s, d, zone] for s in range(1, num_shifts) for e in range(num_employees) ] # Ignore Off shift. min_demand = daily_cover_demands[d, zone] model.Add(sum(works) == min_demand) for zone in zones: for e in range(num_employees): for d in range(num_days): for s in range(1, num_shifts): obj_bool_vars.append(work[e, s, d, zone]) obj_bool_coeffs.append(occ_rues[s - 1, d, zone]) # ------------- MODIFICATION ----------------- for zone in zones: for e in range(num_employees): for d in range(num_days): for s in range(1, num_shifts): if 1 <= s <= 8 or 21 <= s <= 24: obj_bool_vars.append(work[e, s, d, zone]) obj_bool_coeffs.append(14) # --------------------------------------------- # Objective 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)))) # Solve the model. solver = cp_model.CpSolver() solver.parameters.num_search_workers = 8 solver.parameters.max_time_in_seconds = waiting_time solution_printer = cp_model.ObjectiveSolutionPrinter() status_int = solver.SolveWithSolutionCallback(model, solution_printer) # print("status : {}".format(status_int)) # print("INFEASIBLE:{}".format(cp_model.INFEASIBLE)) return solver, work, status_int