def solve_rpq_with_solver(instance: RPQ_Instance): """ W końcu funkcja rozwiązująca instancje problemu RPQ wykorzystując metodę CP z biblioteki or-tools""" from ortools.sat.python import cp_model # importujemy model CP z biblioteki or-tools model = cp_model.CpModel( ) # inicjalizacja modelu - przechowa nasze zmienne oraz ograniczenia naszego problemu # Model będzie operować na zmiennych całkowitoliczbowych - jakie zmienne? Czas rozpoczęcia, czas zakończenia, cmax... # Potrzebujemy określić zakres tych zmiennych - najmniej 0, bo nasz problem nie może mieć negatywnych czasów rozpoczęcia, zakończenia czy cmax. # Co z maksymalną wartością? Spróbujmy policzyć najbardziej pesymistyczny scenariusz (możliwie złą kolejność): # Dla tej złej kolejności zaczniemy od zadania, które ma największe r, później pozostałe zadania i na koniec zadanie o największym q # Gdybyśmy nie znali problemu, moglibyśmy po prostu dodać wszystkie czasy, byłby to gorszy zakres, więcej roboty dla solvera. max_r = 0 max_q = 0 sum_p = 0 for task_number in range( instance.tasks_number): # iterujemy po wszystkich zadaniach: sum_p = sum_p + instance.get_p(task_number) max_r = max(max_r, instance.get_r(task_number)) max_q = max(max_q, instance.get_q(task_number)) variable_max_value = 1 + max_r + sum_p + max_q # dla pewności o jeden za dużo variable_min_value = 0 # nic nie jest ujemne w naszym wypadku # Zaraz będziemy inicjalizować zmienne wewnątrz modelu, aby łatwo się do nich odwoływać - tworzymy na razie puste listy: model_start_vars = [] # tutaj będą czasy rozpoczęć zadań model_ends_vars = [] # tutaj będą czasy zakończeń zadań model_interval_vars = [ ] # tutaj będą przechowywane zmienne odpowiedzialne za zmienne interwałowe # teraz inicjalizacja zmiennych wewnątrz modelu: # zaczniemy od pojedynczej zmiennej, która będzie przechowywać cmax - proszę zauważyć, że jest to zmienna tworzona # wewnątrz modelu i nie jest to typowy int - próba sprawdzenia, czy jest to pythonowy typ int zwróci fałsz: # aby stworzyć tą zmienną musimy podać zakres oraz nazwę zmiennej cmax_optimalization_objective = model.NewIntVar(variable_min_value, variable_max_value, 'cmax_makespan') print("type of cmax:", type(cmax_optimalization_objective), isinstance(cmax_optimalization_objective, int)) # można zakomentować bez żalu # więcej zmiennych: dla czasu rozpoczęcia, zakończenia i interwałów, ale dla każdego zdania więc korzystamy z pętli for task_number in range(instance.tasks_number): suffix = f"t:{task_number}" # do zmiennych należy dodawać nazwę - u nas będzie to po prostu numer zadania start_var = model.NewIntVar( variable_min_value, variable_max_value, 'start_' + suffix ) # zmienna wewnątrz solvera odpowiedzialna za czas rozpoczęcia end_var = model.NewIntVar( variable_min_value, variable_max_value, 'end_' + suffix ) # zmienna wewnątrz solvera odpowiedzialna za czas zakończenia # zmienna interwałowa "łączy" czas rozpoczęcia oraz czas zakonczenia - dodatkowo nasz wykonania zdania trwa dokładnie p: interval_var = model.NewIntervalVar( start_var, instance.get_p(task_number), end_var, 'interval_' + suffix ) # zmienna interwałowa wewnątrz solvera odpowiedzialna za nie nakładanie się zadań # dodawanie zmiennych na listy pomocnicze: model_start_vars.append(start_var) model_ends_vars.append(end_var) model_interval_vars.append(interval_var) # Pora na dodanie ograniczeń - zacznimy od najtrudniejszego: nasze zadania nie mogą się na siebie "nakładać na siebie", # czyli jedyna maszyna w problemie RPQ może pracować na raz tylko nad jednym zadaniem. # W ramach CP jest to ograniczenie łatwe do dodania: model.AddNoOverlap(model_interval_vars) # Gdybyśmy mieli więcej maszyn to musielibyśmy trochę bardziej pokombinować, ale ponieważ w RPQ jest jedna maszyna to # dodajemy wszystkie interwały. W wypadku wielomaszynowym tylko interwały z tej samej maszyny nie mogły się nakładać. # Pora teraz na ograniczenie związane z czasem rozpoczęcia - tutaj sytuacja jest jasna: start zadania jest możliwy dopiero po upływie czasu rozpoczęcia (r) for task_number in range(instance.tasks_number): model.Add(model_start_vars[task_number] >= instance.get_r(task_number) ) # dodajemy do modelu ograniczenie w postaci nierówność # Zostało nam ograniczenie związane z czasem dostarczenia... # My zawrzemy je w ramach obliczenia cmaxa - proszę zwrócić uwagę, że cmax to największy czas dostarczenia ze wszystkich zadań # łatwo to przedstawić jako odpowiednią nierówność - cmax musi być większy/równy od czasu dostarczenia dla każdego zadania for task_number in range(instance.tasks_number): model.Add( cmax_optimalization_objective >= model_ends_vars[task_number] + instance.get_q(task_number)) # Pora dodać do modelu informacje czego właściwie szukamy - chcemy zminimalizować cmax więc: model.Minimize(cmax_optimalization_objective) # Inicjalizujemy solver, który spróbuje znaleźć rozwiązanie w ramach naszego modelu: solver = cp_model.CpSolver() solver.parameters.max_time_in_seconds = 300.0 # dodatkowo ograniczmy czas wykonywania obliczeń do maksymalnie 5 min # Wszystkie ograniczenia dodane! pora odpalić solver! status = solver.Solve( model ) # solver zwróci status, ale jako typ wyliczeniowy, więc troche nieczytelnie dla nas if ( status is not cp_model.OPTIMAL ): # sprawdzamy status, aby określić czy solver znalazł rozwiązanie optymalne status_readable = "not optimal solution :(" else: status_readable = "optimum found!" # Oprócz cmaxa przydałoby się odczytać kolejność wykonywania zadań - dla RPQ będzie to łatwe. # Wystarczy, że sprawdzimy czasy rozpoczęć (lub zakończeń) dla poszczególnych zadań: # Tworzymy listę z parami: (numer zadania, czas rozpoczęcia), sortujemy po tej drugiej wartości. pi_order = [] for task_number in range(instance.tasks_number): pi_order.append( (task_number, solver.Value(model_start_vars[task_number]))) pi_order.sort(key=lambda x: x[1]) pi_order = [ x[0] for x in pi_order ] # modyfikujemy naszą listę, aby przechowywać tylko numer zadań, bez czasów rozpoczęć return solver.ObjectiveValue( ), pi_order, status_readable # zwracamy cmax, kolejność wykonywania zadań oraz informacje czy znaleźliśmy optimum
def schedule_retry2(data, companies): """Try it Schedule with less restrictions""" # Create the model. model = cp_model.CpModel() line_size = 12 # number of slots for meetings line = list(range(0, line_size)) rows = list(range(len(data))) ls_m_c = [data[k].get('Companies') for k in rows] grid = {} for i in rows: for j in line: grid[(i, j)] = model.NewIntVarFromDomain(cp_model.Domain.FromValues(ls_m_c[i]), 'grid %i %i' % (i, j)) #AllDifferent on rows. for i in rows: model.AddAllDifferent([grid[(i, j)] for j in line]) # AllDifferent on columns. for j in line: model.AddAllDifferent([grid[(i, j)] for i in rows]) #Adding objectives: for i in rows: if ls_m_c[i][8] < 100 and ls_m_c[i][10] > 100: model.Add(grid[(i, 5)] >= 100) model.Add(grid[(i, 9)] >= 100) elif ls_m_c[i][7] < 100: model.Add((grid[(i,3)]) >= 100) model.Add((grid[(i,7)]) >= 100) model.Add((grid[(i,11)]) >= 100) elif ls_m_c[i][6] < 100: model.Add((grid[(i,3)] + grid[(i, 4)] + grid[(i, 5)]) < 100) model.Add((grid[(i,7)] + grid[(i,8)] + grid[(i,9)] + grid[(i,10)]) < 100) elif ls_m_c[i][5] < 100: model.Add((grid[(i,1)] + grid[(i,2)] + grid[(i, 3)]) < 100) model.Add((grid[(i, 5)] + grid[(i, 6)] + grid[(i, 7)]) < 100) elif ls_m_c[i][4] < 100: model.Add((grid[(i,1)] + grid[(i,2)]) < 100) model.Add((grid[(i,3)]) >= 100) model.Add((grid[(i, 4)] + grid[(i, 5)] + grid[(i, 6)]) < 100) elif ls_m_c[i][3] < 100: model.Add((grid[(i,7)] + grid[(i, 8)]) < 100) model.Add((grid[(i, 9)] + grid[(i, 10)]) < 100) elif ls_m_c[i][2] < 100: model.Add((grid[(i,8)] + grid[(i, 9)] + grid[(i,10)]) < 100) elif ls_m_c[i][1] < 100: model.Add((grid[(i,8)] + grid[(i, 9)]) < 100) # Solve and save a list with values. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: ls = [] for i in rows: ls.append([int(solver.Value(grid[(i, j)])) for j in line]) try: #Removing fake ids for m in range(len(ls)): for cl in range(len(ls[m])): if ls[m][cl] > len(companies): ls[m][cl] = np.nan # Change companies' ids by its name for l in range(len(ls)): for col in range(len(ls[l])): if ls[l][col]: for c in companies: if ls[l][col] in c: com = c.get(ls[l][col]) ls[l][col] = com #Appending to ls: mentors' name, day and block, to have a more complete list for using it later to recreate the dataframe ls_mentors = [data[i].get('Name') for i in range(len(data))] for i in range(len(ls_mentors)): ls[i].append(ls_mentors[i]) ls[i].append(data[i].get('Email')) ls[i].append(data[i].get('Day')) ls[i].append(data[i].get('AM/PM')) return (ls) except: #print('There is no deasible solution') return([])
def cp_satoptim(self): # Instantiate a cp solver. model = cp_model.CpModel() t_ce = {} vars = [] task_type = collections.namedtuple('task_type', 'start end interval') # Job Shop Scheduling # Variables horizon = self.ub all_tasks = {} for id in self.t_s.keys(): core = self.tsk_placement[id] start_var = model.NewIntVar(0, horizon, 'start_%s_%s' % (id, core)) duration = self.wf.node[id][core] end_var = model.NewIntVar(0, horizon, 'end_%s_%s' % (id, core)) interval_var = model.NewIntervalVar(start_var, duration, end_var, 'interval_%s_%s' % (id, core)) all_tasks[id] = task_type(start=start_var, end=end_var, interval=interval_var) # Creates sequence variables and add disjunctive constraints. for core in self.crs.nodes(): self.rbt_assignment[core] = [ t for t in self.nd_list if self.tsk_placement[t] == core ] intervals = [] for tsk in self.rbt_assignment[core]: intervals.append(all_tasks[tsk].interval) model.AddNoOverlap(intervals) # Add precedence contraints. for j in self.nd_list: for i in self.wf.predecessors(j): if self.tsk_placement[i] == self.tsk_placement[j]: model.Add(all_tasks[j].start >= all_tasks[i].end) else: k, l = self.tsk_placement[i], self.tsk_placement[j] model.Add(all_tasks[j].start >= all_tasks[i].end + round(self.wf[i][j]['data'] / self.crs[k][l]['bandwidth'])) model.Minimize(all_tasks[self.des_node].end) # Solve model. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: # Print out makespan. self.optimal_obj = solver.ObjectiveValue() # print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) # print() for id in self.t_s.keys(): # if collector.Value(best_solution, x[i][j]) == 1: # self.t_s[id] = collector.Value(best_solution,t_cs[id]) self.t_s[id] = solver.Value(all_tasks[id].start) self.t_e[id] = solver.Value(all_tasks[id].end) # print("ts%s=%d, te%s=%d" % (id, self.t_s[id], id, self.t_e[id])) self.rbt_occupy = {} for r in self.crs.nodes(): t_list = [k for (k, v) in self.tsk_placement.items() if v == r] t_list.sort(key=lambda x: self.t_s[x]) self.rbt_occupy[r] = t_list return self.optimal_obj
def _run_bipartite_sat(I, input_edges, adj, h_count, v_count, verbose, timeout, return_walltime): """ Python implementation for FT-BTE's constraint programming formulation using Google OR-Tools solver. """ from ortools.sat.python import cp_model NL, NR = adj.shape assert NL == len(h_count) assert NR == len(v_count) model = cp_model.CpModel() # decision variables yl = np.array([[model.NewBoolVar(f"yl_{i}_{j}") for j in range(NL)] for i in range(I)]) yr = np.array([[model.NewBoolVar(f"yr_{i}_{j}") for j in range(NR)] for i in range(I)]) valid_edge = [(l, r) for l in range(NL) for r in range(NR) if adj[l, r] == 1] if verbose: print(f"(NL: {NL} NR: {NR} Valid edges: {len(valid_edge)})") print("Building constraints...") # edge constraints for u, v in input_edges: or_terms = [] for l, r in valid_edge: uv = model.NewBoolVar(f"lr_({u},{v})_({l},{r})") model.AddImplication(uv, yl[u, l]) model.AddImplication(uv, yr[v, r]) vu = model.NewBoolVar(f"lr_({v},{u})_({l},{r})") model.AddImplication(vu, yl[v, l]) model.AddImplication(vu, yr[u, r]) or_terms.extend((uv, vu)) model.AddBoolOr(or_terms) # input nodes must be assigned to connected nodes for i in range(I): for l in range(NL): for r in range(NR): model.Add(yl[i, l] + yr[i, r] <= int(1 + adj[l, r])) # input nodes are assigned once per partition for i in range(I): model.Add(sum(yl[i, :]) <= 1) for i in range(I): model.Add(sum(yr[i, :]) <= 1) # template nodes are assigned max 1 input node for l in range(NL): model.Add(sum(yl[:, l]) <= h_count[l]) for r in range(NR): model.Add(sum(yr[:, r]) <= v_count[r]) if verbose: print("Finished building constraints") solver = cp_model.CpSolver() solver.parameters.use_pb_resolution = True solver.parameters.log_search_progress = verbose solver.parameters.max_time_in_seconds = timeout status = solver.Solve(model) if status != cp_model.OPTIMAL: return None result = np.full((I, 2), -1) for i in range(I): for l in range(NL): if solver.BooleanValue(yl[i, l]): result[i, 0] = l break for r in range(NR): if solver.BooleanValue(yr[i, r]): result[i, 1] = r break if return_walltime: return result, solver.WallTime() else: return result
def MinimalJobshopSat(jobs_data): """Minimal jobshop problem.""" # Create the model. model = cp_model.CpModel() machines_count = 1 + max(task[0] for job in jobs_data for task in job) all_machines = range(machines_count) # Computes horizon dynamically as the sum of all durations. horizon = sum(task[1] for job in jobs_data for task in job) # Named tuple to store information about created variables. task_type = collections.namedtuple('task_type', 'start end interval') # Named tuple to manipulate solution information. assigned_task_type = collections.namedtuple('assigned_task_type', 'start job index duration') # Creates job intervals and add to the corresponding machine lists. all_tasks = {} machine_to_intervals = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): machine = task[0] duration = task[1] suffix = '_%i_%i' % (job_id, task_id) start_var = model.NewIntVar(0, horizon, 'start' + suffix) end_var = model.NewIntVar(0, horizon, 'end' + suffix) interval_var = model.NewIntervalVar(start_var, duration, end_var, 'interval' + suffix) all_tasks[job_id, task_id] = task_type(start=start_var, end=end_var, interval=interval_var) machine_to_intervals[machine].append(interval_var) # Create and add disjunctive constraints. for machine in all_machines: model.AddNoOverlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): model.Add(all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end) # Makespan objective. obj_var = model.NewIntVar(0, horizon, 'makespan') model.AddMaxEquality(obj_var, [ all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data) ]) model.Minimize(obj_var) # Solve model. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: # Create one list of assigned tasks per machine. assigned_jobs = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): machine = task[0] assigned_jobs[machine].append( assigned_task_type(start=solver.Value( all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1])) # Create per machine output lines. output = '' for machine in all_machines: # Sort by starting time. assigned_jobs[machine].sort() sol_line_tasks = 'Machine ' + str(machine) + ': ' sol_line = ' ' for assigned_task in assigned_jobs[machine]: name = 'job_%i_%i' % (assigned_task.job, assigned_task.index) # Add spaces to output to align columns. sol_line_tasks += '%-10s' % name start = assigned_task.start duration = assigned_task.duration sol_tmp = '[%i,%i]' % (start, start + duration) # Add spaces to output to align columns. sol_line += '%-10s' % sol_tmp sol_line += '\n' sol_line_tasks += '\n' output += sol_line_tasks output += sol_line # Finally print the solution found. print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) print(output)
def main(): model = cp_model.CpModel() jobs = [[3, 3], [2, 5], [1, 3], [3, 7], [7, 3], [2, 2], [2, 2], [5, 5], [10, 2], [4, 3], [2, 6], [1, 2], [6, 8], [4, 5], [3, 7]] max_length = 10 horizon = sum(t[0] for t in jobs) num_jobs = len(jobs) all_jobs = range(num_jobs) intervals = [] intervals0 = [] intervals1 = [] performed = [] starts = [] ends = [] demands = [] for i in all_jobs: # Create main interval. start = model.NewIntVar(0, horizon, 'start_%i' % i) duration = jobs[i][0] end = model.NewIntVar(0, horizon, 'end_%i' % i) interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i) starts.append(start) intervals.append(interval) ends.append(end) demands.append(jobs[i][1]) performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i) performed.append(performed_on_m0) # Create an optional copy of interval to be executed on machine 0. start0 = model.NewOptionalIntVar(0, horizon, performed_on_m0, 'start_%i_on_m0' % i) end0 = model.NewOptionalIntVar(0, horizon, performed_on_m0, 'end_%i_on_m0' % i) interval0 = model.NewOptionalIntervalVar(start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i) intervals0.append(interval0) # Create an optional copy of interval to be executed on machine 1. start1 = model.NewOptionalIntVar(0, horizon, performed_on_m0.Not(), 'start_%i_on_m1' % i) end1 = model.NewOptionalIntVar(0, horizon, performed_on_m0.Not(), 'end_%i_on_m1' % i) interval1 = model.NewOptionalIntervalVar(start1, duration, end1, performed_on_m0.Not(), 'interval_%i_on_m1' % i) intervals1.append(interval1) # We only propagate the constraint if the tasks is performed on the machine. model.Add(start0 == start).OnlyEnforceIf(performed_on_m0) model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not()) # Max Length constraint (modeled as a cumulative) model.AddCumulative(intervals, demands, max_length) # Choose which machine to perform the jobs on. model.AddNoOverlap(intervals0) model.AddNoOverlap(intervals1) # Objective variable. makespan = model.NewIntVar(0, horizon, 'makespan') model.AddMaxEquality(makespan, ends) model.Minimize(makespan) # Symmetry breaking. model.Add(performed[0] == 0) # Solve model. solver = cp_model.CpSolver() solver.Solve(model) # Output solution. if visualization.RunFromIPython(): output = visualization.SvgWrapper(solver.ObjectiveValue(), max_length, 40.0) output.AddTitle('Makespan = %i' % solver.ObjectiveValue()) color_manager = visualization.ColorManager() color_manager.SeedRandomColor(0) for i in all_jobs: performed_machine = 1 - solver.Value(performed[i]) start = solver.Value(starts[i]) dx = jobs[i][0] dy = jobs[i][1] sy = performed_machine * (max_length - dy) output.AddRectangle(start, sy, dx, dy, color_manager.RandomColor(), 'black', 'j%i' % i) output.AddXScale() output.AddYScale() output.Display() else: print('Solution') print(' - makespan = %i' % solver.ObjectiveValue()) for i in all_jobs: performed_machine = 1 - solver.Value(performed[i]) start = solver.Value(starts[i]) print(' - Job %i starts at %i on machine %i' % (i, start, performed_machine)) print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f ms' % solver.WallTime())
def reschedule_retry(data, companies): """Rescheduling programm""" # Create the model. model = cp_model.CpModel() line_size = 12 # number of slots for meetings line = list(range(0, line_size)) rows = list(range(len(data))) ls_m_c = [data[k].get('Companies') for k in rows] initial_grid = [] initial_grid.append(list(np.zeros(12, dtype=int))) for ls in range(1, len(ls_m_c)): initial_grid.append(ls_m_c[ls]) grid = {} for i in rows: for j in line: grid[(i, j)] = model.NewIntVarFromDomain( cp_model.Domain.FromValues(ls_m_c[i]), 'grid %i %i' % (i, j)) #AllDifferent on rows. for i in rows: model.AddAllDifferent([grid[(i, j)] for j in line]) # AllDifferent on columns. for j in line: model.AddAllDifferent([grid[(i, j)] for i in rows]) #Adding objectives: #for i in rows: if ls_m_c[0][8] < 100 and ls_m_c[0][10] > 100: model.Add(grid[(0, 5)] >= 100) model.Add(grid[(0, 9)] >= 100) elif ls_m_c[0][7] < 100: model.Add((grid[(0, 0)] + grid[(0, 1)] + grid[(0, 2)]) < 100) model.Add((grid[(0, 3)]) >= 100) model.Add((grid[(0, 4)] + grid[(0, 5)] + grid[(0, 6)]) < 100) model.Add((grid[(0, 7)]) >= 100) model.Add((grid[(0, 8)] + grid[(0, 9)]) < 100) elif ls_m_c[0][6] < 100: model.Add((grid[(0, 3)] + grid[(0, 4)] + grid[(0, 5)]) < 100) model.Add( (grid[(0, 7)] + grid[(0, 8)] + grid[(0, 9)] + grid[(0, 10)]) < 100) elif ls_m_c[0][5] < 100: model.Add((grid[(0, 1)] + grid[(0, 2)] + grid[(0, 3)]) < 100) model.Add((grid[(0, 4)]) >= 100) model.Add((grid[(0, 5)] + grid[(0, 6)] + grid[(0, 7)]) < 100) elif ls_m_c[0][4] < 100: model.Add((grid[(0, 1)] + grid[(0, 2)]) < 100) model.Add((grid[(0, 3)]) >= 100) model.Add((grid[(0, 4)] + grid[(0, 5)] + grid[(0, 6)]) < 100) elif ls_m_c[0][3] < 100: model.Add((grid[(0, 7)] + grid[(0, 8)]) < 100) model.Add((grid[(0, 9)] + grid[(0, 10)]) < 100) elif ls_m_c[0][2] < 100: model.Add((grid[(0, 8)] + grid[(0, 9)] + grid[(0, 10)]) < 100) elif ls_m_c[0][1] < 100: model.Add((grid[(0, 8)] + grid[(0, 9)]) < 100) #Initial values. for i in rows: for j in line: if initial_grid[i][j] != 0: model.Add(grid[(i, j)] == initial_grid[i][j]) # Solve and save a list with values. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: ls = [] for i in rows: ls.append([int(solver.Value(grid[(i, j)])) for j in line]) try: #Removing fake ids for m in range(len(ls)): for cl in range(len(ls[m])): if ls[m][cl] > len(companies): ls[m][cl] = np.nan # Change companies' ids by its name for l in range(len(ls)): for col in range(len(ls[l])): if ls[l][col]: for c in companies: if ls[l][col] in c: com = c.get(ls[l][col]) ls[l][col] = com #Appending to ls: mentors' name, day and block, to have a more complete list for using it later to recreate the dataframe ls_mentors = [data[i].get('mentor_id') for i in range(len(data))] ls_mentors[0] = data[0].get('mentor') for i in range(len(ls_mentors)): ls[i].append(ls_mentors[i]) #ls[i].append(data[i].get('Email')) ls[i].append(data[i].get('day_id')) ls[i].append(data[i].get('block_id')) return (ls) except: #print('There is no feasible solution') #retry = schedule_retry(data, companies) return ([])
def main(): model = cp.CpModel() # # data # num_skis = 6 num_skiers = 5 ski_heights = [1, 2, 5, 7, 13, 21] skier_heights = [3, 4, 7, 11, 18] # # variables # # which ski to choose for each skier x = [ model.NewIntVar(0, num_skis - 1, 'x[%i]' % i) for i in range(num_skiers) ] z = model.NewIntVar(0, sum(ski_heights), 'z') # # constraints # model.AddAllDifferent(x) # Old: # z_tmp = [ # abs(solver.Element(ski_heights, x[i]) - skier_heights[i]) # for i in range(num_skiers) # ] z_tmp = [model.NewIntVar(0, 100, "z_tmp") for i in range(num_skiers)] for i in range(num_skiers): t1 = model.NewIntVar(-100, 100, "t1") model.AddElement(x[i], ski_heights, t1) t2 = model.NewIntVar(-100, 100, "t1") model.Add(t2 == t1 - skier_heights[i]) model.AddAbsEquality(z_tmp[i], t2) model.Add(z == sum(z_tmp)) # objective model.Minimize(z) # # search and result # solver = cp.CpSolver() status = solver.Solve(model) if status == cp.OPTIMAL: print('total differences:', solver.Value(z)) for i in range(num_skiers): x_val = solver.Value(x[i]) ski_height = ski_heights[solver.Value(x[i])] diff = ski_height - skier_heights[i] print('Skier %i: Ski %i with length %2i (diff: %2i)' %\ (i, x_val, ski_height, diff)) print() print() print('NumConflicts:', solver.NumConflicts()) print('NumBranches:', solver.NumBranches()) print('WallTime:', solver.WallTime())
def jobshop_with_maintenance(): """Solves a jobshop with maintenance on one machine.""" # Create the model. model = cp_model.CpModel() jobs_data = [ # task = (machine_id, processing_time). [(0, 3), (1, 2), (2, 2)], # Job0 [(0, 2), (2, 1), (1, 4)], # Job1 [(1, 4), (2, 3)], # Job2 ] machines_count = 1 + max(task[0] for job in jobs_data for task in job) all_machines = range(machines_count) # Computes horizon dynamically as the sum of all durations. horizon = sum(task[1] for job in jobs_data for task in job) # Named tuple to store information about created variables. task_type = collections.namedtuple('Task', 'start end interval') # Named tuple to manipulate solution information. assigned_task_type = collections.namedtuple('assigned_task_type', 'start job index duration') # Creates job intervals and add to the corresponding machine lists. all_tasks = {} machine_to_intervals = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): machine = task[0] duration = task[1] suffix = '_%i_%i' % (job_id, task_id) start_var = model.NewIntVar(0, horizon, 'start' + suffix) end_var = model.NewIntVar(0, horizon, 'end' + suffix) interval_var = model.NewIntervalVar(start_var, duration, end_var, 'interval' + suffix) all_tasks[job_id, task_id] = task_type(start=start_var, end=end_var, interval=interval_var) machine_to_intervals[machine].append(interval_var) # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}). machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, 'weekend_0')) # Create and add disjunctive constraints. for machine in all_machines: model.AddNoOverlap(machine_to_intervals[machine]) # Precedences inside a job. for job_id, job in enumerate(jobs_data): for task_id in range(len(job) - 1): model.Add(all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end) # Makespan objective. obj_var = model.NewIntVar(0, horizon, 'makespan') model.AddMaxEquality(obj_var, [ all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data) ]) model.Minimize(obj_var) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() status = solver.Solve(model, solution_printer) # Output solution. if status == cp_model.OPTIMAL: # Create one list of assigned tasks per machine. assigned_jobs = collections.defaultdict(list) for job_id, job in enumerate(jobs_data): for task_id, task in enumerate(job): machine = task[0] assigned_jobs[machine].append( assigned_task_type(start=solver.Value( all_tasks[job_id, task_id].start), job=job_id, index=task_id, duration=task[1])) # Create per machine output lines. output = '' for machine in all_machines: # Sort by starting time. assigned_jobs[machine].sort() sol_line_tasks = 'Machine ' + str(machine) + ': ' sol_line = ' ' for assigned_task in assigned_jobs[machine]: name = 'job_%i_%i' % (assigned_task.job, assigned_task.index) # Add spaces to output to align columns. sol_line_tasks += '%-10s' % name start = assigned_task.start duration = assigned_task.duration sol_tmp = '[%i,%i]' % (start, start + duration) # Add spaces to output to align columns. sol_line += '%-10s' % sol_tmp sol_line += '\n' sol_line_tasks += '\n' output += sol_line_tasks output += sol_line # Finally print the solution found. print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) print(output) print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def main(): # Data. num_groups = 10 num_items = 100 num_colors = 3 min_items_of_same_color_per_group = 4 all_groups = range(num_groups) all_items = range(num_items) all_colors = range(num_colors) # Values for each items. values = [1 + i + (i * i // 200) for i in all_items] # Color for each item (simple modulo). colors = [i % num_colors for i in all_items] sum_of_values = sum(values) average_sum_per_group = sum_of_values // num_groups num_items_per_group = num_items // num_groups # Collect all items in a given color. items_per_color = {} for c in all_colors: items_per_color[c] = [] for i in all_items: if colors[i] == c: items_per_color[c].append(i) print('Model has %i items, %i groups, and %i colors' % (num_items, num_groups, num_colors)) print(' average sum per group = %i' % average_sum_per_group) # Model. model = cp_model.CpModel() item_in_group = {} for i in all_items: for g in all_groups: item_in_group[(i, g)] = model.NewBoolVar('item %d in group %d' % (i, g)) # Each group must have the same size. for g in all_groups: model.Add( sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group) # One item must belong to exactly one group. for i in all_items: model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1) # The deviation of the sum of each items in a group against the average. e = model.NewIntVar(0, 550, 'epsilon') # Constrain the sum of values in one group around the average sum per group. for g in all_groups: model.Add( sum(item_in_group[(i, g)] * values[i] for i in all_items) <= average_sum_per_group + e) model.Add( sum(item_in_group[(i, g)] * values[i] for i in all_items) >= average_sum_per_group - e) # color_in_group variables. color_in_group = {} for g in all_groups: for c in all_colors: color_in_group[(c, g)] = model.NewBoolVar( 'color %d is in group %d' % (c, g)) # Item is in a group implies its color is in that group. for i in all_items: for g in all_groups: model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)]) # If a color is in a group, it must contains at least # min_items_of_same_color_per_group items from that color. for c in all_colors: for g in all_groups: literal = color_in_group[(c, g)] model.Add( sum(item_in_group[(i, g)] for i in items_per_color[c]) >= min_items_of_same_color_per_group).OnlyEnforceIf(literal) # Compute the maximum number of colors in a group. max_color = num_items_per_group // min_items_of_same_color_per_group # Redundant contraint: The problem does not solve in reasonable time without it. if max_color < num_colors: for g in all_groups: model.Add( sum(color_in_group[(c, g)] for c in all_colors) <= max_color) # Minimize epsilon model.Minimize(e) solver = cp_model.CpSolver() solution_printer = SolutionPrinter(values, colors, all_groups, all_items, item_in_group) status = solver.SolveWithSolutionCallback(model, solution_printer) if status == cp_model.OPTIMAL: print('Optimal epsilon: %i' % solver.ObjectiveValue()) print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime()) else: print('No solution found')
def flexible_jobshop(): """Solve a small flexible jobshop problem.""" # Data part. # jobs = [[[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1), (1, 2)]], # [[(2, 0), (3, 1), (4, 2)], [(1, 0), (5, 1), (4, 2)], [(2, 0), (1, 1), (4, 2)]], # [[(2, 0), (1, 1), (4, 2)], [(2, 0), (3, 1), (4, 2)], [(3, 0), (1, 1), (5, 2)]] # ] # yapf:disable jobs = [[[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(3, 0), (3, 1), (3, 2)], [(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(2, 3), (2, 4)], [(1, 5), (1, 6)]], [[(1, 5), (1, 6)]], [[(1, 5), (1, 6)]], [[(1, 5), (1, 6)]], ] # yapf:disable ## 3个JOB num_jobs = len(jobs) ## JOB编号0,1,2 all_jobs = range(num_jobs) ## 3台机器 num_machines = 3 ## 机器编号0,1,2 all_machines = range(num_machines) # Model the flexible jobshop problem. model = cp_model.CpModel() horizon = 0 for job in jobs: ## [[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1), (1, 2)]] for task in job: ## [(3, 0), (1, 1), (5, 2)] max_task_duration = 0 for alternative in task: ## (3, 0) max_task_duration = max(max_task_duration, alternative[0]) horizon += max_task_duration print('Horizon = %i' % horizon) # Global storage of variables. intervals_per_resources = collections.defaultdict(list) starts = {} # indexed by (job_id, task_id). presences = {} # indexed by (job_id, task_id, alt_id). job_ends = [] # Scan the jobs and create the relevant variables and intervals. for job_id in all_jobs: # [0,1,2] job = jobs[job_id] ## [[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1), (1, 2)]] num_tasks = len(job) ## 一个job 三个task previous_end = None for task_id in range(num_tasks): # [0,1,2] task = job[task_id] # [(3, 0), (1, 1), (5, 2)] -> [(2, 0), (4, 1), (6, 2)] -> [(2, 0), (3, 1), (1, 2)] min_duration = task[0][0] # 初始化min max_duration = task[0][0] # 初始化max num_alternatives = len(task) # 一个 task 有三个机器可以选 all_alternatives = range(num_alternatives) # 可选编号0,1,2 for alt_id in range(1, num_alternatives): # 从第二个开始比 alt_duration = task[alt_id][0] min_duration = min(min_duration, alt_duration) max_duration = max(max_duration, alt_duration) # Create main interval for the task. suffix_name = '_j%i_t%i' % (job_id, task_id) start = model.NewIntVar(0, horizon, 'start' + suffix_name) duration = model.NewIntVar(min_duration, max_duration, 'duration' + suffix_name) end = model.NewIntVar(0, horizon, 'end' + suffix_name) interval = model.NewIntervalVar(start, duration, end, 'interval' + suffix_name) # Store the start for the solution. starts[(job_id, task_id)] = start # Add precedence with previous task in the same job. if previous_end: model.Add(start >= previous_end) previous_end = end # Create alternative intervals. if num_alternatives > 1: l_presences = [] for alt_id in all_alternatives:# 可选编号0,1,2 alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id) l_presence = model.NewBoolVar('presence' + alt_suffix) l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix) l_duration = task[alt_id][0] l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix) l_interval = model.NewOptionalIntervalVar( l_start, l_duration, l_end, l_presence, 'interval' + alt_suffix) l_presences.append(l_presence) # Link the master variables with the local ones. model.Add(start == l_start).OnlyEnforceIf(l_presence) model.Add(duration == l_duration).OnlyEnforceIf(l_presence) model.Add(end == l_end).OnlyEnforceIf(l_presence) # Add the local interval to the right machine. intervals_per_resources[task[alt_id][1]].append(l_interval) # Store the presences for the solution. presences[(job_id, task_id, alt_id)] = l_presence # Select exactly one presence variable. model.Add(sum(l_presences) == 1) else: intervals_per_resources[task[0][1]].append(interval) presences[(job_id, task_id, 0)] = model.NewConstant(1) job_ends.append(previous_end) # Create machines constraints. for machine_id in all_machines: intervals = intervals_per_resources[machine_id] if len(intervals) > 1: model.AddNoOverlap(intervals) # Makespan objective makespan = model.NewIntVar(0, horizon, 'makespan') model.AddMaxEquality(makespan, job_ends) model.Minimize(makespan) # Solve model. solver = cp_model.CpSolver() solution_printer = SolutionPrinter() status = solver.SolveWithSolutionCallback(model, solution_printer) # Print final solution. for job_id in all_jobs: print('Job %i:' % job_id) for task_id in range(len(jobs[job_id])): start_value = solver.Value(starts[(job_id, task_id)]) machine = -1 duration = -1 selected = -1 for alt_id in range(len(jobs[job_id][task_id])): if solver.Value(presences[(job_id, task_id, alt_id)]): duration = jobs[job_id][task_id][alt_id][0] machine = jobs[job_id][task_id][alt_id][1] selected = alt_id print( ' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' % (job_id, task_id, start_value, selected, machine, duration)) print('Solve status: %s' % solver.StatusName(status)) print('Optimal objective value: %i' % solver.ObjectiveValue()) print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def puzzle(problem=1): print("Problem", problem) model = cp.CpModel() n = 10 # variables all = [model.NewIntVar(0, 9, f"all[{i}]") for i in range(n)] [x0, x1, x2, x3, _x4, x5, x6, x7, x8, x9] = all x = model.NewIntVar(0, 9, "x") if problem == 1: model.Add(x8 + x8 + x0 + x9 == 6) model.Add(x7 + x1 + x1 + x1 == 0) model.Add(x2 + x1 + x7 + x2 == 0) model.Add(x6 + x6 + x6 + x6 == 4) model.Add(x1 + x1 + x1 + x1 == 0) model.Add(x3 + x2 + x1 + x3 == 0) model.Add(x7 + x6 + x6 + x2 == 2) model.Add(x9 + x3 + x1 + x2 == 1) model.Add(x0 + x0 + x0 + x0 == 4) model.Add(x2 + x2 + x2 + x2 == 0) model.Add(x3 + x3 + x3 + x3 == 0) model.Add(x5 + x5 + x5 + x5 == 0) model.Add(x8 + x1 + x9 + x3 == 3) model.Add(x8 + x0 + x9 + x6 == 5) model.Add(x7 + x7 + x7 + x7 == 0) model.Add(x9 + x9 + x9 + x9 == 4) model.Add(x7 + x7 + x5 + x6 == 1) model.Add(x6 + x8 + x5 + x5 == 3) model.Add(x9 + x8 + x8 + x1 == 5) model.Add(x5 + x5 + x3 + x1 == 0) model.Add(x2 + x5 + x8 + x1 == x) else: # This yields two different values for x model.Add(x8 + x8 + x0 + x9 == 6) model.Add(x7 + x6 + x6 + x2 == 2) model.Add(x9 + x3 + x1 + x2 == 1) model.Add(x8 + x1 + x9 + x3 == 3) model.Add(x8 + x0 + x9 + x6 == 5) model.Add(x7 + x7 + x5 + x6 == 1) model.Add(x6 + x8 + x5 + x5 == 3) model.Add(x9 + x8 + x8 + x1 == 5) model.Add(x2 + x5 + x8 + x1 == x) solver = cp.CpSolver() solution_printer = SimpleSolutionPrinter2([x, all]) status = solver.SearchForAllSolutions(model, solution_printer) if not status in [cp.OPTIMAL, cp.FEASIBLE]: print("No solution!") print() print("NumSolutions:", solution_printer.SolutionCount()) print("NumConflicts:", solver.NumConflicts()) print("NumBranches:", solver.NumBranches()) print("WallTime:", solver.WallTime())
def Futoshiki(input, output): Cpmodel = cp_model.CpModel() CpSolver = cp_model.CpSolver() A1 = Cpmodel.NewIntVar(1, 4, 'A1') A2 = Cpmodel.NewIntVar(1, 4, 'A2') A3 = Cpmodel.NewIntVar(1, 4, 'A3') A4 = Cpmodel.NewIntVar(1, 4, 'A4') B1 = Cpmodel.NewIntVar(1, 4, 'B1') B2 = Cpmodel.NewIntVar(1, 4, 'B2') B3 = Cpmodel.NewIntVar(1, 4, 'B3') B4 = Cpmodel.NewIntVar(1, 4, 'B4') C1 = Cpmodel.NewIntVar(1, 4, 'C1') C2 = Cpmodel.NewIntVar(1, 4, 'C2') C3 = Cpmodel.NewIntVar(1, 4, 'C3') C4 = Cpmodel.NewIntVar(1, 4, 'C4') D1 = Cpmodel.NewIntVar(1, 4, 'D1') D2 = Cpmodel.NewIntVar(1, 4, 'D2') D3 = Cpmodel.NewIntVar(1, 4, 'D3') D4 = Cpmodel.NewIntVar(1, 4, 'D4') firstRow = [A1, A2, A3, A4] secondRow = [B1, B2, B3, B4] thirdRow = [C1, C2, C3, C4] fourthRow = [D1, D2, D3, D4] firstColumn = [A1, B1, C1, D1] secondColumn = [A2, B2, C2, D2] thirdColumn = [A3, B3, C3, D3] fourthColumn = [A4, B4, C4, D4] Cpmodel.AddAllDifferent(firstRow) Cpmodel.AddAllDifferent(secondRow) Cpmodel.AddAllDifferent(thirdRow) Cpmodel.AddAllDifferent(fourthRow) Cpmodel.AddAllDifferent(firstColumn) Cpmodel.AddAllDifferent(secondColumn) Cpmodel.AddAllDifferent(thirdColumn) Cpmodel.AddAllDifferent(fourthColumn) inputFile = open(input, 'r') lines = inputFile.readlines() linesplit = [] for i in lines: linesplit.append(i.strip().split(",")) for x in linesplit: try: if (int(x[1]) > 0): Cpmodel.Add(eval(x[0]) == eval(x[1])) except: Cpmodel.Add(eval(x[0]) > eval(x[1])) inputFile.close() outputFile = open(output, 'w') status = CpSolver.Solve(Cpmodel) if status == cp_model.FEASIBLE: outputFile.writelines( str(CpSolver.Value(A1)) + "," + str(CpSolver.Value(A2)) + "," + str(CpSolver.Value(A3)) + "," + str(CpSolver.Value(A4)) + "\n") outputFile.writelines( str(CpSolver.Value(B1)) + "," + str(CpSolver.Value(B2)) + "," + str(CpSolver.Value(B3)) + "," + str(CpSolver.Value(B4)) + "\n") outputFile.writelines( str(CpSolver.Value(C1)) + "," + str(CpSolver.Value(C2)) + "," + str(CpSolver.Value(C3)) + "," + str(CpSolver.Value(C4)) + "\n") outputFile.writelines( str(CpSolver.Value(D1)) + "," + str(CpSolver.Value(D2)) + "," + str(CpSolver.Value(D3)) + "," + str(CpSolver.Value(D4)) + "\n")
def filtered_neighbors(points): node_count = len(points) # Neighbor Detection xy = np.array(points) kdt = KDTree(xy) k = get_k_value(node_count) + 1 dist, neighbors = kdt.query(xy, k) # Create CP Model model = cp_model.CpModel() # Define Variables a = [] # whether i and j are connected u = [] # order of points for i in range(node_count): row = [model.NewBoolVar(f"a_{i}_{j}") for j in range(node_count)] a.append(row) u = [ model.NewIntVar(0, node_count - 1, f"u_{i}") for i in range(node_count) ] # Add Constraints for i in range(node_count): model.AddAbsEquality(0, a[i][i]) model.Add(sum([a[i][j] for j in range(node_count)]) == 1) model.Add(sum([a[j][i] for j in range(node_count)]) == 1) for i in range(node_count - 1): for j in range(i, node_count): model.Add(a[i][j] + a[j][i] <= 1) # Connectivity Constraint model.AddAllDifferent(u) for i in range(1, node_count): for j in range(1, node_count): if i == j: continue model.Add(u[i] - u[j] + node_count * a[i][j] <= node_count - 1) model.Add(u[0] == 0) # Proximity Constraint for i in range(node_count): neigh = neighbors[i, 1:] for j in range(node_count): if j not in neigh: model.AddAbsEquality(0, a[i][j]) model.AddAbsEquality(0, a[j][i]) # Objective obj = [] for i in range(node_count): for j in range(node_count): d = distance(points[i], points[j]) * 100 obj.append(int(d) * a[i][j]) model.Minimize(sum(obj)) # Solve max_time = estimate_time(node_count) max_time = 600 cpsolver = cp_model.CpSolver() cpsolver.parameters.max_time_in_seconds = max_time status = cpsolver.Solve(model) print(cpsolver.StatusName()) if status == cp_model.MODEL_INVALID or status == cp_model.INFEASIBLE: raise RuntimeError("Unable to find a solution.") # Extract Solution sol_mat = [] for i in range(node_count): row = [cpsolver.Value(a[i][j]) for j in range(node_count)] sol_mat.append(row) sol_mat = np.array(sol_mat) # Convert solution to readable format sol = matrix_to_route(sol_mat) return status == cp_model.OPTIMAL, sol
def main(words, word_len, num_answers=5): model = cp.CpModel() # # data # num_words = len(words) n = word_len d, _rev = get_dict() # # declare variables # A = {} for i in range(num_words): for j in range(word_len): A[(i, j)] = model.NewIntVar(0, 29, "A(%i,%i)" % (i, j)) A_flat = [A[(i, j)] for i in range(num_words) for j in range(word_len)] E = [model.NewIntVar(0, num_words, "E%i" % i) for i in range(n)] # # constraints # model.AddAllDifferent(E) # copy the words to a Matrix for I in range(num_words): for J in range(word_len): model.Add(A[(I, J)] == d[words[I][J]]) for i in range(word_len): for j in range(word_len): # This is what I would like to do: # solver.Add(A[(E[i],j)] == A[(E[j],i)]) # We must use Element explicitly val = model.NewIntVar(0, 29, "t") ix1 = model.NewIntVar(0, num_words, "t") model.Add(ix1 == E[i] * word_len + j) ix2 = model.NewIntVar(0, num_words, "t") model.Add(ix2 == E[j] * word_len + i) model.AddElement(ix1, A_flat, val) model.AddElement(ix2, A_flat, val) # # solution and search # solver = cp.CpSolver() # solver.parameters.search_branching = cp.PORTFOLIO_SEARCH # solver.parameters.cp_model_presolve = False solver.parameters.linearization_level = 0 solver.parameters.cp_model_probing_level = 0 solution_printer = SolutionPrinter(E, words, num_answers) status = solver.SearchForAllSolutions(model, solution_printer) if not (status == cp.OPTIMAL or status == cp.FEASIBLE): print("No solution!") print() print("NumSolutions:", solution_printer.SolutionCount()) print("NumConflicts:", solver.NumConflicts()) print("NumBranches:", solver.NumBranches()) print("WallTime:", solver.WallTime())
def tasks_and_workers_assignment_sat(): """Solve the assignment problem.""" model = cp_model.CpModel() # CP-SAT solver is integer only. task_cost = [24, 10, 7, 2, 11, 16, 1, 13, 9, 27] num_tasks = len(task_cost) num_workers = 3 num_groups = 2 all_workers = range(num_workers) all_groups = range(num_groups) all_tasks = range(num_tasks) # Variables ## x_ij = 1 if worker i is assigned to group j x = {} for i in all_workers: for j in all_groups: x[i, j] = model.NewBoolVar('x[%i,%i]' % (i, j)) ## y_kj is 1 if task k is assigned to group j y = {} for k in all_tasks: for j in all_groups: y[k, j] = model.NewBoolVar('x[%i,%i]' % (k, j)) # Constraints # Each task k is assigned to a group and only one. for k in all_tasks: model.Add(sum(y[k, j] for j in all_groups) == 1) # Each worker i is assigned to a group and only one. for i in all_workers: model.Add(sum(x[i, j] for j in all_groups) == 1) # cost per group sum_of_costs = sum(task_cost) averages = [] num_workers_in_group = [] scaled_sum_of_costs_in_group = [] scaling = 1000 # We introduce scaling to deal with floating point average. for j in all_groups: n = model.NewIntVar(1, num_workers, 'num_workers_in_group_%i' % j) model.Add(n == sum(x[i, j] for i in all_workers)) c = model.NewIntVar(0, sum_of_costs * scaling, 'sum_of_costs_of_group_%i' % j) model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks)) a = model.NewIntVar(0, sum_of_costs * scaling, 'average_cost_of_group_%i' % j) model.AddDivisionEquality(a, c, n) averages.append(a) num_workers_in_group.append(n) scaled_sum_of_costs_in_group.append(c) # All workers are assigned. model.Add(sum(num_workers_in_group) == num_workers) # Objective. obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj') model.AddMaxEquality(obj, averages) model.Minimize(obj) # Solve and print out the solution. solver = cp_model.CpSolver() solver.parameters.max_time_in_seconds = 60 * 60 * 2 objective_printer = ObjectivePrinter() status = solver.SolveWithSolutionCallback(model, objective_printer) print(solver.ResponseStats()) if status == cp_model.OPTIMAL: for j in all_groups: print('Group %i' % j) for i in all_workers: if solver.BooleanValue(x[i, j]): print(' - worker %i' % i) for k in all_tasks: if solver.BooleanValue(y[k, j]): print(' - task %i with cost %i' % (k, task_cost[k])) print(' - sum_of_costs = %i' % (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling)) print(' - average cost = %f' % (solver.Value(averages[j]) * 1.0 / scaling))
def main(): model = cp.CpModel() # # data # num_sets = 7 num_elements = 12 belongs = [ # 1 2 3 4 5 6 7 8 9 0 1 2 elements [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # Set 1 [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], # 2 [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0], # 3 [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0], # 4 [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0], # 5 [1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0], # 6 [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1] # 7 ] # # variables # x = [model.NewIntVar(0, 1, 'x[%i]' % i) for i in range(num_sets)] # number of choosen sets z = model.NewIntVar(0, num_sets * 2, 'z') # total number of elements in the choosen sets tot_elements = model.NewIntVar(0, num_sets * num_elements, "tot_elements") # # constraints # model.Add(z == sum(x)) # all sets must be used for j in range(num_elements): model.Add(sum([belongs[i][j] * x[i] for i in range(num_sets)]) >= 1) # number of used elements model.Add(tot_elements == sum([ x[i] * belongs[i][j] for i in range(num_sets) for j in range(num_elements) ])) # objective model.Minimize(z) # # search and result # solver = cp.CpSolver() status = solver.Solve(model) if status == cp.OPTIMAL: print('z:', solver.Value(z)) print('tot_elements:', solver.Value(tot_elements)) print('x:', [solver.Value(x[i]) for i in range(num_sets)]) print() print('NumConflicts:', solver.NumConflicts()) print('NumBranches:', solver.NumBranches()) print('WallTime:', solver.WallTime())
def main(): # [START data] data = {} data['weights'] = [ 48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36 ] data['values'] = [ 10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25 ] assert len(data['weights']) == len(data['values']) data['num_items'] = len(data['weights']) data['all_items'] = range(data['num_items']) data['bin_capacities'] = [100, 100, 100, 100, 100] data['num_bins'] = len(data['bin_capacities']) data['all_bins'] = range(data['num_bins']) # [END data] # [START model] model = cp_model.CpModel() # [END model] # Variables. # [START variables] # x[i, b] = 1 if item i is packed in bin b. x = {} for i in data['all_items']: for b in data['all_bins']: x[i, b] = model.NewBoolVar(f'x_{i}_{b}') # [END variables] # Constraints. # [START constraints] # Each item is assigned to at most one bin. for i in data['all_items']: model.AddAtMostOne([x[i, b] for b in data['all_bins']]) # The amount packed in each bin cannot exceed its capacity. for b in data['all_bins']: model.Add( sum(x[i, b] * data['weights'][i] for i in data['all_items']) <= data['bin_capacities'][b]) # [END constraints] # Objective. # [START objective] # Maximize total value of packed items. objective = [] for i in data['all_items']: for b in data['all_bins']: objective.append( cp_model.LinearExpr.Term(x[i, b], data['values'][i])) model.Maximize(cp_model.LinearExpr.Sum(objective)) # [END objective] # [START solve] solver = cp_model.CpSolver() status = solver.Solve(model) # [END solve] # [START print_solution] if status == cp_model.OPTIMAL: print(f'Total packed value: {solver.ObjectiveValue()}') total_weight = 0 for b in data['all_bins']: print(f'Bin {b}') bin_weight = 0 bin_value = 0 for i in data['all_items']: if solver.Value(x[i, b]) > 0: print( f"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}" ) bin_weight += data['weights'][i] bin_value += data['values'][i] print(f'Packed bin weight: {bin_weight}') print(f'Packed bin value: {bin_value}\n') total_weight += bin_weight print(f'Total packed weight: {total_weight}') else: print('The problem does not have an optimal solution.')
def main(): # Data. cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48, 101], [125, 95, 90, 105, 59, 120], [45, 110, 95, 115, 104, 83], [60, 105, 80, 75, 59, 62], [45, 65, 110, 95, 47, 31], [38, 51, 107, 41, 69, 99], [47, 85, 57, 71, 92, 77], [39, 63, 97, 49, 118, 56], [47, 101, 71, 60, 88, 109], [17, 39, 103, 64, 61, 92], [101, 45, 83, 59, 92, 27]] group1 = [ [0, 0, 1, 1], # Workers 2, 3 [0, 1, 0, 1], # Workers 1, 3 [0, 1, 1, 0], # Workers 1, 2 [1, 1, 0, 0], # Workers 0, 1 [1, 0, 1, 0] ] # Workers 0, 2 group2 = [ [0, 0, 1, 1], # Workers 6, 7 [0, 1, 0, 1], # Workers 5, 7 [0, 1, 1, 0], # Workers 5, 6 [1, 1, 0, 0], # Workers 4, 5 [1, 0, 0, 1] ] # Workers 4, 7 group3 = [ [0, 0, 1, 1], # Workers 10, 11 [0, 1, 0, 1], # Workers 9, 11 [0, 1, 1, 0], # Workers 9, 10 [1, 0, 1, 0], # Workers 8, 10 [1, 0, 0, 1] ] # Workers 8, 11 sizes = [10, 7, 3, 12, 15, 4, 11, 5] total_size_max = 15 num_workers = len(cost) num_tasks = len(cost[1]) all_workers = range(num_workers) all_tasks = range(num_tasks) # Model. model = cp_model.CpModel() # Variables total_cost = model.NewIntVar(0, 1000, 'total_cost') x = [[model.NewBoolVar('x[%i,%i]' % (i, j)) for j in all_tasks] for i in all_workers] works = [model.NewBoolVar('works[%i]' % i) for i in all_workers] # Constraints # Link x and workers. for i in range(num_workers): model.AddMaxEquality(works[i], x[i]) # Each task is assigned to at least one worker. for j in all_tasks: model.Add(sum(x[i][j] for i in all_workers) >= 1) # Total task size for each worker is at most total_size_max for i in all_workers: model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max) # Group constraints. model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1) model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2) model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3) # Total cost model.Add(total_cost == sum( x[i][j] * cost[i][j] for j in all_tasks for i in all_workers)) model.Minimize(total_cost) # Solve and output solution. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: print('Total cost = %i' % solver.ObjectiveValue()) print() for i in all_workers: for j in all_tasks: if solver.Value(x[i][j]) == 1: print('Worker ', i, ' assigned to task ', j, ' Cost = ', cost[i][j]) print() print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f ms' % solver.WallTime())
def main(): # Instantiate a cp model. cost = [[90, 76, 75, 70, 50, 74, 12, 68], [35, 85, 55, 65, 48, 101, 70, 83], [125, 95, 90, 105, 59, 120, 36, 73], [45, 110, 95, 115, 104, 83, 37, 71], [60, 105, 80, 75, 59, 62, 93, 88], [45, 65, 110, 95, 47, 31, 81, 34], [38, 51, 107, 41, 69, 99, 115, 48], [47, 85, 57, 71, 92, 77, 109, 36], [39, 63, 97, 49, 118, 56, 92, 61], [47, 101, 71, 60, 88, 109, 52, 90]] sizes = [10, 7, 3, 12, 15, 4, 11, 5] total_size_max = 15 num_workers = len(cost) num_tasks = len(cost[1]) all_workers = range(num_workers) all_tasks = range(num_tasks) model = cp_model.CpModel() # Variables total_cost = model.NewIntVar(0, 1000, 'total_cost') x = [] for i in all_workers: t = [] for j in all_tasks: t.append(model.NewBoolVar('x[%i,%i]' % (i, j))) x.append(t) # Constraints # Each task is assigned to at least one worker. [model.Add(sum(x[i][j] for i in all_workers) >= 1) for j in all_tasks] # Total task size for each worker is at most total_size_max for i in all_workers: model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max) # Total cost model.Add(total_cost == sum(x[i][j] * cost[i][j] for j in all_tasks for i in all_workers)) model.Minimize(total_cost) solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: print('Total cost = %i' % solver.ObjectiveValue()) print() for i in all_workers: for j in all_tasks: if solver.Value(x[i][j]) == 1: print('Worker ', i, ' assigned to task ', j, ' Cost = ', cost[i][j]) print() print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def solve_cutting_stock_with_arc_flow_and_sat(output_proto): """Solve the cutting stock with arc-flow and the CP-SAT solver.""" items = regroup_and_count(DESIRED_LENGTHS) print('Items:', items) num_items = len(DESIRED_LENGTHS) max_capacity = max(POSSIBLE_CAPACITIES) states, transitions = create_state_graph(items, max_capacity) print('Dynamic programming has generated', len(states), 'states and', len(transitions), 'transitions') incoming_vars = collections.defaultdict(list) outgoing_vars = collections.defaultdict(list) incoming_sink_vars = [] item_vars = collections.defaultdict(list) item_coeffs = collections.defaultdict(list) transition_vars = [] model = cp_model.CpModel() objective_vars = [] objective_coeffs = [] for outgoing, incoming, item_index, card in transitions: count = items[item_index][1] max_count = count // card count_var = model.NewIntVar( 0, max_count, 'i%i_f%i_t%i_C%s' % (item_index, incoming, outgoing, card)) incoming_vars[incoming].append(count_var) outgoing_vars[outgoing].append(count_var) item_vars[item_index].append(count_var) item_coeffs[item_index].append(card) transition_vars.append(count_var) for state_index, state in enumerate(states): if state_index == 0: continue exit_var = model.NewIntVar(0, num_items, 'e%i' % state_index) outgoing_vars[state_index].append(exit_var) incoming_sink_vars.append(exit_var) price = price_usage(state, POSSIBLE_CAPACITIES) objective_vars.append(exit_var) objective_coeffs.append(price) # Flow conservation for state_index in range(1, len(states)): model.Add( sum(incoming_vars[state_index]) == sum(outgoing_vars[state_index])) # Flow going out of the source must go in the sink model.Add(sum(outgoing_vars[0]) == sum(incoming_sink_vars)) # Items must be placed for item_index, size_and_count in enumerate(items): num_arcs = len(item_vars[item_index]) model.Add( sum(item_vars[item_index][i] * item_coeffs[item_index][i] for i in range(num_arcs)) == size_and_count[1]) # Objective is the sum of waste model.Minimize( sum(objective_vars[i] * objective_coeffs[i] for i in range(len(objective_vars)))) # Output model proto to file. if output_proto: output_file = open(output_proto, 'w') output_file.write(str(model.Proto())) output_file.close() # Solve model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = True solver.parameters.num_search_workers = 8 status = solver.Solve(model) print(solver.ResponseStats())
def solve_hidato(puzzle, index): """Solve the given hidato table.""" # Create the model. model = cp_model.CpModel() r = len(puzzle) c = len(puzzle[0]) if not visualization.RunFromIPython(): print('') print('----- Solving problem %i -----' % index) print('') print(('Initial game (%i x %i)' % (r, c))) print_matrix(puzzle) # # declare variables # positions = [ model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c) ] # # constraints # model.AddAllDifferent(positions) # # Fill in the clues # for i in range(r): for j in range(c): if puzzle[i][j] > 0: model.Add(positions[puzzle[i][j] - 1] == i * c + j) # Consecutive numbers much touch each other in the grid. # We use an allowed assignment constraint to model it. close_tuples = build_pairs(r, c) for k in range(0, r * c - 1): model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples) # # solution and search # solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.FEASIBLE: if visualization.RunFromIPython(): output = visualization.SvgWrapper(10, r, 40.0) for i, var in enumerate(positions): val = solver.Value(var) x = val % c y = val // c color = 'white' if puzzle[y][x] == 0 else 'lightgreen' output.AddRectangle(x, r - y - 1, 1, 1, color, 'black', str(i + 1)) output.AddTitle('Puzzle %i solved in %f s' % (index, solver.WallTime())) output.Display() else: print_solution( [solver.Value(x) for x in positions], r, c, ) print('Statistics') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime())
def main(): model = cp.CpModel() # # data # n = 7 # # variables # X = [model.NewIntVar(0, 10, "X(%i)" % i) for i in range(n)] X0, X1, X2, X3, X4, X5, X6 = X # # constraints # model.Add(-76706 * X0 + 98205 * X1 + 23445 * X2 + 67921 * X3 + 24111 * X4 + -48614 * X5 + -41906 * X6 == 821228) model.Add(87059 * X0 + -29101 * X1 + -5513 * X2 + -21219 * X3 + 22128 * X4 + 7276 * X5 + 57308 * X6 == 22167) model.Add(-60113 * X0 + 29475 * X1 + 34421 * X2 + -76870 * X3 + 62646 * X4 + 29278 * X5 + -15212 * X6 == 251591) model.Add(49149 * X0 + 52871 * X1 + -7132 * X2 + 56728 * X3 + -33576 * X4 + -49530 * X5 + -62089 * X6 == 146074) model.Add(-10343 * X0 + 87758 * X1 + -11782 * X2 + 19346 * X3 + 70072 * X4 + -36991 * X5 + 44529 * X6 == 740061) model.Add(85176 * X0 + -95332 * X1 + -1268 * X2 + 57898 * X3 + 15883 * X4 + 50547 * X5 + 83287 * X6 == 373854) model.Add(-85698 * X0 + 29958 * X1 + 57308 * X2 + 48789 * X3 + -78219 * X4 + 4657 * X5 + 34539 * X6 == 249912) model.Add(-67456 * X0 + 84750 * X1 + -51553 * X2 + 21239 * X3 + 81675 * X4 + -99395 * X5 + -4254 * X6 == 277271) model.Add(94016 * X0 + -82071 * X1 + 35961 * X2 + 66597 * X3 + -30705 * X4 + -44404 * X5 + -38304 * X6 == 25334) model.Add(-60301 * X0 + 31227 * X1 + 93951 * X2 + 73889 * X3 + 81526 * X4 + -72702 * X5 + 68026 * X6 == 1410723) model.Add(-16835 * X0 + 47385 * X1 + 97715 * X2 + -12640 * X3 + 69028 * X4 + 76212 * X5 + -81102 * X6 == 1244857) model.Add(-43277 * X0 + 43525 * X1 + 92298 * X2 + 58630 * X3 + 92590 * X4 + -9372 * X5 + -60227 * X6 == 1503588) model.Add(-64919 * X0 + 80460 * X1 + 90840 * X2 + -59624 * X3 + -75542 * X4 + 25145 * X5 + -47935 * X6 == 18465) model.Add(-45086 * X0 + 51830 * X1 + -4578 * X2 + 96120 * X3 + 21231 * X4 + 97919 * X5 + 65651 * X6 == 1198280) model.Add(85268 * X0 + 54180 * X1 + -18810 * X2 + -48219 * X3 + 6013 * X4 + 78169 * X5 + -79785 * X6 == 90614) model.Add(8874 * X0 + -58412 * X1 + 73947 * X2 + 17147 * X3 + 62335 * X4 + 16005 * X5 + 8632 * X6 == 752447) model.Add(71202 * X0 + -11119 * X1 + 73017 * X2 + -38875 * X3 + -14413 * X4 + -29234 * X5 + 72370 * X6 == 129768) model.Add(1671 * X0 + -34121 * X1 + 10763 * X2 + 80609 * X3 + 42532 * X4 + 93520 * X5 + -33488 * X6 == 915683) model.Add(51637 * X0 + 67761 * X1 + 95951 * X2 + 3834 * X3 + -96722 * X4 + 59190 * X5 + 15280 * X6 == 533909) model.Add(-16105 * X0 + 62397 * X1 + -6704 * X2 + 43340 * X3 + 95100 * X4 + -68610 * X5 + 58301 * X6 == 876370) # # search and result # solver = cp.CpSolver() status = solver.Solve(model) if status == cp.OPTIMAL: print("X:", [solver.Value(X[i]) for i in range(n)]) print() print() print("NumConflicts:", solver.NumConflicts()) print("NumBranches:", solver.NumBranches()) print("WallTime:", solver.WallTime())
def dispatch_jobs_to_slots(self, worker_slots={}, jobs=[]): """Assign jobs to worker_slots.""" num_slots = len(worker_slots) num_jobs = len(jobs) # All durations are in minutes. # max_onsite_time = 10*60 # 660 # 108 # 8 hours. / 5 *num_slots This is per worker. # max_shift_time = 4*60 # 60*10 min_lunch_break_minutes = 30 min_delay_between_jobs = 2 # 2 # extra_time = 5 # min_working_time = 390 # 6.5 hours # worker_start_time = 1+ 0*60 # 8:00 # worker_end_time = 24*60 # total_worker_minutes = ( worker_end_time - worker_start_time ) * num_slots MAX_TRAVEL_MINUTE = 600 # 2 * 24 * 60 # 24 hours in one day. # MIN_START_MINUTE = min([slot.start_minutes for slot in worker_slots]) # MAX_START_MINUTE = max([slot.end_minutes for slot in worker_slots]) # START_TIME_BASE = MIN_START_MINUTE MIN_START_MINUTE = 0 MAX_START_MINUTE = 400 # MAX_START_MINUTE - START_TIME_BASE # Computed statistics. all_jobs_total_working_time = sum( [jobs[j].requested_duration_minutes for j in range(1, num_jobs)]) random_total_travel_time = sum([ self._get_travel_time_2locations(jobs[shift - 1].location, jobs[shift].location) for shift in range(1, num_jobs) ]) print("num worker_slots: ", num_slots, ", num_jobs: ", num_jobs) # print('worker_start_time: ', worker_start_time ,'worker_end_time: ', worker_end_time ) print("all_jobs_total_working_time: ", all_jobs_total_working_time) print("random_total_travel_time: ", random_total_travel_time) # print("total worker_slots time:", sum([1])) # We are going to build a flow from a the start of the day to the end # of the day. # # Along the path, we will accumulate travel time model = cp_model.CpModel() # Per node info incoming_literals = collections.defaultdict(list) outgoing_literals = collections.defaultdict(list) outgoing_other_job_index = [] # emp_job_literals [job] = [worker_n_lit, ....] workers_assigned2_job_literals = collections.defaultdict(list) travel_time_per_emp_sum_dict = {} # TODO # incoming_sink_literals = [] # outgoing_source_literals = [] # new_start_time = [] # Create all the shift variables before iterating on the transitions # between these shifts. total_travel_until_emp = {} shift_start_time_dict = {} travel_time_until_job = {} source_lit_dict = {} sink_lit_dict = {} total_travel_until_emp[-1] = model.NewIntVar( 0, 0, "total_travel until emp {}".format("init")) for ei in range(num_slots): source_lit_dict[ei] = {} sink_lit_dict[ei] = {} travel_time_per_emp_sum_dict[ei] = model.NewIntVar( 0, MAX_TRAVEL_MINUTE, "total_travel time for emp {}".format(ei)) total_travel_until_emp[ei] = model.NewIntVar( 0, MAX_TRAVEL_MINUTE * (ei + 1), "total_travel for emp {}".format(ei)) # To chain and accumulate all travel times for each employee model.Add( total_travel_until_emp[ei] == total_travel_until_emp[ei - 1] + travel_time_per_emp_sum_dict[ei]) # total traval (travel_time_final_total) is the last one travel_time_final_total = model.NewIntVar( 0, num_slots * int(MAX_TRAVEL_MINUTE * 1), "total_travel for - {}".format("all")) model.Add(travel_time_final_total == total_travel_until_emp[num_slots - 1]) # Not sure why sum() does not work!!! sum(travel_time_per_emp_sum_dict) """ model.Add(travel_time_final_total == travel_time_per_emp_sum_dict[0] + travel_time_per_emp_sum_dict[1] \ + travel_time_per_emp_sum_dict[2] + travel_time_per_emp_sum_dict[3] \ + travel_time_per_emp_sum_dict[4] + travel_time_per_emp_sum_dict[5] \ ) """ # other_start_time_dict = {} for shift in range(num_jobs): # if jobs[shift].job_schedule_type != JobScheduleType.NORMAL: shift_start_time_dict[shift] = model.NewIntVar( jobs[shift].requested_start_min_minutes, jobs[shift].requested_start_max_minutes, "start_time_shift_%i" % shift, ) else: shift_start_time_dict[shift] = model.NewIntVar( MIN_START_MINUTE, MAX_START_MINUTE, "start_time_shift_%i" % shift) # if jobs[shift]['job_schedule_type'] == 'FS': # model.Add( shift_start_time_dict[shift] == jobs[shift]['requested_start_minutes'] ) travel_time_until_job[shift] = model.NewIntVar( 1, MAX_TRAVEL_MINUTE, "travel_time_until_shift_%i" % shift) outgoing_other_job_index.append([]) for ei in range(num_slots): source_lit_dict[ei][shift] = model.NewBoolVar( "from emp {} source to job {}".format(ei, shift)) # Arc from source worker to this job incoming_literals[shift].append(source_lit_dict[ei][shift]) # If this job[shift] is first job for worker[ei], travel time on this job is from home to job. model.Add(travel_time_until_job[shift] == self._get_travel_time_2locations( worker_slots[ei].start_location, jobs[shift].location)).OnlyEnforceIf( source_lit_dict[ei][shift]) sink_lit_dict[ei][shift] = model.NewBoolVar( "from job {} sink to emp {} ".format(shift, ei)) # Arc from job to sinking_worker. outgoing_literals[shift].append(sink_lit_dict[ei][shift]) outgoing_other_job_index[shift].append("worker_{}".format(ei)) # If this job[shift] is the last job for worker[ei], travel_time_per_emp_sum_dict (total) is the travel time on this job is from job to home. model.Add( travel_time_per_emp_sum_dict[ei] == travel_time_until_job[shift] + self._get_travel_time_2locations( worker_slots[ei].end_location, jobs[shift].location) ).OnlyEnforceIf(sink_lit_dict[ei][shift]) model.Add(shift_start_time_dict[shift] <= int( worker_slots[ei].end_minutes - worker_slots[ei].start_minutes - # Monday jobs[shift].requested_duration_minutes + self._get_travel_time_2locations( worker_slots[ei].end_location, jobs[shift].location)) ).OnlyEnforceIf(sink_lit_dict[ei][shift]) emp_on_job_lit = model.NewBoolVar( "path Emp {} on job {}".format(ei, shift)) # # ordered by worker_index workers_assigned2_job_literals[shift].append(emp_on_job_lit) # # job must obey Start time for worker, if assigned to this worker # # TODO, only 1 day for now, [0] # model.Add( # shift_start_time_dict[shift] >= worker_slots[ei].start_minutes # ).OnlyEnforceIf(emp_on_job_lit) # # # # job must obey end time for worker, if assigned to this worker # model.Add( # shift_start_time_dict[shift] # <= int( # worker_slots[ei].end_minutes # Monday # - jobs[shift].requested_duration_minutes # ) # ).OnlyEnforceIf(emp_on_job_lit) # If source or sink, it is on this emp. model.Add(emp_on_job_lit == 1).OnlyEnforceIf( sink_lit_dict[ei][shift]) model.Add(emp_on_job_lit == 1).OnlyEnforceIf( source_lit_dict[ei][shift]) if (jobs[shift].job_code in config.DEBUGGING_JOB_CODE_SET ) and (worker_slots[ei].worker_id == "Tom"): log.debug(f"debug {jobs[shift].job_code}") if (worker_slots[ei].worker_id, ) not in jobs[shift].searching_worker_candidates: model.Add(source_lit_dict[ei][shift] == 0) model.Add(sink_lit_dict[ei][shift] == 0) model.Add(emp_on_job_lit == 0) for shift in range(num_jobs): duration = jobs[shift].requested_duration_minutes for ei in range(num_slots): pass # outgoing_source_literals.append(source_lit_dict[ei][shift]) # Arc from source to shift. # incoming_sink_literals.append(sink_lit_dict[ei][shift]) for other in range(num_jobs): if shift == other: continue other_duration = jobs[other].requested_duration_minutes lit = model.NewBoolVar("job path from %i to %i" % (shift, other)) # constraint for start time by duan 2019-10-09 16:58:42 #### + jobs[shift]['requested_duration_minutes'] + min_delay_between_shifts # fmt: off model.Add( shift_start_time_dict[shift] + int(jobs[shift].requested_duration_minutes) + min_delay_between_jobs + self._get_travel_time_2locations( jobs[shift].location, jobs[other].location) < shift_start_time_dict[other]).OnlyEnforceIf(lit) # fmt: on # Increase travel time model.Add(travel_time_until_job[other] == travel_time_until_job[shift] + self._get_travel_time_2locations( jobs[shift].location, jobs[other].location) ).OnlyEnforceIf(lit) # Make sure that for one flow path, all jobs belong to one worker 2020-01-07 12:59:21 for ei in range(num_slots): model.Add(workers_assigned2_job_literals[other][ei] == workers_assigned2_job_literals[shift] [ei]).OnlyEnforceIf(lit) # Add arc outgoing_literals[shift].append(lit) outgoing_other_job_index[shift].append("job_{}".format(other)) incoming_literals[other].append(lit) # Create dag constraint. for shift in range(num_jobs): model.Add(sum(outgoing_literals[shift]) == 1) model.Add(sum(incoming_literals[shift]) == 1) # <= 1 makes it possible to leave some worker_slots idle. for ei in range(num_slots): emp_lits = [] for ii in range(num_jobs): emp_lits.append(source_lit_dict[ei][ii]) model.Add(sum(emp_lits) == 1) for ei in range(num_slots): emp_lits = [] for ii in range(num_jobs): emp_lits.append(sink_lit_dict[ei][ii]) model.Add(sum(emp_lits) == 1) model.Minimize(travel_time_final_total) # Solve model. solver = cp_model.CpSolver() solver.parameters.log_search_progress = self.config[ "log_search_progress"] # solver.parameters.num_search_workers = 4 # https://developers.google.com/optimization/cp/cp_tasks solver.parameters.max_time_in_seconds = self.config[ "max_exec_time"] # two minutes status = solver.Solve(model) if status == cp_model.INFEASIBLE: print("SufficientAssumptionsForInfeasibility = " f"{solver.SufficientAssumptionsForInfeasibility()}") return [] if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE: # print("Status = %s, failed." % solver.StatusName(status)) return [] optimal_travel_time = int(solver.ObjectiveValue()) print("optimal_travel_time =", optimal_travel_time) all_following_tasks = {} emp_following_tasks = {} """ for ei in range(num_slots): print("w_{}_{}: {}, hasta {}".format( ei, worker_slots[ei]['worker_code'], solver.Value( travel_time_per_emp_sum_dict[ei]), solver.Value( total_travel_until_emp[ei]) ) ) for ei in range(num_jobs): print("j_{} : {}, travel {}, start: {}".format( ei, jobs[ei]['requested_duration_minutes'], solver.Value(travel_time_until_job[ei] ) , solver.Value( shift_start_time_dict [ei]) ) ) """ for shift in range(num_jobs): if shift == 7: pass # print("pause") for ii in range(len(outgoing_literals[shift])): if solver.Value(outgoing_literals[shift][ii]) == 1: # print( # "job_", # shift, # f"({jobs[shift].job_code})--> ", # outgoing_other_job_index[shift][ii], # ) # TODO make it a list for multiple worker_slots on same job # 0 means this is a job, 1 is ending at worker all_following_tasks[shift] = outgoing_other_job_index[ shift][ii].split("_") for ei in range(num_slots): if solver.Value(source_lit_dict[ei][shift]) == 1: # print("worker:", ei, f"({worker_slots[ei].worker_id})--> job_", shift) emp_following_tasks[ei] = ["job", shift] # j_file.write('') to_print_json_list = [] for _ in range(num_slots): to_print_json_list.append([]) for ei in emp_following_tasks.keys(): my_next_node = emp_following_tasks[ei] prev_node = my_next_node while my_next_node[0] == "job": shift = int(my_next_node[1]) to_print_json_list[ei].append([ shift, solver.Value(shift_start_time_dict[shift]), self._get_travel_time_2locations( jobs[int(prev_node[1])].location, jobs[shift].location), jobs[shift].requested_duration_minutes, ]) prev_node = my_next_node my_next_node = all_following_tasks[shift] to_print_json_list[ei].append([ -1, -2, self._get_travel_time_2locations( worker_slots[ei].start_location, jobs[shift].location), -4, ]) """ print(to_print_json_list) """ for ei in range(num_slots): print( "worker:", ei, f"({worker_slots[ei].worker_id}) --> ", [( to_print_json_list[ei][si][0], f"({jobs[ to_print_json_list[ei][si][0] ].job_code})", to_print_json_list[ei][si][1], ) for si in range(len(to_print_json_list[ei]) - 1)], ) return to_print_json_list
from ortools.sat.python import cp_model budget = 10000 numb_worker = 1200 area = 110 model = cp_model.CpModel() # Define variables corn = model.NewIntVar(0, area + 1, 'X') barley = model.NewIntVar(0, area + 1, 'Y') # Define constraints model.Add(100 * corn + 200 * barley <= budget) model.Add(10 * corn + 30 * barley <= numb_worker) model.Add(corn + barley <= area) # objective function model.Maximize(50 * corn + 120 * barley) # Solve solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: print('Maximum of objective function: %i' % solver.ObjectiveValue()) print() print('corn value : ', solver.Value(corn)) print('barley value: ', solver.Value(barley))
def __init__(self): self.model = cp_model.CpModel() self.solver = cp_model.CpSolver() self.grid_size = 9 self.cell_size = 3
def solve_zebra(): """Solves the zebra problem.""" # Create the model. model = cp_model.CpModel() red = model.NewIntVar(1, 5, 'red') green = model.NewIntVar(1, 5, 'green') yellow = model.NewIntVar(1, 5, 'yellow') blue = model.NewIntVar(1, 5, 'blue') ivory = model.NewIntVar(1, 5, 'ivory') englishman = model.NewIntVar(1, 5, 'englishman') spaniard = model.NewIntVar(1, 5, 'spaniard') japanese = model.NewIntVar(1, 5, 'japanese') ukrainian = model.NewIntVar(1, 5, 'ukrainian') norwegian = model.NewIntVar(1, 5, 'norwegian') dog = model.NewIntVar(1, 5, 'dog') snails = model.NewIntVar(1, 5, 'snails') fox = model.NewIntVar(1, 5, 'fox') zebra = model.NewIntVar(1, 5, 'zebra') horse = model.NewIntVar(1, 5, 'horse') tea = model.NewIntVar(1, 5, 'tea') coffee = model.NewIntVar(1, 5, 'coffee') water = model.NewIntVar(1, 5, 'water') milk = model.NewIntVar(1, 5, 'milk') fruit_juice = model.NewIntVar(1, 5, 'fruit juice') old_gold = model.NewIntVar(1, 5, 'old gold') kools = model.NewIntVar(1, 5, 'kools') chesterfields = model.NewIntVar(1, 5, 'chesterfields') lucky_strike = model.NewIntVar(1, 5, 'lucky strike') parliaments = model.NewIntVar(1, 5, 'parliaments') model.AddAllDifferent([red, green, yellow, blue, ivory]) model.AddAllDifferent( [englishman, spaniard, japanese, ukrainian, norwegian]) model.AddAllDifferent([dog, snails, fox, zebra, horse]) model.AddAllDifferent([tea, coffee, water, milk, fruit_juice]) model.AddAllDifferent( [parliaments, kools, chesterfields, lucky_strike, old_gold]) model.Add(englishman == red) model.Add(spaniard == dog) model.Add(coffee == green) model.Add(ukrainian == tea) model.Add(green == ivory + 1) model.Add(old_gold == snails) model.Add(kools == yellow) model.Add(milk == 3) model.Add(norwegian == 1) diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields') model.Add(diff_fox_chesterfields == fox - chesterfields) model.AddAbsEquality(1, diff_fox_chesterfields) diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools') model.Add(diff_horse_kools == horse - kools) model.AddAbsEquality(1, diff_horse_kools) model.Add(lucky_strike == fruit_juice) model.Add(japanese == parliaments) diff_norwegian_blue = model.NewIntVar(-4, 4, 'diff_norwegian_blue') model.Add(diff_norwegian_blue == norwegian - blue) model.AddAbsEquality(1, diff_norwegian_blue) # Solve and print out the solution. solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL: people = [englishman, spaniard, japanese, ukrainian, norwegian] water_drinker = [ p for p in people if solver.Value(p) == solver.Value(water) ][0] zebra_owner = [ p for p in people if solver.Value(p) == solver.Value(zebra) ][0] print('The', water_drinker.Name(), 'drinks water.') print('The', zebra_owner.Name(), 'owns the zebra.') else: print('No solutions to the zebra problem, this is unusual!')
def solve_shift_scheduling(params, output_proto, num_employees, num_weeks, shifts, fixed_assignments, requests, weekly_cover_demands): """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, 7, 7, 0), # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are # possible but penalized. #(2, 1, 2, 20, 2, 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, 7, 7, 0), # At least 1 night shift per week (penalized). At most 4 (hard). #(2, 0, 0, 2, 4, 4, 0), ] # Penalized transitions: # (previous_shift, next_shift, penalty (0 means forbidden)) penalized_transitions = [ # Afternoon to night has a penalty of 4. #(1, 2, 4), # Night to morning is forbidden. #(2, 1, 0), ] # daily demands for work shifts (morning, afternon, night) for each day # of the week starting on Monday. '''weekly_cover_demands = [ [0],[1],[0],[0],[0],[0],[0] ]''' # Penalty for exceeding the cover constraint per shift type. excess_cover_penalties = tuple([1000]*(len(shifts)-1)) 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. employee_dict = {} 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 = '' schedulelist=[] for d in range(num_days): for s in range(num_shifts): if solver.BooleanValue(work[e, s, d]): schedule += shifts[s] + ' ' schedulelist.append(shifts[s]) print('worker %i: %s' % (e, schedule)) employee_dict[e] = schedulelist 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()) return(employee_dict)
def channeling_example(): # Create the model. # the_model is an object created by cp_model method from CpModel class the_model = cp_model.CpModel() # 'Channeling Example') # # data # n = 3 # a constant ... not var # # Creating CP variables # vetor_BOOL = [the_model.NewBoolVar('vetor_BOOL[%i]' % i) for i in range(n)] # bool_var_array # Other vars in the_model created by the method NewIntVar x = the_model.NewIntVar(0, 10, 'x') y = the_model.NewIntVar(0, 10, 'y') z = the_model.NewIntVar(0, 10, 'z') # NewIntVar(self, lb, ub, name) # # constraints # ### # x >= 5 and x < 10 then write as: the_model.Add(x >= 5) the_model.Add(x < 10) ###the_model.Add(y >= 8 and y <= 10) .... NOT WORKS the_model.Add(y > 8) . OnlyEnforceIf ( vetor_BOOL[1] ) the_model.Add( y <= 10 ) . OnlyEnforceIf (vetor_BOOL[1].Not() ) the_model.Add(z > 7) the_model.Add((vetor_BOOL[0] and vetor_BOOL[1] and vetor_BOOL[2]) == True) #### NOT WORKS the_model.Add(sum(vetor_BOOL) > 1) # # search and result # Search for x values in increasing order. the_model.AddDecisionStrategy(vetor_BOOL, cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE) ## Very interesting Decision Strategy # # Create a solver and solve with a fixed search. solver = cp_model.CpSolver() #solver.parameters.max_time_in_seconds = 10.0 status = solver.Solve(the_model) ### #values_vetor_BOOL = [solver.Value(vetor_BOOL[i]) for i in range(n)] ### VERY BIZARRE if (status == cp_model.FEASIBLE): print_output_VARS(solver.Value(x), solver.Value(y), solver.Value(z), [solver.Value(vetor_BOOL[i]) for i in range(n)]) else: print ("NOT OK: ", solver.StatusName(status)) return #print('vetor_BOOL:', [solver.Value(vetor_BOOL[i]) for i in range(n)]) print('\n\n** Final Statistics **') print(' - conflicts : %i' % solver.NumConflicts()) print(' - branches : %i' % solver.NumBranches()) print(' - wall time : %f s' % solver.WallTime()) print("\n END SOLVER ===================================")
def main(): model = cp.CpModel() n = 4 perms = ["+2", "/8", "-3", "*7"] # ages 16..120 age_low = 16 age_high = 120 # variables m = model.NewIntVar(age_low, age_high, "m") # my age h = model.NewIntVar(age_low, age_high, "h") # my age perm1 = [model.NewIntVar(0, n - 1, "perm1") for i in range(n)] perm2 = [model.NewIntVar(0, n - 1, "perm2") for i in range(n)] # for calculating my age mlist = [model.NewIntVar(0, 1000, "perm2") for i in range(n + 1)] # for calculating husbands age hlist = [model.NewIntVar(0, 1000, "perm2") for i in range(n + 1)] # constraints model.AddAllDifferent(perm1) model.AddAllDifferent(perm2) # same operations in different order pb = [model.NewBoolVar("pb") for i in range(n)] for i in range(n): model.Add(perm1[i] != perm2[i]).OnlyEnforceIf(pb[i]) model.Add(perm1[i] == perm2[i]).OnlyEnforceIf(pb[i].Not()) model.Add(sum(pb) > 0) # find husbands age, start with my age model.Add(hlist[0] == m) # husband's age is last in hlist model.Add(h == hlist[n]) # checking my age, start with husband's age model.Add(mlist[0] == h) # my age is last in mlist model.Add(m == mlist[n]) # check the operations for i in range(n): check(model, perm1[i], hlist[i], hlist[i + 1]) check(model, perm2[i], mlist[i], mlist[i + 1]) # Symmetry breaking: I'm younger than husband # model.Add(m < h) solver = cp.CpSolver() solution_printer = SolutionPrinter(n, perms, m, h, hlist, mlist, perm1, perm2) status = solver.SearchForAllSolutions(model, solution_printer) if not status in [cp.OPTIMAL, cp.FEASIBLE]: print("No solution!") print() print("NumSolutions:", solution_printer.SolutionCount()) print("NumConflicts:", solver.NumConflicts()) print("NumBranches:", solver.NumBranches()) print("WallTime:", solver.WallTime()) print()