update notebooks

This commit is contained in:
Mizux Seiha
2024-04-30 21:21:29 +02:00
committed by Corentin Le Molgat
parent df5c9411af
commit e3c7c30b89
91 changed files with 2666 additions and 1583 deletions

View File

@@ -380,7 +380,7 @@
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f' {manager.IndexToNode(index)} '\n",
" f'Load({assignment.Value(load_var)}) '\n",
" f'Load({assignment.Min(load_var)}) '\n",
" f'Time({assignment.Min(time_var)},{assignment.Max(time_var)}) ->'\n",
" )\n",
" previous_index = index\n",
@@ -391,15 +391,15 @@
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f' {manager.IndexToNode(index)} '\n",
" f'Load({assignment.Value(load_var)}) '\n",
" f'Load({assignment.Min(load_var)}) '\n",
" f'Time({assignment.Min(time_var)},{assignment.Max(time_var)})\\n')\n",
" plan_output += f'Distance of the route: {distance}m\\n'\n",
" plan_output += f'Load of the route: {assignment.Value(load_var)}\\n'\n",
" plan_output += f'Time of the route: {assignment.Value(time_var)}min\\n'\n",
" plan_output += f'Load of the route: {assignment.Min(load_var)}\\n'\n",
" plan_output += f'Time of the route: {assignment.Min(time_var)}min\\n'\n",
" print(plan_output)\n",
" total_distance += distance\n",
" total_load += assignment.Value(load_var)\n",
" total_time += assignment.Value(time_var)\n",
" total_load += assignment.Min(load_var)\n",
" total_time += assignment.Min(time_var)\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f'Total Load of all routes: {total_load}')\n",
" print(f'Total Time of all routes: {total_time}min')\n",

View File

@@ -91,6 +91,8 @@
"metadata": {},
"outputs": [],
"source": [
"import weakref\n",
"\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"\n",
@@ -159,20 +161,21 @@
" model: pywrapcp.RoutingModel,\n",
" limit: int,\n",
" ):\n",
" self._routing_manager = manager\n",
" self._routing_model = model\n",
" # We need a weak ref on the routing model to avoid a cycle.\n",
" self._routing_manager_ref = weakref.ref(manager)\n",
" self._routing_model_ref = weakref.ref(model)\n",
" self._counter = 0\n",
" self._counter_limit = limit\n",
" self.objectives = []\n",
"\n",
" def __call__(self):\n",
" objective = int(self._routing_model.CostVar().Value())\n",
" objective = int(self._routing_model_ref().CostVar().Value())\n",
" if not self.objectives or objective < self.objectives[-1]:\n",
" self.objectives.append(objective)\n",
" print_solution(self._routing_manager, self._routing_model)\n",
" print_solution(self._routing_manager_ref(), self._routing_model_ref())\n",
" self._counter += 1\n",
" if self._counter > self._counter_limit:\n",
" self._routing_model.solver().FinishCurrentSearch()\n",
" self._routing_model_ref().solver().FinishCurrentSearch()\n",
"\n",
"\n",
"\n",

View File

@@ -108,17 +108,19 @@
" self.__variables = variables\n",
" self.__collect = []\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" \"\"\"Collect a new combination.\"\"\"\n",
" combination = [self.Value(v) for v in self.__variables]\n",
" combination = [self.value(v) for v in self.__variables]\n",
" self.__collect.append(combination)\n",
"\n",
" def combinations(self):\n",
" def combinations(self) -> list[list[int]]:\n",
" \"\"\"Returns all collected combinations.\"\"\"\n",
" return self.__collect\n",
"\n",
"\n",
"def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max):\n",
"def enumerate_all_knapsacks_with_repetition(\n",
" item_sizes: list[int], total_size_min: int, total_size_max: int\n",
") -> list[list[int]]:\n",
" \"\"\"Enumerate all possible knapsacks with total size in the given range.\n",
"\n",
" Args:\n",
@@ -132,22 +134,26 @@
" nonnegative integer: the number of times we put item #K in the knapsack.\n",
" \"\"\"\n",
" model = cp_model.CpModel()\n",
" variables = [model.NewIntVar(0, total_size_max // size, \"\") for size in item_sizes]\n",
" variables = [\n",
" model.new_int_var(0, total_size_max // size, \"\") for size in item_sizes\n",
" ]\n",
" load = sum(variables[i] * size for i, size in enumerate(item_sizes))\n",
" model.AddLinearConstraint(load, total_size_min, total_size_max)\n",
" model.add_linear_constraint(load, total_size_min, total_size_max)\n",
"\n",
" solver = cp_model.CpSolver()\n",
" solution_collector = AllSolutionCollector(variables)\n",
" # Enumerate all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" # Solve\n",
" solver.Solve(model, solution_collector)\n",
" # solve\n",
" solver.solve(model, solution_collector)\n",
" return solution_collector.combinations()\n",
"\n",
"\n",
"def AggregateItemCollectionsOptimally(\n",
" item_collections, max_num_collections, ideal_item_ratios\n",
"):\n",
"def aggregate_item_collections_optimally(\n",
" item_collections: list[list[int]],\n",
" max_num_collections: int,\n",
" ideal_item_ratios: list[float],\n",
") -> list[int]:\n",
" \"\"\"Selects a set (with repetition) of combination of items optimally.\n",
"\n",
" Given a set of collections of N possible items (in each collection, an item\n",
@@ -237,7 +243,9 @@
" return []\n",
"\n",
"\n",
"def GetOptimalSchedule(demand):\n",
"def get_optimal_schedule(\n",
" demand: list[tuple[float, str, int]]\n",
") -> list[tuple[int, list[tuple[int, str]]]]:\n",
" \"\"\"Computes the optimal schedule for the installation input.\n",
"\n",
" Args:\n",
@@ -249,8 +257,10 @@
" Returns:\n",
" The same output type as EnumerateAllKnapsacksWithRepetition.\n",
" \"\"\"\n",
" combinations = EnumerateAllKnapsacksWithRepetition(\n",
" [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value\n",
" combinations = enumerate_all_knapsacks_with_repetition(\n",
" [a[2] + _COMMUTE_TIME.value for a in demand],\n",
" _LOAD_MIN.value,\n",
" _LOAD_MAX.value,\n",
" )\n",
" print(\n",
" (\n",
@@ -259,18 +269,18 @@
" )\n",
" )\n",
"\n",
" selection = AggregateItemCollectionsOptimally(\n",
" selection = aggregate_item_collections_optimally(\n",
" combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand]\n",
" )\n",
" output = []\n",
" for i in range(len(selection)):\n",
" if selection[i] != 0:\n",
" for i, s in enumerate(selection):\n",
" if s != 0:\n",
" output.append(\n",
" (\n",
" selection[i],\n",
" s,\n",
" [\n",
" (combinations[i][t], demand[t][1])\n",
" for t in range(len(demand))\n",
" (combinations[i][t], d[1])\n",
" for t, d in enumerate(demand)\n",
" if combinations[i][t] != 0\n",
" ],\n",
" )\n",
@@ -291,7 +301,7 @@
" % (_LOAD_MIN.value, _LOAD_MAX.value)\n",
" )\n",
" print(\"%d workers\" % _NUM_WORKERS.value)\n",
" selection = GetOptimalSchedule(demand)\n",
" selection = get_optimal_schedule(demand)\n",
" print()\n",
" installed = 0\n",
" installed_per_type = {}\n",
@@ -315,7 +325,8 @@
" per_type = installed_per_type[name]\n",
" if installed != 0:\n",
" print(\n",
" f\" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned\"\n",
" f\" {per_type} ({per_type * 100.0 / installed}%) installations of\"\n",
" f\" type {name} planned\"\n",
" )\n",
" else:\n",
" print(f\" {per_type} installations of type {name} planned\")\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve an assignment problem with combination constraints on workers.\n"
"solve an assignment problem with combination constraints on workers.\n"
]
},
{
@@ -88,7 +88,7 @@
"\n",
"\n",
"def solve_assignment():\n",
" \"\"\"Solve the assignment problem.\"\"\"\n",
" \"\"\"solve the assignment problem.\"\"\"\n",
" # Data.\n",
" cost = [\n",
" [90, 76, 75, 70, 50, 74],\n",
@@ -141,44 +141,45 @@
" model = cp_model.CpModel()\n",
" # Variables\n",
" selected = [\n",
" [model.NewBoolVar(\"x[%i,%i]\" % (i, j)) for j in all_tasks] for i in all_workers\n",
" [model.new_bool_var(\"x[%i,%i]\" % (i, j)) for j in all_tasks]\n",
" for i in all_workers\n",
" ]\n",
" works = [model.NewBoolVar(\"works[%i]\" % i) for i in all_workers]\n",
" works = [model.new_bool_var(\"works[%i]\" % i) for i in all_workers]\n",
"\n",
" # Constraints\n",
"\n",
" # Link selected and workers.\n",
" for i in range(num_workers):\n",
" model.AddMaxEquality(works[i], selected[i])\n",
" model.add_max_equality(works[i], selected[i])\n",
"\n",
" # Each task is assigned to at least one worker.\n",
" for j in all_tasks:\n",
" model.Add(sum(selected[i][j] for i in all_workers) >= 1)\n",
" model.add(sum(selected[i][j] for i in all_workers) >= 1)\n",
"\n",
" # Total task size for each worker is at most total_size_max\n",
" for i in all_workers:\n",
" model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)\n",
" model.add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)\n",
"\n",
" # Group constraints.\n",
" model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1)\n",
" model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2)\n",
" model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3)\n",
" model.add_allowed_assignments([works[0], works[1], works[2], works[3]], group1)\n",
" model.add_allowed_assignments([works[4], works[5], works[6], works[7]], group2)\n",
" model.add_allowed_assignments([works[8], works[9], works[10], works[11]], group3)\n",
"\n",
" # Objective\n",
" model.Minimize(\n",
" model.minimize(\n",
" sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers)\n",
" )\n",
"\n",
" # Solve and output solution.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Total cost = %i\" % solver.ObjectiveValue())\n",
" print(\"Total cost = %i\" % solver.objective_value)\n",
" print()\n",
" for i in all_workers:\n",
" for j in all_tasks:\n",
" if solver.BooleanValue(selected[i][j]):\n",
" if solver.boolean_value(selected[i][j]):\n",
" print(\n",
" \"Worker \", i, \" assigned to task \", j, \" Cost = \", cost[i][j]\n",
" )\n",
@@ -186,9 +187,9 @@
" print()\n",
"\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -109,14 +109,14 @@
" print(\"Solution %i\" % self.__solution_count)\n",
" self.__solution_count += 1\n",
"\n",
" print(\" objective value = %i\" % self.ObjectiveValue())\n",
" print(\" objective value = %i\" % self.objective_value)\n",
" groups = {}\n",
" sums = {}\n",
" for g in self.__all_groups:\n",
" groups[g] = []\n",
" sums[g] = 0\n",
" for item in self.__all_items:\n",
" if self.BooleanValue(self.__item_in_group[(item, g)]):\n",
" if self.boolean_value(self.__item_in_group[(item, g)]):\n",
" groups[g].append(item)\n",
" sums[g] += self.__values[item]\n",
"\n",
@@ -145,7 +145,7 @@
" all_items = range(num_items)\n",
" all_colors = range(num_colors)\n",
"\n",
" # Values for each items.\n",
" # values for each items.\n",
" values = [1 + i + (i * i // 200) for i in all_items]\n",
" # Color for each item (simple modulo).\n",
" colors = [i % num_colors for i in all_items]\n",
@@ -176,26 +176,26 @@
" item_in_group = {}\n",
" for i in all_items:\n",
" for g in all_groups:\n",
" item_in_group[(i, g)] = model.NewBoolVar(\"item %d in group %d\" % (i, g))\n",
" item_in_group[(i, g)] = model.new_bool_var(\"item %d in group %d\" % (i, g))\n",
"\n",
" # Each group must have the same size.\n",
" for g in all_groups:\n",
" model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)\n",
" model.add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)\n",
"\n",
" # One item must belong to exactly one group.\n",
" for i in all_items:\n",
" model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1)\n",
" model.add(sum(item_in_group[(i, g)] for g in all_groups) == 1)\n",
"\n",
" # The deviation of the sum of each items in a group against the average.\n",
" e = model.NewIntVar(0, 550, \"epsilon\")\n",
" e = model.new_int_var(0, 550, \"epsilon\")\n",
"\n",
" # Constrain the sum of values in one group around the average sum per group.\n",
" for g in all_groups:\n",
" model.Add(\n",
" model.add(\n",
" sum(item_in_group[(i, g)] * values[i] for i in all_items)\n",
" <= average_sum_per_group + e\n",
" )\n",
" model.Add(\n",
" model.add(\n",
" sum(item_in_group[(i, g)] * values[i] for i in all_items)\n",
" >= average_sum_per_group - e\n",
" )\n",
@@ -204,24 +204,24 @@
" color_in_group = {}\n",
" for g in all_groups:\n",
" for c in all_colors:\n",
" color_in_group[(c, g)] = model.NewBoolVar(\n",
" color_in_group[(c, g)] = model.new_bool_var(\n",
" \"color %d is in group %d\" % (c, g)\n",
" )\n",
"\n",
" # Item is in a group implies its color is in that group.\n",
" for i in all_items:\n",
" for g in all_groups:\n",
" model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)])\n",
" model.add_implication(item_in_group[(i, g)], color_in_group[(colors[i], g)])\n",
"\n",
" # If a color is in a group, it must contains at least\n",
" # min_items_of_same_color_per_group items from that color.\n",
" for c in all_colors:\n",
" for g in all_groups:\n",
" literal = color_in_group[(c, g)]\n",
" model.Add(\n",
" model.add(\n",
" sum(item_in_group[(i, g)] for i in items_per_color[c])\n",
" >= min_items_of_same_color_per_group\n",
" ).OnlyEnforceIf(literal)\n",
" ).only_enforce_if(literal)\n",
"\n",
" # Compute the maximum number of colors in a group.\n",
" max_color = num_items_per_group // min_items_of_same_color_per_group\n",
@@ -229,10 +229,10 @@
" # Redundant constraint, it helps with solving time.\n",
" if max_color < num_colors:\n",
" for g in all_groups:\n",
" model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)\n",
" model.add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)\n",
"\n",
" # Minimize epsilon\n",
" model.Minimize(e)\n",
" # minimize epsilon\n",
" model.minimize(e)\n",
"\n",
" solver = cp_model.CpSolver()\n",
" # solver.parameters.log_search_progress = True\n",
@@ -240,14 +240,14 @@
" solution_printer = SolutionPrinter(\n",
" values, colors, all_groups, all_items, item_in_group\n",
" )\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Optimal epsilon: %i\" % solver.ObjectiveValue())\n",
" print(\"Optimal epsilon: %i\" % solver.objective_value)\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
" else:\n",
" print(\"No solution found\")\n",
"\n",

View File

@@ -1776,7 +1776,7 @@
"] # yapf:disable\n",
"\n",
"\n",
"def bus_driver_scheduling(minimize_drivers, max_num_drivers):\n",
"def bus_driver_scheduling(minimize_drivers: bool, max_num_drivers: int) -> int:\n",
" \"\"\"Optimize the bus driver scheduling problem.\n",
"\n",
" This model has two modes.\n",
@@ -1874,14 +1874,14 @@
"\n",
" for d in range(num_drivers):\n",
" start_times.append(\n",
" model.NewIntVar(min_start_time - setup_time, max_end_time, \"start_%i\" % d)\n",
" model.new_int_var(min_start_time - setup_time, max_end_time, \"start_%i\" % d)\n",
" )\n",
" end_times.append(\n",
" model.NewIntVar(min_start_time, max_end_time + cleanup_time, \"end_%i\" % d)\n",
" model.new_int_var(min_start_time, max_end_time + cleanup_time, \"end_%i\" % d)\n",
" )\n",
" driving_times.append(model.NewIntVar(0, max_driving_time, \"driving_%i\" % d))\n",
" driving_times.append(model.new_int_var(0, max_driving_time, \"driving_%i\" % d))\n",
" working_times.append(\n",
" model.NewIntVar(0, max_working_time, \"working_times_%i\" % d)\n",
" model.new_int_var(0, max_working_time, \"working_times_%i\" % d)\n",
" )\n",
"\n",
" incoming_literals = collections.defaultdict(list)\n",
@@ -1892,13 +1892,13 @@
" # Create all the shift variables before iterating on the transitions\n",
" # between these shifts.\n",
" for s in range(num_shifts):\n",
" total_driving[d, s] = model.NewIntVar(\n",
" total_driving[d, s] = model.new_int_var(\n",
" 0, max_driving_time, \"dr_%i_%i\" % (d, s)\n",
" )\n",
" no_break_driving[d, s] = model.NewIntVar(\n",
" no_break_driving[d, s] = model.new_int_var(\n",
" 0, max_driving_time_without_pauses, \"mdr_%i_%i\" % (d, s)\n",
" )\n",
" performed[d, s] = model.NewBoolVar(\"performed_%i_%i\" % (d, s))\n",
" performed[d, s] = model.new_bool_var(\"performed_%i_%i\" % (d, s))\n",
"\n",
" for s in range(num_shifts):\n",
" shift = shifts[s]\n",
@@ -1907,42 +1907,44 @@
" # Arc from source to shift.\n",
" # - set the start time of the driver\n",
" # - increase driving time and driving time since break\n",
" source_lit = model.NewBoolVar(\"%i from source to %i\" % (d, s))\n",
" source_lit = model.new_bool_var(\"%i from source to %i\" % (d, s))\n",
" outgoing_source_literals.append(source_lit)\n",
" incoming_literals[s].append(source_lit)\n",
" shared_incoming_literals[s].append(source_lit)\n",
" model.Add(start_times[d] == shift[3] - setup_time).OnlyEnforceIf(source_lit)\n",
" model.Add(total_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n",
" model.Add(no_break_driving[d, s] == duration).OnlyEnforceIf(source_lit)\n",
" model.add(start_times[d] == shift[3] - setup_time).only_enforce_if(\n",
" source_lit\n",
" )\n",
" model.add(total_driving[d, s] == duration).only_enforce_if(source_lit)\n",
" model.add(no_break_driving[d, s] == duration).only_enforce_if(source_lit)\n",
" starting_shifts[d, s] = source_lit\n",
"\n",
" # Arc from shift to sink\n",
" # - set the end time of the driver\n",
" # - set the driving times of the driver\n",
" sink_lit = model.NewBoolVar(\"%i from %i to sink\" % (d, s))\n",
" sink_lit = model.new_bool_var(\"%i from %i to sink\" % (d, s))\n",
" outgoing_literals[s].append(sink_lit)\n",
" shared_outgoing_literals[s].append(sink_lit)\n",
" incoming_sink_literals.append(sink_lit)\n",
" model.Add(end_times[d] == shift[4] + cleanup_time).OnlyEnforceIf(sink_lit)\n",
" model.Add(driving_times[d] == total_driving[d, s]).OnlyEnforceIf(sink_lit)\n",
" model.add(end_times[d] == shift[4] + cleanup_time).only_enforce_if(sink_lit)\n",
" model.add(driving_times[d] == total_driving[d, s]).only_enforce_if(sink_lit)\n",
"\n",
" # Node not performed\n",
" # - set both driving times to 0\n",
" # - add a looping arc on the node\n",
" model.Add(total_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not())\n",
" model.Add(no_break_driving[d, s] == 0).OnlyEnforceIf(performed[d, s].Not())\n",
" incoming_literals[s].append(performed[d, s].Not())\n",
" outgoing_literals[s].append(performed[d, s].Not())\n",
" # Not adding to the shared lists, because, globally, each node will have\n",
" # one incoming literal, and one outgoing literal.\n",
" model.add(total_driving[d, s] == 0).only_enforce_if(~performed[d, s])\n",
" model.add(no_break_driving[d, s] == 0).only_enforce_if(~performed[d, s])\n",
" incoming_literals[s].append(~performed[d, s])\n",
" outgoing_literals[s].append(~performed[d, s])\n",
" # negated adding to the shared lists, because, globally, each node will\n",
" # have one incoming literal, and one outgoing literal.\n",
"\n",
" # Node performed:\n",
" # - add upper bound on start_time\n",
" # - add lower bound on end_times\n",
" model.Add(start_times[d] <= shift[3] - setup_time).OnlyEnforceIf(\n",
" model.add(start_times[d] <= shift[3] - setup_time).only_enforce_if(\n",
" performed[d, s]\n",
" )\n",
" model.Add(end_times[d] >= shift[4] + cleanup_time).OnlyEnforceIf(\n",
" model.add(end_times[d] >= shift[4] + cleanup_time).only_enforce_if(\n",
" performed[d, s]\n",
" )\n",
"\n",
@@ -1951,22 +1953,22 @@
" delay = other[3] - shift[4]\n",
" if delay < min_delay_between_shifts:\n",
" continue\n",
" lit = model.NewBoolVar(\"%i from %i to %i\" % (d, s, o))\n",
" lit = model.new_bool_var(\"%i from %i to %i\" % (d, s, o))\n",
"\n",
" # Increase driving time\n",
" model.Add(\n",
" model.add(\n",
" total_driving[d, o] == total_driving[d, s] + other[5]\n",
" ).OnlyEnforceIf(lit)\n",
" ).only_enforce_if(lit)\n",
"\n",
" # Increase no_break_driving or reset it to 0 depending on the delay\n",
" if delay >= min_pause_after_4h:\n",
" model.Add(no_break_driving[d, o] == other[5]).OnlyEnforceIf(lit)\n",
" model.add(no_break_driving[d, o] == other[5]).only_enforce_if(lit)\n",
" else:\n",
" model.Add(\n",
" model.add(\n",
" no_break_driving[d, o] == no_break_driving[d, s] + other[5]\n",
" ).OnlyEnforceIf(lit)\n",
" ).only_enforce_if(lit)\n",
"\n",
" # Add arc\n",
" # add arc\n",
" outgoing_literals[s].append(lit)\n",
" shared_outgoing_literals[s].append(lit)\n",
" incoming_literals[o].append(lit)\n",
@@ -1976,68 +1978,68 @@
" delay_literals.append(lit)\n",
" delay_weights.append(delay)\n",
"\n",
" model.Add(working_times[d] == end_times[d] - start_times[d])\n",
" model.add(working_times[d] == end_times[d] - start_times[d])\n",
"\n",
" if minimize_drivers:\n",
" # Driver is not working.\n",
" working = model.NewBoolVar(\"working_%i\" % d)\n",
" model.Add(start_times[d] == min_start_time).OnlyEnforceIf(working.Not())\n",
" model.Add(end_times[d] == min_start_time).OnlyEnforceIf(working.Not())\n",
" model.Add(driving_times[d] == 0).OnlyEnforceIf(working.Not())\n",
" working = model.new_bool_var(\"working_%i\" % d)\n",
" model.add(start_times[d] == min_start_time).only_enforce_if(~working)\n",
" model.add(end_times[d] == min_start_time).only_enforce_if(~working)\n",
" model.add(driving_times[d] == 0).only_enforce_if(~working)\n",
" working_drivers.append(working)\n",
" outgoing_source_literals.append(working.Not())\n",
" incoming_sink_literals.append(working.Not())\n",
" outgoing_source_literals.append(~working)\n",
" incoming_sink_literals.append(~working)\n",
" # Conditional working time constraints\n",
" model.Add(working_times[d] >= min_working_time).OnlyEnforceIf(working)\n",
" model.Add(working_times[d] == 0).OnlyEnforceIf(working.Not())\n",
" model.add(working_times[d] >= min_working_time).only_enforce_if(working)\n",
" model.add(working_times[d] == 0).only_enforce_if(~working)\n",
" else:\n",
" # Working time constraints\n",
" model.Add(working_times[d] >= min_working_time)\n",
" model.add(working_times[d] >= min_working_time)\n",
"\n",
" # Create circuit constraint.\n",
" model.AddExactlyOne(outgoing_source_literals)\n",
" model.add_exactly_one(outgoing_source_literals)\n",
" for s in range(num_shifts):\n",
" model.AddExactlyOne(outgoing_literals[s])\n",
" model.AddExactlyOne(incoming_literals[s])\n",
" model.AddExactlyOne(incoming_sink_literals)\n",
" model.add_exactly_one(outgoing_literals[s])\n",
" model.add_exactly_one(incoming_literals[s])\n",
" model.add_exactly_one(incoming_sink_literals)\n",
"\n",
" # Each shift is covered.\n",
" for s in range(num_shifts):\n",
" model.AddExactlyOne(performed[d, s] for d in range(num_drivers))\n",
" model.add_exactly_one(performed[d, s] for d in range(num_drivers))\n",
" # Globally, each node has one incoming and one outgoing literal\n",
" model.AddExactlyOne(shared_incoming_literals[s])\n",
" model.AddExactlyOne(shared_outgoing_literals[s])\n",
" model.add_exactly_one(shared_incoming_literals[s])\n",
" model.add_exactly_one(shared_outgoing_literals[s])\n",
"\n",
" # Symmetry breaking\n",
"\n",
" # The first 3 shifts must be performed by 3 different drivers.\n",
" # Let's assign them to the first 3 drivers in sequence\n",
" model.Add(starting_shifts[0, 0] == 1)\n",
" model.Add(starting_shifts[1, 1] == 1)\n",
" model.Add(starting_shifts[2, 2] == 1)\n",
" model.add(starting_shifts[0, 0] == 1)\n",
" model.add(starting_shifts[1, 1] == 1)\n",
" model.add(starting_shifts[2, 2] == 1)\n",
"\n",
" if minimize_drivers:\n",
" # Push non working drivers to the end\n",
" for d in range(num_drivers - 1):\n",
" model.AddImplication(working_drivers[d].Not(), working_drivers[d + 1].Not())\n",
" model.add_implication(~working_drivers[d], ~working_drivers[d + 1])\n",
"\n",
" # Redundant constraints: sum of driving times = sum of shift driving times\n",
" model.Add(cp_model.LinearExpr.Sum(driving_times) == total_driving_time)\n",
" model.add(cp_model.LinearExpr.sum(driving_times) == total_driving_time)\n",
" if not minimize_drivers:\n",
" model.Add(\n",
" cp_model.LinearExpr.Sum(working_times)\n",
" model.add(\n",
" cp_model.LinearExpr.sum(working_times)\n",
" == total_driving_time\n",
" + num_drivers * (setup_time + cleanup_time)\n",
" + cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights)\n",
" + cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights)\n",
" )\n",
"\n",
" if minimize_drivers:\n",
" # Minimize the number of working drivers\n",
" model.Minimize(cp_model.LinearExpr.Sum(working_drivers))\n",
" # minimize the number of working drivers\n",
" model.minimize(cp_model.LinearExpr.sum(working_drivers))\n",
" else:\n",
" # Minimize the sum of delays between tasks, which in turns minimize the\n",
" # minimize the sum of delays between tasks, which in turns minimize the\n",
" # sum of working times as the total driving time is fixed\n",
" model.Minimize(cp_model.LinearExpr.WeightedSum(delay_literals, delay_weights))\n",
" model.minimize(cp_model.LinearExpr.weighted_sum(delay_literals, delay_weights))\n",
"\n",
" if not minimize_drivers and _OUTPUT_PROTO.value:\n",
" print(\"Writing proto to %s\" % _OUTPUT_PROTO.value)\n",
@@ -2049,41 +2051,41 @@
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status != cp_model.OPTIMAL and status != cp_model.FEASIBLE:\n",
" return -1\n",
"\n",
" # Display solution\n",
" if minimize_drivers:\n",
" max_num_drivers = int(solver.ObjectiveValue())\n",
" max_num_drivers = int(solver.objective_value)\n",
" print(\"minimal number of drivers =\", max_num_drivers)\n",
" return max_num_drivers\n",
"\n",
" for d in range(num_drivers):\n",
" print(\"Driver %i: \" % (d + 1))\n",
" print(\" total driving time =\", solver.Value(driving_times[d]))\n",
" print(\" total driving time =\", solver.value(driving_times[d]))\n",
" print(\n",
" \" working time =\",\n",
" solver.Value(working_times[d]) + setup_time + cleanup_time,\n",
" solver.value(working_times[d]) + setup_time + cleanup_time,\n",
" )\n",
"\n",
" first = True\n",
" for s in range(num_shifts):\n",
" shift = shifts[s]\n",
"\n",
" if not solver.BooleanValue(performed[d, s]):\n",
" if not solver.boolean_value(performed[d, s]):\n",
" continue\n",
"\n",
" # Hack to detect if the waiting time between the last shift and\n",
" # this one exceeds 30 minutes. For this, we look at the\n",
" # no_break_driving which was reinitialized in that case.\n",
" if solver.Value(no_break_driving[d, s]) == shift[5] and not first:\n",
" if solver.value(no_break_driving[d, s]) == shift[5] and not first:\n",
" print(\" **break**\")\n",
" print(\" shift \", shift[0], \":\", shift[1], \"-\", shift[2])\n",
" first = False\n",
"\n",
" return int(solver.ObjectiveValue())\n",
" return int(solver.objective_value)\n",
"\n",
"\n",
"def main(_):\n",

View File

@@ -138,37 +138,40 @@
" for s in all_sets\n",
" ]\n",
"\n",
" set_vars = [model.NewIntVar(0, max_set[s], f\"set_{s}\") for s in all_sets]\n",
" set_vars = [model.new_int_var(0, max_set[s], f\"set_{s}\") for s in all_sets]\n",
"\n",
" epsilon = model.NewIntVar(0, 10000000, \"epsilon\")\n",
" epsilon = model.new_int_var(0, 10000000, \"epsilon\")\n",
"\n",
" for p in all_products:\n",
" model.Add(\n",
" model.add(\n",
" sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)\n",
" <= int(max_quantities[p][1] * 10000)\n",
" )\n",
" model.Add(\n",
" model.add(\n",
" sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)\n",
" >= int(max_quantities[p][1] * 10000) - epsilon\n",
" )\n",
"\n",
" model.Minimize(epsilon)\n",
" model.minimize(epsilon)\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" status = solver.solve(model)\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" # The objective value of the solution.\n",
" print(f\"Optimal objective value = {solver.ObjectiveValue() / 10000.0}\")\n",
" print(f\"Optimal objective value = {solver.objective_value / 10000.0}\")\n",
"\n",
" for s in all_sets:\n",
" print(f\" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}\", end=\" \")\n",
" print(\n",
" f\" {chemical_set[s][0]} = {solver.value(set_vars[s]) / 1000.0}\",\n",
" end=\" \",\n",
" )\n",
" print()\n",
" for p in all_products:\n",
" name = max_quantities[p][0]\n",
" max_quantity = max_quantities[p][1]\n",
" quantity = sum(\n",
" solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]\n",
" solver.value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]\n",
" for s in all_sets\n",
" )\n",
" print(f\"{name}: {quantity} out of {max_quantity}\")\n",

View File

@@ -151,14 +151,14 @@
" obj_coeffs = []\n",
" for n1 in range(num_nodes - 1):\n",
" for n2 in range(n1 + 1, num_nodes):\n",
" same = model.NewBoolVar(\"neighbors_%i_%i\" % (n1, n2))\n",
" same = model.new_bool_var(\"neighbors_%i_%i\" % (n1, n2))\n",
" neighbors[n1, n2] = same\n",
" obj_vars.append(same)\n",
" obj_coeffs.append(distance_matrix[n1][n2] + distance_matrix[n2][n1])\n",
"\n",
" # Number of neighborss:\n",
" for n in range(num_nodes):\n",
" model.Add(\n",
" model.add(\n",
" sum(neighbors[m, n] for m in range(n))\n",
" + sum(neighbors[n, m] for m in range(n + 1, num_nodes))\n",
" == group_size - 1\n",
@@ -168,23 +168,23 @@
" for n1 in range(num_nodes - 2):\n",
" for n2 in range(n1 + 1, num_nodes - 1):\n",
" for n3 in range(n2 + 1, num_nodes):\n",
" model.Add(\n",
" model.add(\n",
" neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2\n",
" )\n",
"\n",
" # Redundant constraints on total sum of neighborss.\n",
" model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2)\n",
" model.add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2)\n",
"\n",
" # Minimize weighted sum of arcs.\n",
" model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
" model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.log_search_progress = True\n",
" solver.parameters.num_search_workers = 8\n",
"\n",
" status = solver.Solve(model)\n",
" print(solver.ResponseStats())\n",
" status = solver.solve(model)\n",
" print(solver.response_stats())\n",
"\n",
" if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n",
" visited = set()\n",
@@ -194,7 +194,7 @@
" visited.add(n)\n",
" output = str(n)\n",
" for o in range(n + 1, num_nodes):\n",
" if solver.BooleanValue(neighbors[n, o]):\n",
" if solver.boolean_value(neighbors[n, o]):\n",
" visited.add(o)\n",
" output += \" \" + str(o)\n",
" print(\"Group\", g, \":\", output)\n",

View File

@@ -87,7 +87,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def cover_rectangle(num_squares):\n",
"def cover_rectangle(num_squares: int) -> bool:\n",
" \"\"\"Try to fill the rectangle with a given number of squares.\"\"\"\n",
" size_x = 60\n",
" size_y = 50\n",
@@ -103,17 +103,17 @@
"\n",
" # Creates intervals for the NoOverlap2D and size variables.\n",
" for i in range(num_squares):\n",
" size = model.NewIntVar(1, size_y, \"size_%i\" % i)\n",
" start_x = model.NewIntVar(0, size_x, \"sx_%i\" % i)\n",
" end_x = model.NewIntVar(0, size_x, \"ex_%i\" % i)\n",
" start_y = model.NewIntVar(0, size_y, \"sy_%i\" % i)\n",
" end_y = model.NewIntVar(0, size_y, \"ey_%i\" % i)\n",
" size = model.new_int_var(1, size_y, \"size_%i\" % i)\n",
" start_x = model.new_int_var(0, size_x, \"sx_%i\" % i)\n",
" end_x = model.new_int_var(0, size_x, \"ex_%i\" % i)\n",
" start_y = model.new_int_var(0, size_y, \"sy_%i\" % i)\n",
" end_y = model.new_int_var(0, size_y, \"ey_%i\" % i)\n",
"\n",
" interval_x = model.NewIntervalVar(start_x, size, end_x, \"ix_%i\" % i)\n",
" interval_y = model.NewIntervalVar(start_y, size, end_y, \"iy_%i\" % i)\n",
" interval_x = model.new_interval_var(start_x, size, end_x, \"ix_%i\" % i)\n",
" interval_y = model.new_interval_var(start_y, size, end_y, \"iy_%i\" % i)\n",
"\n",
" area = model.NewIntVar(1, size_y * size_y, \"area_%i\" % i)\n",
" model.AddMultiplicationEquality(area, [size, size])\n",
" area = model.new_int_var(1, size_y * size_y, \"area_%i\" % i)\n",
" model.add_multiplication_equality(area, [size, size])\n",
"\n",
" areas.append(area)\n",
" x_intervals.append(interval_x)\n",
@@ -123,47 +123,46 @@
" y_starts.append(start_y)\n",
"\n",
" # Main constraint.\n",
" model.AddNoOverlap2D(x_intervals, y_intervals)\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" # Redundant constraints.\n",
" model.AddCumulative(x_intervals, sizes, size_y)\n",
" model.AddCumulative(y_intervals, sizes, size_x)\n",
" model.add_cumulative(x_intervals, sizes, size_y)\n",
" model.add_cumulative(y_intervals, sizes, size_x)\n",
"\n",
" # Forces the rectangle to be exactly covered.\n",
" model.Add(sum(areas) == size_x * size_y)\n",
" model.add(sum(areas) == size_x * size_y)\n",
"\n",
" # Symmetry breaking 1: sizes are ordered.\n",
" for i in range(num_squares - 1):\n",
" model.Add(sizes[i] <= sizes[i + 1])\n",
" model.add(sizes[i] <= sizes[i + 1])\n",
"\n",
" # Define same to be true iff sizes[i] == sizes[i + 1]\n",
" same = model.NewBoolVar(\"\")\n",
" model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same)\n",
" model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not())\n",
" same = model.new_bool_var(\"\")\n",
" model.add(sizes[i] == sizes[i + 1]).only_enforce_if(same)\n",
" model.add(sizes[i] < sizes[i + 1]).only_enforce_if(~same)\n",
"\n",
" # Tie break with starts.\n",
" model.Add(x_starts[i] <= x_starts[i + 1]).OnlyEnforceIf(same)\n",
" model.add(x_starts[i] <= x_starts[i + 1]).only_enforce_if(same)\n",
"\n",
" # Symmetry breaking 2: first square in one quadrant.\n",
" model.Add(x_starts[0] < (size_x + 1) // 2)\n",
" model.Add(y_starts[0] < (size_y + 1) // 2)\n",
" model.add(x_starts[0] < (size_x + 1) // 2)\n",
" model.add(y_starts[0] < (size_y + 1) // 2)\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_workers = 16\n",
" # solver.parameters.log_search_progress = True\n",
" solver.parameters.num_workers = 8\n",
" solver.parameters.max_time_in_seconds = 10.0\n",
" status = solver.Solve(model)\n",
" print(\"%s found in %0.2fs\" % (solver.StatusName(status), solver.WallTime()))\n",
" status = solver.solve(model)\n",
" print(\"%s found in %0.2fs\" % (solver.status_name(status), solver.wall_time))\n",
"\n",
" # Prints solution.\n",
" solution_found = status == cp_model.OPTIMAL or status == cp_model.FEASIBLE\n",
" if solution_found:\n",
" display = [[\" \" for _ in range(size_x)] for _ in range(size_y)]\n",
" for i in range(num_squares):\n",
" sol_x = solver.Value(x_starts[i])\n",
" sol_y = solver.Value(y_starts[i])\n",
" sol_s = solver.Value(sizes[i])\n",
" sol_x = solver.value(x_starts[i])\n",
" sol_y = solver.value(y_starts[i])\n",
" sol_s = solver.value(sizes[i])\n",
" char = format(i, \"01x\")\n",
" for j in range(sol_s):\n",
" for k in range(sol_s):\n",

View File

@@ -88,58 +88,58 @@
"\n",
"\n",
"def send_more_money():\n",
" \"\"\"Solve the cryptarithmic puzzle SEND+MORE=MONEY.\"\"\"\n",
" \"\"\"solve the cryptarithmic puzzle SEND+MORE=MONEY.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
" # Create variables.\n",
" # Since s is a leading digit, it can't be 0.\n",
" s = model.NewIntVar(1, 9, \"s\")\n",
" e = model.NewIntVar(0, 9, \"e\")\n",
" n = model.NewIntVar(0, 9, \"n\")\n",
" d = model.NewIntVar(0, 9, \"d\")\n",
" s = model.new_int_var(1, 9, \"s\")\n",
" e = model.new_int_var(0, 9, \"e\")\n",
" n = model.new_int_var(0, 9, \"n\")\n",
" d = model.new_int_var(0, 9, \"d\")\n",
" # Since m is a leading digit, it can't be 0.\n",
" m = model.NewIntVar(1, 9, \"m\")\n",
" o = model.NewIntVar(0, 9, \"o\")\n",
" r = model.NewIntVar(0, 9, \"r\")\n",
" y = model.NewIntVar(0, 9, \"y\")\n",
" m = model.new_int_var(1, 9, \"m\")\n",
" o = model.new_int_var(0, 9, \"o\")\n",
" r = model.new_int_var(0, 9, \"r\")\n",
" y = model.new_int_var(0, 9, \"y\")\n",
"\n",
" # Create carry variables. c0 is true if the first column of addends carries\n",
" # a 1, c2 is true if the second column carries a 1, and so on.\n",
" c0 = model.NewBoolVar(\"c0\")\n",
" c1 = model.NewBoolVar(\"c1\")\n",
" c2 = model.NewBoolVar(\"c2\")\n",
" c3 = model.NewBoolVar(\"c3\")\n",
" c0 = model.new_bool_var(\"c0\")\n",
" c1 = model.new_bool_var(\"c1\")\n",
" c2 = model.new_bool_var(\"c2\")\n",
" c3 = model.new_bool_var(\"c3\")\n",
"\n",
" # Force all letters to take on different values.\n",
" model.AddAllDifferent(s, e, n, d, m, o, r, y)\n",
" model.add_all_different(s, e, n, d, m, o, r, y)\n",
"\n",
" # Column 0:\n",
" model.Add(c0 == m)\n",
" model.add(c0 == m)\n",
"\n",
" # Column 1:\n",
" model.Add(c1 + s + m == o + 10 * c0)\n",
" model.add(c1 + s + m == o + 10 * c0)\n",
"\n",
" # Column 2:\n",
" model.Add(c2 + e + o == n + 10 * c1)\n",
" model.add(c2 + e + o == n + 10 * c1)\n",
"\n",
" # Column 3:\n",
" model.Add(c3 + n + r == e + 10 * c2)\n",
" model.add(c3 + n + r == e + 10 * c2)\n",
"\n",
" # Column 4:\n",
" model.Add(d + e == y + 10 * c3)\n",
" model.add(d + e == y + 10 * c3)\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" if solver.Solve(model) == cp_model.OPTIMAL:\n",
" if solver.solve(model) == cp_model.OPTIMAL:\n",
" print(\"Optimal solution found!\")\n",
" print(\"s:\", solver.Value(s))\n",
" print(\"e:\", solver.Value(e))\n",
" print(\"n:\", solver.Value(n))\n",
" print(\"d:\", solver.Value(d))\n",
" print(\"m:\", solver.Value(m))\n",
" print(\"o:\", solver.Value(o))\n",
" print(\"r:\", solver.Value(r))\n",
" print(\"y:\", solver.Value(y))\n",
" print(\"s:\", solver.value(s))\n",
" print(\"e:\", solver.value(e))\n",
" print(\"n:\", solver.value(n))\n",
" print(\"d:\", solver.value(d))\n",
" print(\"m:\", solver.value(m))\n",
" print(\"o:\", solver.value(o))\n",
" print(\"r:\", solver.value(r))\n",
" print(\"y:\", solver.value(y))\n",
"\n",
"\n",
"def main(_):\n",

View File

@@ -109,13 +109,13 @@
" \"\"\"Called at each new solution.\"\"\"\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" % (self.__solution_count, self.wall_time, self.objective_value)\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
"def flexible_jobshop():\n",
" \"\"\"Solve a small flexible jobshop problem.\"\"\"\n",
" \"\"\"solve a small flexible jobshop problem.\"\"\"\n",
" # Data part.\n",
" jobs = [ # task = (processing_time, machine_id)\n",
" [ # Job 0\n",
@@ -181,12 +181,12 @@
"\n",
" # Create main interval for the task.\n",
" suffix_name = \"_j%i_t%i\" % (job_id, task_id)\n",
" start = model.NewIntVar(0, horizon, \"start\" + suffix_name)\n",
" duration = model.NewIntVar(\n",
" start = model.new_int_var(0, horizon, \"start\" + suffix_name)\n",
" duration = model.new_int_var(\n",
" min_duration, max_duration, \"duration\" + suffix_name\n",
" )\n",
" end = model.NewIntVar(0, horizon, \"end\" + suffix_name)\n",
" interval = model.NewIntervalVar(\n",
" end = model.new_int_var(0, horizon, \"end\" + suffix_name)\n",
" interval = model.new_interval_var(\n",
" start, duration, end, \"interval\" + suffix_name\n",
" )\n",
"\n",
@@ -195,7 +195,7 @@
"\n",
" # Add precedence with previous task in the same job.\n",
" if previous_end is not None:\n",
" model.Add(start >= previous_end)\n",
" model.add(start >= previous_end)\n",
" previous_end = end\n",
"\n",
" # Create alternative intervals.\n",
@@ -203,19 +203,19 @@
" l_presences = []\n",
" for alt_id in all_alternatives:\n",
" alt_suffix = \"_j%i_t%i_a%i\" % (job_id, task_id, alt_id)\n",
" l_presence = model.NewBoolVar(\"presence\" + alt_suffix)\n",
" l_start = model.NewIntVar(0, horizon, \"start\" + alt_suffix)\n",
" l_presence = model.new_bool_var(\"presence\" + alt_suffix)\n",
" l_start = model.new_int_var(0, horizon, \"start\" + alt_suffix)\n",
" l_duration = task[alt_id][0]\n",
" l_end = model.NewIntVar(0, horizon, \"end\" + alt_suffix)\n",
" l_interval = model.NewOptionalIntervalVar(\n",
" l_end = model.new_int_var(0, horizon, \"end\" + alt_suffix)\n",
" l_interval = model.new_optional_interval_var(\n",
" l_start, l_duration, l_end, l_presence, \"interval\" + alt_suffix\n",
" )\n",
" l_presences.append(l_presence)\n",
"\n",
" # Link the primary/global variables with the local ones.\n",
" model.Add(start == l_start).OnlyEnforceIf(l_presence)\n",
" model.Add(duration == l_duration).OnlyEnforceIf(l_presence)\n",
" model.Add(end == l_end).OnlyEnforceIf(l_presence)\n",
" model.add(start == l_start).only_enforce_if(l_presence)\n",
" model.add(duration == l_duration).only_enforce_if(l_presence)\n",
" model.add(end == l_end).only_enforce_if(l_presence)\n",
"\n",
" # Add the local interval to the right machine.\n",
" intervals_per_resources[task[alt_id][1]].append(l_interval)\n",
@@ -224,10 +224,10 @@
" presences[(job_id, task_id, alt_id)] = l_presence\n",
"\n",
" # Select exactly one presence variable.\n",
" model.AddExactlyOne(l_presences)\n",
" model.add_exactly_one(l_presences)\n",
" else:\n",
" intervals_per_resources[task[0][1]].append(interval)\n",
" presences[(job_id, task_id, 0)] = model.NewConstant(1)\n",
" presences[(job_id, task_id, 0)] = model.new_constant(1)\n",
"\n",
" job_ends.append(previous_end)\n",
"\n",
@@ -235,28 +235,28 @@
" for machine_id in all_machines:\n",
" intervals = intervals_per_resources[machine_id]\n",
" if len(intervals) > 1:\n",
" model.AddNoOverlap(intervals)\n",
" model.add_no_overlap(intervals)\n",
"\n",
" # Makespan objective\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, job_ends)\n",
" model.Minimize(makespan)\n",
" makespan = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(makespan, job_ends)\n",
" model.minimize(makespan)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = SolutionPrinter()\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" # Print final solution.\n",
" for job_id in all_jobs:\n",
" print(\"Job %i:\" % job_id)\n",
" for task_id in range(len(jobs[job_id])):\n",
" start_value = solver.Value(starts[(job_id, task_id)])\n",
" start_value = solver.value(starts[(job_id, task_id)])\n",
" machine = -1\n",
" duration = -1\n",
" selected = -1\n",
" for alt_id in range(len(jobs[job_id][task_id])):\n",
" if solver.Value(presences[(job_id, task_id, alt_id)]):\n",
" if solver.value(presences[(job_id, task_id, alt_id)]):\n",
" duration = jobs[job_id][task_id][alt_id][0]\n",
" machine = jobs[job_id][task_id][alt_id][1]\n",
" selected = alt_id\n",
@@ -265,12 +265,12 @@
" % (job_id, task_id, start_value, selected, machine, duration)\n",
" )\n",
"\n",
" print(\"Solve status: %s\" % solver.StatusName(status))\n",
" print(\"Optimal objective value: %i\" % solver.ObjectiveValue())\n",
" print(\"solve status: %s\" % solver.status_name(status))\n",
" print(\"Optimal objective value: %i\" % solver.objective_value)\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"flexible_jobshop()\n",

View File

@@ -134,66 +134,70 @@
"\n",
" for i in all_jobs:\n",
" # Create main interval.\n",
" start = model.NewIntVar(0, horizon, \"start_%i\" % i)\n",
" start = model.new_int_var(0, horizon, \"start_%i\" % i)\n",
" duration = jobs[i][0]\n",
" end = model.NewIntVar(0, horizon, \"end_%i\" % i)\n",
" interval = model.NewIntervalVar(start, duration, end, \"interval_%i\" % i)\n",
" end = model.new_int_var(0, horizon, \"end_%i\" % i)\n",
" interval = model.new_interval_var(start, duration, end, \"interval_%i\" % i)\n",
" starts.append(start)\n",
" intervals.append(interval)\n",
" ends.append(end)\n",
" demands.append(jobs[i][1])\n",
"\n",
" # Create an optional copy of interval to be executed on machine 0.\n",
" performed_on_m0 = model.NewBoolVar(\"perform_%i_on_m0\" % i)\n",
" performed_on_m0 = model.new_bool_var(\"perform_%i_on_m0\" % i)\n",
" performed.append(performed_on_m0)\n",
" start0 = model.NewIntVar(0, horizon, \"start_%i_on_m0\" % i)\n",
" end0 = model.NewIntVar(0, horizon, \"end_%i_on_m0\" % i)\n",
" interval0 = model.NewOptionalIntervalVar(\n",
" start0 = model.new_int_var(0, horizon, \"start_%i_on_m0\" % i)\n",
" end0 = model.new_int_var(0, horizon, \"end_%i_on_m0\" % i)\n",
" interval0 = model.new_optional_interval_var(\n",
" start0, duration, end0, performed_on_m0, \"interval_%i_on_m0\" % i\n",
" )\n",
" intervals0.append(interval0)\n",
"\n",
" # Create an optional copy of interval to be executed on machine 1.\n",
" start1 = model.NewIntVar(0, horizon, \"start_%i_on_m1\" % i)\n",
" end1 = model.NewIntVar(0, horizon, \"end_%i_on_m1\" % i)\n",
" interval1 = model.NewOptionalIntervalVar(\n",
" start1, duration, end1, performed_on_m0.Not(), \"interval_%i_on_m1\" % i\n",
" start1 = model.new_int_var(0, horizon, \"start_%i_on_m1\" % i)\n",
" end1 = model.new_int_var(0, horizon, \"end_%i_on_m1\" % i)\n",
" interval1 = model.new_optional_interval_var(\n",
" start1,\n",
" duration,\n",
" end1,\n",
" ~performed_on_m0,\n",
" \"interval_%i_on_m1\" % i,\n",
" )\n",
" intervals1.append(interval1)\n",
"\n",
" # We only propagate the constraint if the tasks is performed on the machine.\n",
" model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)\n",
" model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())\n",
" model.add(start0 == start).only_enforce_if(performed_on_m0)\n",
" model.add(start1 == start).only_enforce_if(~performed_on_m0)\n",
"\n",
" # Width constraint (modeled as a cumulative)\n",
" model.AddCumulative(intervals, demands, max_width)\n",
" model.add_cumulative(intervals, demands, max_width)\n",
"\n",
" # Choose which machine to perform the jobs on.\n",
" model.AddNoOverlap(intervals0)\n",
" model.AddNoOverlap(intervals1)\n",
" model.add_no_overlap(intervals0)\n",
" model.add_no_overlap(intervals1)\n",
"\n",
" # Objective variable.\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, ends)\n",
" model.Minimize(makespan)\n",
" makespan = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(makespan, ends)\n",
" model.minimize(makespan)\n",
"\n",
" # Symmetry breaking.\n",
" model.Add(performed[0] == 0)\n",
" model.add(performed[0] == 0)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.Solve(model)\n",
" solver.solve(model)\n",
"\n",
" # Output solution.\n",
" if visualization.RunFromIPython():\n",
" output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)\n",
" output.AddTitle(\"Makespan = %i\" % solver.ObjectiveValue())\n",
" output = visualization.SvgWrapper(solver.objective_value, max_width, 40.0)\n",
" output.AddTitle(\"Makespan = %i\" % solver.objective_value)\n",
" color_manager = visualization.ColorManager()\n",
" color_manager.SeedRandomColor(0)\n",
"\n",
" for i in all_jobs:\n",
" performed_machine = 1 - solver.Value(performed[i])\n",
" start = solver.Value(starts[i])\n",
" performed_machine = 1 - solver.value(performed[i])\n",
" start = solver.value(starts[i])\n",
" d_x = jobs[i][0]\n",
" d_y = jobs[i][1]\n",
" s_y = performed_machine * (max_width - d_y)\n",
@@ -206,17 +210,17 @@
" output.Display()\n",
" else:\n",
" print(\"Solution\")\n",
" print(\" - makespan = %i\" % solver.ObjectiveValue())\n",
" print(\" - makespan = %i\" % solver.objective_value)\n",
" for i in all_jobs:\n",
" performed_machine = 1 - solver.Value(performed[i])\n",
" start = solver.Value(starts[i])\n",
" performed_machine = 1 - solver.value(performed[i])\n",
" start = solver.value(starts[i])\n",
" print(\n",
" \" - Job %i starts at %i on machine %i\" % (i, start, performed_machine)\n",
" )\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"main()\n",

View File

@@ -105,7 +105,7 @@
")\n",
"\n",
"\n",
"def solve_golomb_ruler(order, params):\n",
"def solve_golomb_ruler(order: int, params: str):\n",
" \"\"\"Solve the Golomb ruler problem.\"\"\"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
@@ -113,26 +113,26 @@
" var_max = order * order\n",
" all_vars = list(range(0, order))\n",
"\n",
" marks = [model.NewIntVar(0, var_max, f\"marks_{i}\") for i in all_vars]\n",
" marks = [model.new_int_var(0, var_max, f\"marks_{i}\") for i in all_vars]\n",
"\n",
" model.Add(marks[0] == 0)\n",
" model.add(marks[0] == 0)\n",
" for i in range(order - 2):\n",
" model.Add(marks[i + 1] > marks[i])\n",
" model.add(marks[i + 1] > marks[i])\n",
"\n",
" diffs = []\n",
" for i in range(order - 1):\n",
" for j in range(i + 1, order):\n",
" diff = model.NewIntVar(0, var_max, f\"diff [{j},{i}]\")\n",
" model.Add(diff == marks[j] - marks[i])\n",
" diff = model.new_int_var(0, var_max, f\"diff [{j},{i}]\")\n",
" model.add(diff == marks[j] - marks[i])\n",
" diffs.append(diff)\n",
" model.AddAllDifferent(diffs)\n",
" model.add_all_different(diffs)\n",
"\n",
" # symmetry breaking\n",
" if order > 2:\n",
" model.Add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0])\n",
" model.add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0])\n",
"\n",
" # Objective\n",
" model.Minimize(marks[order - 1])\n",
" model.minimize(marks[order - 1])\n",
"\n",
" # Solve the model.\n",
" solver = cp_model.CpSolver()\n",
@@ -140,21 +140,21 @@
" text_format.Parse(params, solver.parameters)\n",
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
" print(f\"Golomb ruler(order={order})\")\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" # Print solution.\n",
" print(f\"status: {solver.StatusName(status)}\")\n",
" print(f\"status: {solver.status_name(status)}\")\n",
" if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n",
" for idx, var in enumerate(marks):\n",
" print(f\"mark[{idx}]: {solver.Value(var)}\")\n",
" intervals = [solver.Value(diff) for diff in diffs]\n",
" print(f\"mark[{idx}]: {solver.value(var)}\")\n",
" intervals = [solver.value(diff) for diff in diffs]\n",
" intervals.sort()\n",
" print(f\"intervals: {intervals}\")\n",
"\n",
" print(\"Statistics:\")\n",
" print(f\"- conflicts: {solver.NumConflicts()}\")\n",
" print(f\"- branches : {solver.NumBranches()}\")\n",
" print(f\"- wall time: {solver.WallTime()}s\\n\")\n",
" print(f\"- conflicts: {solver.num_conflicts}\")\n",
" print(f\"- branches : {solver.num_branches}\")\n",
" print(f\"- wall time: {solver.wall_time}s\\n\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -83,11 +83,12 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Union\n",
"from ortools.sat.colab import visualization\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def build_pairs(rows, cols):\n",
"def build_pairs(rows: int, cols: int) -> list[tuple[int, int]]:\n",
" \"\"\"Build closeness pairs for consecutive numbers.\n",
"\n",
" Build set of allowed pairs such that two consecutive numbers touch\n",
@@ -116,7 +117,7 @@
" return result\n",
"\n",
"\n",
"def print_solution(positions, rows, cols):\n",
"def print_solution(positions: list[int], rows: int, cols: int):\n",
" \"\"\"Print a current solution.\"\"\"\n",
" # Create empty board.\n",
" board = []\n",
@@ -131,7 +132,7 @@
" print_matrix(board)\n",
"\n",
"\n",
"def print_matrix(game):\n",
"def print_matrix(game: list[list[int]]) -> None:\n",
" \"\"\"Pretty print of a matrix.\"\"\"\n",
" rows = len(game)\n",
" cols = len(game[0])\n",
@@ -145,7 +146,7 @@
" print(line)\n",
"\n",
"\n",
"def build_puzzle(problem):\n",
"def build_puzzle(problem: int) -> Union[None, list[list[int]]]:\n",
" \"\"\"Build the problem from its index.\"\"\"\n",
" #\n",
" # models, a 0 indicates an open cell which number is not yet known.\n",
@@ -214,8 +215,8 @@
" return puzzle\n",
"\n",
"\n",
"def solve_hidato(puzzle, index):\n",
" \"\"\"Solve the given hidato table.\"\"\"\n",
"def solve_hidato(puzzle: list[list[int]], index: int):\n",
" \"\"\"solve the given hidato table.\"\"\"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
@@ -229,58 +230,58 @@
" print_matrix(puzzle)\n",
"\n",
" #\n",
" # declare variables\n",
" # Declare variables.\n",
" #\n",
" positions = [model.NewIntVar(0, r * c - 1, \"p[%i]\" % i) for i in range(r * c)]\n",
" positions = [model.new_int_var(0, r * c - 1, \"p[%i]\" % i) for i in range(r * c)]\n",
"\n",
" #\n",
" # constraints\n",
" # Constraints.\n",
" #\n",
" model.AddAllDifferent(positions)\n",
" model.add_all_different(positions)\n",
"\n",
" #\n",
" # Fill in the clues\n",
" # Fill in the clues.\n",
" #\n",
" for i in range(r):\n",
" for j in range(c):\n",
" if puzzle[i][j] > 0:\n",
" model.Add(positions[puzzle[i][j] - 1] == i * c + j)\n",
" model.add(positions[puzzle[i][j] - 1] == i * c + j)\n",
"\n",
" # Consecutive numbers much touch each other in the grid.\n",
" # We use an allowed assignment constraint to model it.\n",
" close_tuples = build_pairs(r, c)\n",
" for k in range(0, r * c - 1):\n",
" model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)\n",
" model.add_allowed_assignments([positions[k], positions[k + 1]], close_tuples)\n",
"\n",
" #\n",
" # solution and search\n",
" # Solution and search.\n",
" #\n",
"\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" if visualization.RunFromIPython():\n",
" output = visualization.SvgWrapper(10, r, 40.0)\n",
" for i, var in enumerate(positions):\n",
" val = solver.Value(var)\n",
" val = solver.value(var)\n",
" x = val % c\n",
" y = val // c\n",
" color = \"white\" if puzzle[y][x] == 0 else \"lightgreen\"\n",
" output.AddRectangle(x, r - y - 1, 1, 1, color, \"black\", str(i + 1))\n",
"\n",
" output.AddTitle(\"Puzzle %i solved in %f s\" % (index, solver.WallTime()))\n",
" output.AddTitle(\"Puzzle %i solved in %f s\" % (index, solver.wall_time))\n",
" output.Display()\n",
" else:\n",
" print_solution(\n",
" [solver.Value(x) for x in positions],\n",
" [solver.value(x) for x in positions],\n",
" r,\n",
" c,\n",
" )\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"def main(_):\n",

View File

@@ -171,6 +171,7 @@
" # RunIntegerExampleNaturalLanguageAPI('CBC')\n",
" RunIntegerExampleNaturalLanguageAPI(\"SCIP\")\n",
" RunIntegerExampleNaturalLanguageAPI(\"SAT\")\n",
" RunIntegerExampleNaturalLanguageAPI(\"XPRESS\")\n",
"\n",
"\n",
"def RunAllIntegerExampleCppStyleAPI():\n",
@@ -179,6 +180,7 @@
" # RunIntegerExampleCppStyleAPI('CBC')\n",
" RunIntegerExampleCppStyleAPI(\"SCIP\")\n",
" RunIntegerExampleCppStyleAPI(\"SAT\")\n",
" RunIntegerExampleCppStyleAPI(\"XPRESS\")\n",
"\n",
"\n",
"def main():\n",

View File

@@ -100,7 +100,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def distance_between_jobs(x, y):\n",
"def distance_between_jobs(x: int, y: int) -> int:\n",
" \"\"\"Returns the distance between tasks of job x and tasks of job y.\"\"\"\n",
" return abs(x - y)\n",
"\n",
@@ -142,10 +142,10 @@
" all_tasks = {}\n",
" for i in all_jobs:\n",
" for j in all_machines:\n",
" start_var = model.NewIntVar(0, horizon, \"start_%i_%i\" % (i, j))\n",
" start_var = model.new_int_var(0, horizon, \"start_%i_%i\" % (i, j))\n",
" duration = durations[i][j]\n",
" end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.NewIntervalVar(\n",
" end_var = model.new_int_var(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.new_interval_var(\n",
" start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n",
" )\n",
" all_tasks[(i, j)] = task_type(\n",
@@ -165,51 +165,51 @@
" job_indices.append(j)\n",
" job_starts.append(all_tasks[(j, k)].start)\n",
" job_ends.append(all_tasks[(j, k)].end)\n",
" model.AddNoOverlap(job_intervals)\n",
" model.add_no_overlap(job_intervals)\n",
"\n",
" arcs = []\n",
" for j1 in range(len(job_intervals)):\n",
" # Initial arc from the dummy node (0) to a task.\n",
" start_lit = model.NewBoolVar(\"%i is first job\" % j1)\n",
" start_lit = model.new_bool_var(\"%i is first job\" % j1)\n",
" arcs.append((0, j1 + 1, start_lit))\n",
" # Final arc from an arc to the dummy node.\n",
" arcs.append((j1 + 1, 0, model.NewBoolVar(\"%i is last job\" % j1)))\n",
" arcs.append((j1 + 1, 0, model.new_bool_var(\"%i is last job\" % j1)))\n",
"\n",
" for j2 in range(len(job_intervals)):\n",
" if j1 == j2:\n",
" continue\n",
"\n",
" lit = model.NewBoolVar(\"%i follows %i\" % (j2, j1))\n",
" lit = model.new_bool_var(\"%i follows %i\" % (j2, j1))\n",
" arcs.append((j1 + 1, j2 + 1, lit))\n",
"\n",
" # We add the reified precedence to link the literal with the\n",
" # times of the two tasks.\n",
" min_distance = distance_between_jobs(j1, j2)\n",
" model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf(\n",
" lit\n",
" )\n",
" model.add(\n",
" job_starts[j2] >= job_ends[j1] + min_distance\n",
" ).only_enforce_if(lit)\n",
"\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # Precedences inside a job.\n",
" for i in all_jobs:\n",
" for j in range(0, machines_count - 1):\n",
" model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n",
" model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n",
"\n",
" # Makespan objective.\n",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n",
" )\n",
" model.Minimize(obj_var)\n",
" model.minimize(obj_var)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Output solution.\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Optimal makespan: %i\" % solver.ObjectiveValue())\n",
" print(\"Optimal makespan: %i\" % solver.objective_value)\n",
"\n",
"\n",
"jobshop_ft06_distance()\n",

View File

@@ -98,7 +98,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def jobshop_ft06():\n",
"def jobshop_ft06() -> None:\n",
" \"\"\"Solves the ft06 jobshop.\"\"\"\n",
" # Creates the solver.\n",
" model = cp_model.CpModel()\n",
@@ -135,10 +135,10 @@
" all_tasks = {}\n",
" for i in all_jobs:\n",
" for j in all_machines:\n",
" start_var = model.NewIntVar(0, horizon, \"start_%i_%i\" % (i, j))\n",
" start_var = model.new_int_var(0, horizon, \"start_%i_%i\" % (i, j))\n",
" duration = durations[i][j]\n",
" end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.NewIntervalVar(\n",
" end_var = model.new_int_var(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.new_interval_var(\n",
" start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n",
" )\n",
" all_tasks[(i, j)] = task_type(\n",
@@ -154,35 +154,35 @@
" if machines[j][k] == i:\n",
" machines_jobs.append(all_tasks[(j, k)].interval)\n",
" machine_to_jobs[i] = machines_jobs\n",
" model.AddNoOverlap(machines_jobs)\n",
" model.add_no_overlap(machines_jobs)\n",
"\n",
" # Precedences inside a job.\n",
" for i in all_jobs:\n",
" for j in range(0, machines_count - 1):\n",
" model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n",
" model.add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)\n",
"\n",
" # Makespan objective.\n",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n",
" )\n",
" model.Minimize(obj_var)\n",
" model.minimize(obj_var)\n",
"\n",
" # Solve model.\n",
" # Solve the model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.log_search_progress = True\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Output solution.\n",
" # Output the solution.\n",
" if status == cp_model.OPTIMAL:\n",
" if visualization.RunFromIPython():\n",
" starts = [\n",
" [solver.Value(all_tasks[(i, j)][0]) for j in all_machines]\n",
" [solver.value(all_tasks[(i, j)][0]) for j in all_machines]\n",
" for i in all_jobs\n",
" ]\n",
" visualization.DisplayJobshop(starts, durations, machines, \"FT06\")\n",
" else:\n",
" print(\"Optimal makespan: %i\" % solver.ObjectiveValue())\n",
" print(\"Optimal makespan: %i\" % solver.objective_value)\n",
"\n",
"\n",
"jobshop_ft06()\n",

View File

@@ -99,7 +99,7 @@
" \"\"\"Called at each new solution.\"\"\"\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" % (self.__solution_count, self.wall_time, self.objective_value)\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
@@ -133,13 +133,14 @@
" machine_to_intervals = collections.defaultdict(list)\n",
"\n",
" for job_id, job in enumerate(jobs_data):\n",
" for task_id, task in enumerate(job):\n",
" for entry in enumerate(job):\n",
" task_id, task = entry\n",
" machine = task[0]\n",
" duration = task[1]\n",
" suffix = \"_%i_%i\" % (job_id, task_id)\n",
" start_var = model.NewIntVar(0, horizon, \"start\" + suffix)\n",
" end_var = model.NewIntVar(0, horizon, \"end\" + suffix)\n",
" interval_var = model.NewIntervalVar(\n",
" start_var = model.new_int_var(0, horizon, \"start\" + suffix)\n",
" end_var = model.new_int_var(0, horizon, \"end\" + suffix)\n",
" interval_var = model.new_interval_var(\n",
" start_var, duration, end_var, \"interval\" + suffix\n",
" )\n",
" all_tasks[job_id, task_id] = task_type(\n",
@@ -148,31 +149,31 @@
" machine_to_intervals[machine].append(interval_var)\n",
"\n",
" # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}).\n",
" machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, \"weekend_0\"))\n",
" machine_to_intervals[0].append(model.new_interval_var(4, 4, 8, \"weekend_0\"))\n",
"\n",
" # Create and add disjunctive constraints.\n",
" for machine in all_machines:\n",
" model.AddNoOverlap(machine_to_intervals[machine])\n",
" model.add_no_overlap(machine_to_intervals[machine])\n",
"\n",
" # Precedences inside a job.\n",
" for job_id, job in enumerate(jobs_data):\n",
" for task_id in range(len(job) - 1):\n",
" model.Add(\n",
" model.add(\n",
" all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end\n",
" )\n",
"\n",
" # Makespan objective.\n",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(\n",
" obj_var,\n",
" [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)],\n",
" )\n",
" model.Minimize(obj_var)\n",
" model.minimize(obj_var)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = SolutionPrinter()\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" # Output solution.\n",
" if status == cp_model.OPTIMAL:\n",
@@ -183,7 +184,7 @@
" machine = task[0]\n",
" assigned_jobs[machine].append(\n",
" assigned_task_type(\n",
" start=solver.Value(all_tasks[job_id, task_id].start),\n",
" start=solver.value(all_tasks[job_id, task_id].start),\n",
" job=job_id,\n",
" index=task_id,\n",
" duration=task[1],\n",
@@ -200,13 +201,13 @@
"\n",
" for assigned_task in assigned_jobs[machine]:\n",
" name = \"job_%i_%i\" % (assigned_task.job, assigned_task.index)\n",
" # Add spaces to output to align columns.\n",
" # add spaces to output to align columns.\n",
" sol_line_tasks += \"%-10s\" % name\n",
" start = assigned_task.start\n",
" duration = assigned_task.duration\n",
"\n",
" sol_tmp = \"[%i,%i]\" % (start, start + duration)\n",
" # Add spaces to output to align columns.\n",
" # add spaces to output to align columns.\n",
" sol_line += \"%-10s\" % sol_tmp\n",
"\n",
" sol_line += \"\\n\"\n",
@@ -215,12 +216,12 @@
" output += sol_line\n",
"\n",
" # Finally print the solution found.\n",
" print(\"Optimal Schedule Length: %i\" % solver.ObjectiveValue())\n",
" print(\"Optimal Schedule Length: %i\" % solver.objective_value)\n",
" print(output)\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -97,6 +97,7 @@
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"_OUTPUT_PROTO = flags.define_string(\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
@@ -110,7 +111,7 @@
")\n",
"\n",
"\n",
"def build_data():\n",
"def build_data() -> tuple[pd.Series, int, int]:\n",
" \"\"\"Build the data frame.\"\"\"\n",
" data = \"\"\"\n",
" item width height available value color\n",
@@ -138,8 +139,8 @@
" return (data, max_height, max_width)\n",
"\n",
"\n",
"def solve_with_duplicate_items(data, max_height, max_width):\n",
" \"\"\"Solve the problem by building 2 items (rotated or not) for each item.\"\"\"\n",
"def solve_with_duplicate_items(data: pd.Series, max_height: int, max_width: int):\n",
" \"\"\"solve the problem by building 2 items (rotated or not) for each item.\"\"\"\n",
" # Derived data (expanded to individual items).\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
@@ -173,41 +174,47 @@
"\n",
" for i in range(num_items):\n",
" ## Is the item used?\n",
" is_used.append(model.NewBoolVar(f\"is_used{i}\"))\n",
" is_used.append(model.new_bool_var(f\"is_used{i}\"))\n",
"\n",
" ## Item coordinates.\n",
" x_starts.append(model.NewIntVar(0, max_width, f\"x_start{i}\"))\n",
" x_ends.append(model.NewIntVar(0, max_width, f\"x_end{i}\"))\n",
" y_starts.append(model.NewIntVar(0, max_height, f\"y_start{i}\"))\n",
" y_ends.append(model.NewIntVar(0, max_height, f\"y_end{i}\"))\n",
" x_starts.append(model.new_int_var(0, max_width, f\"x_start{i}\"))\n",
" x_ends.append(model.new_int_var(0, max_width, f\"x_end{i}\"))\n",
" y_starts.append(model.new_int_var(0, max_height, f\"y_start{i}\"))\n",
" y_ends.append(model.new_int_var(0, max_height, f\"y_end{i}\"))\n",
"\n",
" ## Interval variables.\n",
" x_intervals.append(\n",
" model.NewIntervalVar(\n",
" x_starts[i], item_widths[i] * is_used[i], x_ends[i], f\"x_interval{i}\"\n",
" model.new_interval_var(\n",
" x_starts[i],\n",
" item_widths[i] * is_used[i],\n",
" x_ends[i],\n",
" f\"x_interval{i}\",\n",
" )\n",
" )\n",
" y_intervals.append(\n",
" model.NewIntervalVar(\n",
" y_starts[i], item_heights[i] * is_used[i], y_ends[i], f\"y_interval{i}\"\n",
" model.new_interval_var(\n",
" y_starts[i],\n",
" item_heights[i] * is_used[i],\n",
" y_ends[i],\n",
" f\"y_interval{i}\",\n",
" )\n",
" )\n",
"\n",
" # Unused boxes are fixed at (0.0).\n",
" model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n",
" model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n",
" model.add(x_starts[i] == 0).only_enforce_if(~is_used[i])\n",
" model.add(y_starts[i] == 0).only_enforce_if(~is_used[i])\n",
"\n",
" # Constraints.\n",
"\n",
" ## Only one of non-rotated/rotated pair can be used.\n",
" for i in range(num_data_items):\n",
" model.Add(is_used[i] + is_used[i + num_data_items] <= 1)\n",
" model.add(is_used[i] + is_used[i + num_data_items] <= 1)\n",
"\n",
" ## 2D no overlap.\n",
" model.AddNoOverlap2D(x_intervals, y_intervals)\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" ## Objective.\n",
" model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values))\n",
" model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values))\n",
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
@@ -220,27 +227,29 @@
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n",
" used = {i for i in range(num_items) if solver.boolean_value(is_used[i])}\n",
" data = pd.DataFrame(\n",
" {\n",
" \"x_start\": [solver.Value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.Value(y_starts[i]) for i in used],\n",
" \"x_start\": [solver.value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.value(y_starts[i]) for i in used],\n",
" \"item_width\": [item_widths[i] for i in used],\n",
" \"item_height\": [item_heights[i] for i in used],\n",
" \"x_end\": [solver.Value(x_ends[i]) for i in used],\n",
" \"y_end\": [solver.Value(y_ends[i]) for i in used],\n",
" \"x_end\": [solver.value(x_ends[i]) for i in used],\n",
" \"y_end\": [solver.value(y_ends[i]) for i in used],\n",
" \"item_value\": [item_values[i] for i in used],\n",
" }\n",
" )\n",
" print(data)\n",
"\n",
"\n",
"def solve_with_duplicate_optional_items(data, max_height, max_width):\n",
" \"\"\"Solve the problem by building 2 optional items (rotated or not) for each item.\"\"\"\n",
"def solve_with_duplicate_optional_items(\n",
" data: pd.Series, max_height: int, max_width: int\n",
"):\n",
" \"\"\"solve the problem by building 2 optional items (rotated or not) for each item.\"\"\"\n",
" # Derived data (expanded to individual items).\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
@@ -272,42 +281,42 @@
"\n",
" for i in range(num_items):\n",
" ## Is the item used?\n",
" is_used.append(model.NewBoolVar(f\"is_used{i}\"))\n",
" is_used.append(model.new_bool_var(f\"is_used{i}\"))\n",
"\n",
" ## Item coordinates.\n",
" x_starts.append(\n",
" model.NewIntVar(0, max_width - int(item_widths[i]), f\"x_start{i}\")\n",
" model.new_int_var(0, max_width - int(item_widths[i]), f\"x_start{i}\")\n",
" )\n",
" y_starts.append(\n",
" model.NewIntVar(0, max_height - int(item_heights[i]), f\"y_start{i}\")\n",
" model.new_int_var(0, max_height - int(item_heights[i]), f\"y_start{i}\")\n",
" )\n",
"\n",
" ## Interval variables.\n",
" x_intervals.append(\n",
" model.NewOptionalFixedSizeIntervalVar(\n",
" model.new_optional_fixed_size_interval_var(\n",
" x_starts[i], item_widths[i], is_used[i], f\"x_interval{i}\"\n",
" )\n",
" )\n",
" y_intervals.append(\n",
" model.NewOptionalFixedSizeIntervalVar(\n",
" model.new_optional_fixed_size_interval_var(\n",
" y_starts[i], item_heights[i], is_used[i], f\"y_interval{i}\"\n",
" )\n",
" )\n",
" # Unused boxes are fixed at (0.0).\n",
" model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n",
" model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())\n",
" model.add(x_starts[i] == 0).only_enforce_if(~is_used[i])\n",
" model.add(y_starts[i] == 0).only_enforce_if(~is_used[i])\n",
"\n",
" # Constraints.\n",
"\n",
" ## Only one of non-rotated/rotated pair can be used.\n",
" for i in range(num_data_items):\n",
" model.Add(is_used[i] + is_used[i + num_data_items] <= 1)\n",
" model.add(is_used[i] + is_used[i + num_data_items] <= 1)\n",
"\n",
" ## 2D no overlap.\n",
" model.AddNoOverlap2D(x_intervals, y_intervals)\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" ## Objective.\n",
" model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values))\n",
" model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values))\n",
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
@@ -315,32 +324,32 @@
" with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n",
" used = {i for i in range(num_items) if solver.boolean_value(is_used[i])}\n",
" data = pd.DataFrame(\n",
" {\n",
" \"x_start\": [solver.Value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.Value(y_starts[i]) for i in used],\n",
" \"x_start\": [solver.value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.value(y_starts[i]) for i in used],\n",
" \"item_width\": [item_widths[i] for i in used],\n",
" \"item_height\": [item_heights[i] for i in used],\n",
" \"x_end\": [solver.Value(x_starts[i]) + item_widths[i] for i in used],\n",
" \"y_end\": [solver.Value(y_starts[i]) + item_heights[i] for i in used],\n",
" \"x_end\": [solver.value(x_starts[i]) + item_widths[i] for i in used],\n",
" \"y_end\": [solver.value(y_starts[i]) + item_heights[i] for i in used],\n",
" \"item_value\": [item_values[i] for i in used],\n",
" }\n",
" )\n",
" print(data)\n",
"\n",
"\n",
"def solve_with_rotations(data, max_height, max_width):\n",
" \"\"\"Solve the problem by rotating items.\"\"\"\n",
"def solve_with_rotations(data: pd.Series, max_height: int, max_width: int):\n",
" \"\"\"solve the problem by rotating items.\"\"\"\n",
" # Derived data (expanded to individual items).\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
@@ -369,25 +378,29 @@
" for i in range(num_items):\n",
" sizes = [0, int(item_widths[i]), int(item_heights[i])]\n",
" # X coordinates.\n",
" x_starts.append(model.NewIntVar(0, max_width, f\"x_start{i}\"))\n",
" x_starts.append(model.new_int_var(0, max_width, f\"x_start{i}\"))\n",
" x_sizes.append(\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"x_size{i}\")\n",
" model.new_int_var_from_domain(\n",
" cp_model.Domain.FromValues(sizes), f\"x_size{i}\"\n",
" )\n",
" )\n",
" x_ends.append(model.NewIntVar(0, max_width, f\"x_end{i}\"))\n",
" x_ends.append(model.new_int_var(0, max_width, f\"x_end{i}\"))\n",
"\n",
" # Y coordinates.\n",
" y_starts.append(model.NewIntVar(0, max_height, f\"y_start{i}\"))\n",
" y_starts.append(model.new_int_var(0, max_height, f\"y_start{i}\"))\n",
" y_sizes.append(\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"y_size{i}\")\n",
" model.new_int_var_from_domain(\n",
" cp_model.Domain.FromValues(sizes), f\"y_size{i}\"\n",
" )\n",
" )\n",
" y_ends.append(model.NewIntVar(0, max_height, f\"y_end{i}\"))\n",
" y_ends.append(model.new_int_var(0, max_height, f\"y_end{i}\"))\n",
"\n",
" ## Interval variables\n",
" x_intervals.append(\n",
" model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i], f\"x_interval{i}\")\n",
" model.new_interval_var(x_starts[i], x_sizes[i], x_ends[i], f\"x_interval{i}\")\n",
" )\n",
" y_intervals.append(\n",
" model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i], f\"y_interval{i}\")\n",
" model.new_interval_var(y_starts[i], y_sizes[i], y_ends[i], f\"y_interval{i}\")\n",
" )\n",
"\n",
" # is_used[i] == True if and only if item i is selected.\n",
@@ -397,34 +410,34 @@
"\n",
" ## for each item, decide is unselected, no_rotation, rotated.\n",
" for i in range(num_items):\n",
" not_selected = model.NewBoolVar(f\"not_selected_{i}\")\n",
" no_rotation = model.NewBoolVar(f\"no_rotation_{i}\")\n",
" rotated = model.NewBoolVar(f\"rotated_{i}\")\n",
" not_selected = model.new_bool_var(f\"not_selected_{i}\")\n",
" no_rotation = model.new_bool_var(f\"no_rotation_{i}\")\n",
" rotated = model.new_bool_var(f\"rotated_{i}\")\n",
"\n",
" ### Exactly one state must be chosen.\n",
" model.AddExactlyOne(not_selected, no_rotation, rotated)\n",
" model.add_exactly_one(not_selected, no_rotation, rotated)\n",
"\n",
" ### Define height and width according to the state.\n",
" dim1 = item_widths[i]\n",
" dim2 = item_heights[i]\n",
" # Unused boxes are fixed at (0.0).\n",
" model.Add(x_sizes[i] == 0).OnlyEnforceIf(not_selected)\n",
" model.Add(y_sizes[i] == 0).OnlyEnforceIf(not_selected)\n",
" model.Add(x_starts[i] == 0).OnlyEnforceIf(not_selected)\n",
" model.Add(y_starts[i] == 0).OnlyEnforceIf(not_selected)\n",
" model.add(x_sizes[i] == 0).only_enforce_if(not_selected)\n",
" model.add(y_sizes[i] == 0).only_enforce_if(not_selected)\n",
" model.add(x_starts[i] == 0).only_enforce_if(not_selected)\n",
" model.add(y_starts[i] == 0).only_enforce_if(not_selected)\n",
" # Sizes are fixed by the rotation.\n",
" model.Add(x_sizes[i] == dim1).OnlyEnforceIf(no_rotation)\n",
" model.Add(y_sizes[i] == dim2).OnlyEnforceIf(no_rotation)\n",
" model.Add(x_sizes[i] == dim2).OnlyEnforceIf(rotated)\n",
" model.Add(y_sizes[i] == dim1).OnlyEnforceIf(rotated)\n",
" model.add(x_sizes[i] == dim1).only_enforce_if(no_rotation)\n",
" model.add(y_sizes[i] == dim2).only_enforce_if(no_rotation)\n",
" model.add(x_sizes[i] == dim2).only_enforce_if(rotated)\n",
" model.add(y_sizes[i] == dim1).only_enforce_if(rotated)\n",
"\n",
" is_used.append(not_selected.Not())\n",
" is_used.append(~not_selected)\n",
"\n",
" ## 2D no overlap.\n",
" model.AddNoOverlap2D(x_intervals, y_intervals)\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" # Objective.\n",
" model.Maximize(cp_model.LinearExpr.WeightedSum(is_used, item_values))\n",
" model.maximize(cp_model.LinearExpr.weighted_sum(is_used, item_values))\n",
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
@@ -432,24 +445,24 @@
" with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}\n",
" used = {i for i in range(num_items) if solver.boolean_value(is_used[i])}\n",
" data = pd.DataFrame(\n",
" {\n",
" \"x_start\": [solver.Value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.Value(y_starts[i]) for i in used],\n",
" \"item_width\": [solver.Value(x_sizes[i]) for i in used],\n",
" \"item_height\": [solver.Value(y_sizes[i]) for i in used],\n",
" \"x_end\": [solver.Value(x_ends[i]) for i in used],\n",
" \"y_end\": [solver.Value(y_ends[i]) for i in used],\n",
" \"x_start\": [solver.value(x_starts[i]) for i in used],\n",
" \"y_start\": [solver.value(y_starts[i]) for i in used],\n",
" \"item_width\": [solver.value(x_sizes[i]) for i in used],\n",
" \"item_height\": [solver.value(y_sizes[i]) for i in used],\n",
" \"x_end\": [solver.value(x_ends[i]) for i in used],\n",
" \"y_end\": [solver.value(y_ends[i]) for i in used],\n",
" \"item_value\": [item_values[i] for i in used],\n",
" }\n",
" )\n",
@@ -457,7 +470,7 @@
"\n",
"\n",
"def main(_):\n",
" \"\"\"Solve the problem with all models.\"\"\"\n",
" \"\"\"solve the problem with all models.\"\"\"\n",
" data, max_height, max_width = build_data()\n",
" if _MODEL.value == \"duplicate\":\n",
" solve_with_duplicate_items(data, max_height, max_width)\n",

View File

@@ -252,7 +252,7 @@
"\n",
"\n",
"def solve_boolean_model(model, hint):\n",
" \"\"\"Solve the given model.\"\"\"\n",
" \"\"\"solve the given model.\"\"\"\n",
"\n",
" print(\"Solving using the Boolean model\")\n",
" # Model data\n",
@@ -275,73 +275,73 @@
" # Create the variables\n",
" for t in all_tasks:\n",
" for p in all_pods:\n",
" assign[t, p] = model.NewBoolVar(f\"assign_{t}_{p}\")\n",
" possible[t, p] = model.NewBoolVar(f\"possible_{t}_{p}\")\n",
" assign[t, p] = model.new_bool_var(f\"assign_{t}_{p}\")\n",
" possible[t, p] = model.new_bool_var(f\"possible_{t}_{p}\")\n",
"\n",
" # active[p] indicates if pod p is active.\n",
" active = [model.NewBoolVar(f\"active_{p}\") for p in all_pods]\n",
" active = [model.new_bool_var(f\"active_{p}\") for p in all_pods]\n",
"\n",
" # Each task is done on exactly one pod.\n",
" for t in all_tasks:\n",
" model.AddExactlyOne([assign[t, p] for p in all_pods])\n",
" model.add_exactly_one([assign[t, p] for p in all_pods])\n",
"\n",
" # Total tasks assigned to one pod cannot exceed cycle time.\n",
" for p in all_pods:\n",
" model.Add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)\n",
" model.add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)\n",
"\n",
" # Maintain the possible variables:\n",
" # possible at pod p -> possible at any pod after p\n",
" for t in all_tasks:\n",
" for p in range(num_pods - 1):\n",
" model.AddImplication(possible[t, p], possible[t, p + 1])\n",
" model.add_implication(possible[t, p], possible[t, p + 1])\n",
"\n",
" # Link possible and active variables.\n",
" for t in all_tasks:\n",
" for p in all_pods:\n",
" model.AddImplication(assign[t, p], possible[t, p])\n",
" model.add_implication(assign[t, p], possible[t, p])\n",
" if p > 1:\n",
" model.AddImplication(assign[t, p], possible[t, p - 1].Not())\n",
" model.add_implication(assign[t, p], ~possible[t, p - 1])\n",
"\n",
" # Precedences.\n",
" for before, after in precedences:\n",
" for p in range(1, num_pods):\n",
" model.AddImplication(assign[before, p], possible[after, p - 1].Not())\n",
" model.add_implication(assign[before, p], ~possible[after, p - 1])\n",
"\n",
" # Link active variables with the assign one.\n",
" for p in all_pods:\n",
" all_assign_vars = [assign[t, p] for t in all_tasks]\n",
" for a in all_assign_vars:\n",
" model.AddImplication(a, active[p])\n",
" model.AddBoolOr(all_assign_vars + [active[p].Not()])\n",
" model.add_implication(a, active[p])\n",
" model.add_bool_or(all_assign_vars + [~active[p]])\n",
"\n",
" # Force pods to be contiguous. This is critical to get good lower bounds\n",
" # on the objective, even if it makes feasibility harder.\n",
" for p in range(1, num_pods):\n",
" model.AddImplication(active[p - 1].Not(), active[p].Not())\n",
" model.add_implication(~active[p - 1], ~active[p])\n",
" for t in all_tasks:\n",
" model.AddImplication(active[p].Not(), possible[t, p - 1])\n",
" model.add_implication(~active[p], possible[t, p - 1])\n",
"\n",
" # Objective.\n",
" model.Minimize(sum(active))\n",
" model.minimize(sum(active))\n",
"\n",
" # Add search hinting from the greedy solution.\n",
" # add search hinting from the greedy solution.\n",
" for t in all_tasks:\n",
" model.AddHint(assign[t, hint[t]], 1)\n",
" model.add_hint(assign[t, hint[t]], 1)\n",
"\n",
" if _OUTPUT_PROTO.value:\n",
" print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n",
" model.ExportToFile(_OUTPUT_PROTO.value)\n",
" model.export_to_file(_OUTPUT_PROTO.value)\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" solver.Solve(model)\n",
" solver.solve(model)\n",
"\n",
"\n",
"def solve_scheduling_model(model, hint):\n",
" \"\"\"Solve the given model using a cumutive model.\"\"\"\n",
" \"\"\"solve the given model using a cumutive model.\"\"\"\n",
"\n",
" print(\"Solving using the scheduling model\")\n",
" # Model data\n",
@@ -358,47 +358,49 @@
" # pod[t] indicates on which pod the task is performed.\n",
" pods = {}\n",
" for t in all_tasks:\n",
" pods[t] = model.NewIntVar(0, num_pods - 1, f\"pod_{t}\")\n",
" pods[t] = model.new_int_var(0, num_pods - 1, f\"pod_{t}\")\n",
"\n",
" # Create the variables\n",
" intervals = []\n",
" demands = []\n",
" for t in all_tasks:\n",
" interval = model.NewFixedSizeIntervalVar(pods[t], 1, \"\")\n",
" interval = model.new_fixed_size_interval_var(pods[t], 1, \"\")\n",
" intervals.append(interval)\n",
" demands.append(durations[t])\n",
"\n",
" # Add terminating interval as the objective.\n",
" obj_var = model.NewIntVar(1, num_pods, \"obj_var\")\n",
" obj_size = model.NewIntVar(1, num_pods, \"obj_duration\")\n",
" obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1, \"obj_interval\")\n",
" # add terminating interval as the objective.\n",
" obj_var = model.new_int_var(1, num_pods, \"obj_var\")\n",
" obj_size = model.new_int_var(1, num_pods, \"obj_duration\")\n",
" obj_interval = model.new_interval_var(\n",
" obj_var, obj_size, num_pods + 1, \"obj_interval\"\n",
" )\n",
" intervals.append(obj_interval)\n",
" demands.append(cycle_time)\n",
"\n",
" # Cumulative constraint.\n",
" model.AddCumulative(intervals, demands, cycle_time)\n",
" model.add_cumulative(intervals, demands, cycle_time)\n",
"\n",
" # Precedences.\n",
" for before, after in precedences:\n",
" model.Add(pods[after] >= pods[before])\n",
" model.add(pods[after] >= pods[before])\n",
"\n",
" # Objective.\n",
" model.Minimize(obj_var)\n",
" model.minimize(obj_var)\n",
"\n",
" # Add search hinting from the greedy solution.\n",
" # add search hinting from the greedy solution.\n",
" for t in all_tasks:\n",
" model.AddHint(pods[t], hint[t])\n",
" model.add_hint(pods[t], hint[t])\n",
"\n",
" if _OUTPUT_PROTO.value:\n",
" print(f\"Writing proto to{_OUTPUT_PROTO.value}\")\n",
" model.ExportToFile(_OUTPUT_PROTO.value)\n",
" model.export_to_file(_OUTPUT_PROTO.value)\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" solver.Solve(model)\n",
" solver.solve(model)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -207,11 +207,13 @@
" RunLinearExampleNaturalLanguageAPI(\"GLPK_LP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"CLP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"PDLP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"XPRESS_LP\")\n",
"\n",
" RunLinearExampleCppStyleAPI(\"GLOP\")\n",
" RunLinearExampleCppStyleAPI(\"GLPK_LP\")\n",
" RunLinearExampleCppStyleAPI(\"CLP\")\n",
" RunLinearExampleCppStyleAPI(\"PDLP\")\n",
" RunLinearExampleCppStyleAPI(\"XPRESS_LP\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -118,8 +118,8 @@
" before_rank = position_to_rank[(x, y, z)]\n",
" after_index = index_map[(x + dx, y + dy, z + dz)]\n",
" after_rank = position_to_rank[(x + dx, y + dy, z + dz)]\n",
" move_literal = model.NewBoolVar(\"\")\n",
" model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal)\n",
" move_literal = model.new_bool_var(\"\")\n",
" model.add(after_rank == before_rank + 1).only_enforce_if(move_literal)\n",
" arcs.append((before_index, after_index, move_literal))\n",
"\n",
"\n",
@@ -147,13 +147,13 @@
" position_to_rank = {}\n",
"\n",
" for coord in reverse_map:\n",
" position_to_rank[coord] = model.NewIntVar(0, counter - 1, f\"rank_{coord}\")\n",
" position_to_rank[coord] = model.new_int_var(0, counter - 1, f\"rank_{coord}\")\n",
"\n",
" # Path constraints.\n",
" model.Add(position_to_rank[start] == 0)\n",
" model.Add(position_to_rank[end] == counter - 1)\n",
" model.add(position_to_rank[start] == 0)\n",
" model.add(position_to_rank[end] == counter - 1)\n",
" for i in range(len(boxes) - 1):\n",
" model.Add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]])\n",
" model.add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]])\n",
"\n",
" # Circuit constraint: visit all blocks exactly once, and maintains the rank\n",
" # of each block.\n",
@@ -184,18 +184,18 @@
" arcs.append((index_map[end], index_map[start], True))\n",
"\n",
" # Adds the circuit (hamiltonian path) constraint.\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # Exports the model if required.\n",
" if output_proto:\n",
" model.ExportToFile(output_proto)\n",
" model.export_to_file(output_proto)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" result = solver.Solve(model)\n",
" result = solver.solve(model)\n",
"\n",
" # Prints solution.\n",
" if result == cp_model.OPTIMAL:\n",
@@ -204,7 +204,7 @@
" for y in range(size):\n",
" for z in range(size):\n",
" position = (x, y, z)\n",
" rank = solver.Value(position_to_rank[position])\n",
" rank = solver.value(position_to_rank[position])\n",
" msg = f\"({x}, {y}, {z})\"\n",
" if position == start:\n",
" msg += \" [start]\"\n",

View File

@@ -0,0 +1,253 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# memory_layout_and_infeasibility_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/examples/memory_layout_and_infeasibility_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/examples/python/memory_layout_and_infeasibility_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves the memory allocation problem, and returns a minimal set of demands to explain infeasibility.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from collections.abc import Sequence\n",
"from typing import List\n",
"\n",
"from ortools.sat.colab import flags\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"_OUTPUT_PROTO = flags.define_string(\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.define_string(\n",
" \"params\", \"num_workers:1,linearization_level:2\", \"Sat solver parameters.\"\n",
")\n",
"\n",
"\n",
"# Input of the problem.\n",
"DEMANDS = [\n",
" [1578, 1583, 43008, 1],\n",
" [1588, 1589, 11264, 1],\n",
" [1590, 1595, 43008, 1],\n",
" [1583, 1588, 47872, 1],\n",
" [1589, 1590, 22848, 1],\n",
" [1586, 1590, 22848, 1],\n",
" [1591, 1594, 43008, 1],\n",
"]\n",
"CAPACITY = 98304\n",
"\n",
"\n",
"def solve_hard_model(output_proto: str, params: str) -> bool:\n",
" \"\"\"Solves the hard assignment model.\"\"\"\n",
" print(\"Solving the hard assignment model\")\n",
" model = cp_model.CpModel()\n",
"\n",
" x_intervals: List[cp_model.IntervalVar] = []\n",
" y_starts: List[cp_model.IntVar] = []\n",
" y_intervals: List[cp_model.IntervalVar] = []\n",
"\n",
" for start, end, demand, unused_alignment in DEMANDS:\n",
" x_interval = model.new_fixed_size_interval_var(start, end - start + 1, \"\")\n",
" y_start = model.new_int_var(0, CAPACITY - demand, \"\")\n",
" y_interval = model.new_fixed_size_interval_var(y_start, demand, \"\")\n",
"\n",
" x_intervals.append(x_interval)\n",
" y_starts.append(y_start)\n",
" y_intervals.append(y_interval)\n",
"\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" if output_proto:\n",
" model.export_to_file(output_proto)\n",
"\n",
" solver = cp_model.CpSolver()\n",
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" status = solver.solve(model)\n",
" print(solver.response_stats())\n",
"\n",
" if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n",
" for index, start in enumerate(y_starts):\n",
" print(f\"task {index} buffer starts at {solver.value(start)}\")\n",
"\n",
" return status != cp_model.INFEASIBLE\n",
"\n",
"\n",
"def solve_soft_model_with_assumptions() -> None:\n",
" \"\"\"Solves the soft model using assumptions.\"\"\"\n",
" print(\"Solving the soft model using assumptions\")\n",
"\n",
" model = cp_model.CpModel()\n",
"\n",
" presences: List[cp_model.IntVar] = []\n",
" x_intervals: List[cp_model.IntervalVar] = []\n",
" y_starts: List[cp_model.IntVar] = []\n",
" y_intervals: List[cp_model.IntervalVar] = []\n",
"\n",
" for start, end, demand, unused_alignment in DEMANDS:\n",
" presence = model.new_bool_var(\"\")\n",
" x_interval = model.new_optional_fixed_size_interval_var(\n",
" start, end - start + 1, presence, \"\"\n",
" )\n",
" y_start = model.new_int_var(0, CAPACITY - demand, \"\")\n",
" y_interval = model.new_optional_fixed_size_interval_var(\n",
" y_start, demand, presence, \"\"\n",
" )\n",
"\n",
" presences.append(presence)\n",
" x_intervals.append(x_interval)\n",
" y_starts.append(y_start)\n",
" y_intervals.append(y_interval)\n",
"\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
" model.add_assumptions(presences)\n",
"\n",
" solver = cp_model.CpSolver()\n",
" status = solver.solve(model)\n",
" print(solver.response_stats())\n",
" if status == cp_model.INFEASIBLE:\n",
" # The list actually contains the indices of the variables sufficient to\n",
" # explain infeasibility.\n",
" infeasible_variable_indices = solver.sufficient_assumptions_for_infeasibility()\n",
" infeasible_variable_indices_set = set(infeasible_variable_indices)\n",
"\n",
" for index, presence in enumerate(presences):\n",
" if presence.index in infeasible_variable_indices_set:\n",
" print(f\"using task {index} is sufficient to explain infeasibility\")\n",
"\n",
"\n",
"def solve_soft_model_with_maximization(params: str) -> None:\n",
" \"\"\"Solves the soft model using maximization.\"\"\"\n",
" print(\"Solving the soft model using minimization\")\n",
"\n",
" model = cp_model.CpModel()\n",
"\n",
" presences: List[cp_model.IntVar] = []\n",
" x_intervals: List[cp_model.IntervalVar] = []\n",
" y_starts: List[cp_model.IntVar] = []\n",
" y_intervals: List[cp_model.IntervalVar] = []\n",
"\n",
" for start, end, demand, unused_alignment in DEMANDS:\n",
" presence = model.new_bool_var(\"\")\n",
" x_interval = model.new_optional_fixed_size_interval_var(\n",
" start, end - start + 1, presence, \"\"\n",
" )\n",
" y_start = model.new_int_var(0, CAPACITY - demand, \"\")\n",
" y_interval = model.new_optional_fixed_size_interval_var(\n",
" y_start, demand, presence, \"\"\n",
" )\n",
"\n",
" presences.append(presence)\n",
" x_intervals.append(x_interval)\n",
" y_starts.append(y_start)\n",
" y_intervals.append(y_interval)\n",
"\n",
" model.add_no_overlap_2d(x_intervals, y_intervals)\n",
"\n",
" model.maximize(sum(presences))\n",
"\n",
" solver = cp_model.CpSolver()\n",
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" status = solver.solve(model)\n",
" print(solver.response_stats())\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" for index, presence in enumerate(presences):\n",
" if not solver.boolean_value(presence):\n",
" print(f\"task {index} does not fit\")\n",
" else:\n",
" print(f\"task {index} buffer starts at {solver.value(y_starts[index])}\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" if not solve_hard_model(_OUTPUT_PROTO.value, _PARAMS.value):\n",
" solve_soft_model_with_assumptions()\n",
" solve_soft_model_with_maximization(_PARAMS.value)\n",
"\n",
"\n",
"main()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -299,68 +299,68 @@
"\n",
" start = None\n",
" if previous_end is None:\n",
" start = model.NewIntVar(start_work, horizon, f\"start{suffix}\")\n",
" start = model.new_int_var(start_work, horizon, f\"start{suffix}\")\n",
" orders_sequence_of_events[order_id].append(\n",
" (start, f\"start{suffix}\")\n",
" )\n",
" else:\n",
" start = previous_end\n",
"\n",
" size = model.NewIntVar(\n",
" size = model.new_int_var(\n",
" task.min_duration, task.max_duration, f\"size{suffix}\"\n",
" )\n",
" end = None\n",
" if task == recipe.tasks[-1]:\n",
" # The order must end after the due_date. Ideally, exactly at the\n",
" # due_date.\n",
" tardiness = model.NewIntVar(0, horizon - due_date, f\"end{suffix}\")\n",
" tardiness = model.new_int_var(0, horizon - due_date, f\"end{suffix}\")\n",
" end = tardiness + due_date\n",
"\n",
" # Store the end_var for the objective.\n",
" tardiness_vars.append(tardiness)\n",
" else:\n",
" end = model.NewIntVar(start_work, horizon, f\"end{suffix}\")\n",
" end = model.new_int_var(start_work, horizon, f\"end{suffix}\")\n",
" orders_sequence_of_events[order_id].append((end, f\"end{suffix}\"))\n",
" previous_end = end\n",
"\n",
" # Per resource copy.\n",
" presence_literals = []\n",
" for resource in resource_list_by_skill_name[skill_name]:\n",
" presence = model.NewBoolVar(f\"presence{suffix}_{resource.name}\")\n",
" copy = model.NewOptionalIntervalVar(\n",
" presence = model.new_bool_var(f\"presence{suffix}_{resource.name}\")\n",
" copy = model.new_optional_interval_var(\n",
" start, size, end, presence, f\"interval{suffix}_{resource.name}\"\n",
" )\n",
" interval_list_by_resource_name[resource.name].append(copy)\n",
" presence_literals.append(presence)\n",
"\n",
" # Only one copy will be performed.\n",
" model.AddExactlyOne(presence_literals)\n",
" model.add_exactly_one(presence_literals)\n",
"\n",
" # Create resource constraints.\n",
" for resource in resources:\n",
" intervals = interval_list_by_resource_name[resource.name]\n",
" if resource.capacity == 1:\n",
" model.AddNoOverlap(intervals)\n",
" model.add_no_overlap(intervals)\n",
" else:\n",
" model.AddCumulative(intervals, [1] * len(intervals), resource.capacity)\n",
" model.add_cumulative(intervals, [1] * len(intervals), resource.capacity)\n",
"\n",
" # The objective is to minimize the sum of the tardiness values of each jobs.\n",
" # The tardiness is difference between the end time of an order and its\n",
" # due date.\n",
" model.Minimize(sum(tardiness_vars))\n",
" model.minimize(sum(tardiness_vars))\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" for order_id in sorted_orders:\n",
" print(f\"{order_id}:\")\n",
" for time_expr, event_id in orders_sequence_of_events[order_id]:\n",
" time = solver.Value(time_expr)\n",
" time = solver.value(time_expr)\n",
" print(f\" {event_id} at {time // 60}:{time % 60:02}\")\n",
"\n",
"\n",

View File

@@ -94,16 +94,17 @@
"class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, queens):\n",
" def __init__(self, queens: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__queens = queens\n",
" self.__solution_count = 0\n",
" self.__start_time = time.time()\n",
"\n",
" def SolutionCount(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" current_time = time.time()\n",
" print(\n",
" \"Solution %i, time = %f s\"\n",
@@ -114,7 +115,7 @@
" all_queens = range(len(self.__queens))\n",
" for i in all_queens:\n",
" for j in all_queens:\n",
" if self.Value(self.__queens[j]) == i:\n",
" if self.value(self.__queens[j]) == i:\n",
" # There is a queen in column j, row i.\n",
" print(\"Q\", end=\" \")\n",
" else:\n",
@@ -131,41 +132,43 @@
"\n",
" ### Creates the variables.\n",
" # The array index is the column, and the value is the row.\n",
" queens = [model.NewIntVar(0, board_size - 1, \"x%i\" % i) for i in range(board_size)]\n",
" queens = [\n",
" model.new_int_var(0, board_size - 1, \"x%i\" % i) for i in range(board_size)\n",
" ]\n",
"\n",
" ### Creates the constraints.\n",
"\n",
" # All columns must be different because the indices of queens are all\n",
" # different, so we just add the all different constraint on the rows.\n",
" model.AddAllDifferent(queens)\n",
" model.add_all_different(queens)\n",
"\n",
" # No two queens can be on the same diagonal.\n",
" diag1 = []\n",
" diag2 = []\n",
" for i in range(board_size):\n",
" q1 = model.NewIntVar(0, 2 * board_size, \"diag1_%i\" % i)\n",
" q2 = model.NewIntVar(-board_size, board_size, \"diag2_%i\" % i)\n",
" q1 = model.new_int_var(0, 2 * board_size, \"diag1_%i\" % i)\n",
" q2 = model.new_int_var(-board_size, board_size, \"diag2_%i\" % i)\n",
" diag1.append(q1)\n",
" diag2.append(q2)\n",
" model.Add(q1 == queens[i] + i)\n",
" model.Add(q2 == queens[i] - i)\n",
" model.AddAllDifferent(diag1)\n",
" model.AddAllDifferent(diag2)\n",
" model.add(q1 == queens[i] + i)\n",
" model.add(q2 == queens[i] - i)\n",
" model.add_all_different(diag1)\n",
" model.add_all_different(diag2)\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = NQueenSolutionPrinter(queens)\n",
" # Enumerate all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" # Solve.\n",
" solver.Solve(model, solution_printer)\n",
" # solve.\n",
" solver.solve(model, solution_printer)\n",
"\n",
" print()\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - solutions found : %i\" % solution_printer.SolutionCount())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
" print(\" - solutions found : %i\" % solution_printer.solution_count)\n",
"\n",
"\n",
"main()\n",

View File

@@ -0,0 +1,264 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# pentominoes_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/examples/pentominoes_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/examples/python/pentominoes_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Example to solves a pentomino paving problem.\n",
"\n",
"Given a subset of n different pentomino, the problem is to pave a square of\n",
"size 5 x n. The problem is reduced to an exact set cover problem and encoded\n",
"as a linear boolean problem.\n",
"\n",
"This problem comes from the game Katamino:\n",
"http://boardgamegeek.com/boardgame/6931/katamino\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from collections.abc import Sequence\n",
"from typing import Dict, List\n",
"\n",
"from ortools.sat.colab import flags\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"_PARAMS = flags.define_string(\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:false,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"\n",
"_PIECES = flags.define_string(\n",
" \"pieces\", \"FILNPTUVWXYZ\", \"The subset of pieces to consider.\"\n",
")\n",
"\n",
"\n",
"def is_one(mask: List[List[int]], x: int, y: int, orientation: int) -> bool:\n",
" \"\"\"Returns true if the oriented piece is 1 at position [i][j].\n",
"\n",
" The 3 bits in orientation respectively mean: transposition, symmetry by\n",
" x axis, symmetry by y axis.\n",
"\n",
" Args:\n",
" mask: The shape of the piece.\n",
" x: position.\n",
" y: position.\n",
" orientation: between 0 and 7.\n",
" \"\"\"\n",
" if orientation & 1:\n",
" tmp: int = x\n",
" x = y\n",
" y = tmp\n",
" if orientation & 2:\n",
" x = len(mask[0]) - 1 - x\n",
" if orientation & 4:\n",
" y = len(mask) - 1 - y\n",
" return mask[y][x] == 1\n",
"\n",
"\n",
"def get_height(mask: List[List[int]], orientation: int) -> int:\n",
" if orientation & 1:\n",
" return len(mask[0])\n",
" return len(mask)\n",
"\n",
"\n",
"def get_width(mask: List[List[int]], orientation: int) -> int:\n",
" if orientation & 1:\n",
" return len(mask)\n",
" return len(mask[0])\n",
"\n",
"\n",
"def orientation_is_redundant(mask: List[List[int]], orientation: int) -> bool:\n",
" \"\"\"Checks if the current rotated figure is the same as a previous rotation.\"\"\"\n",
" size_i: int = get_width(mask, orientation)\n",
" size_j: int = get_height(mask, orientation)\n",
" for o in range(orientation):\n",
" if size_i != get_width(mask, o):\n",
" continue\n",
" if size_j != get_height(mask, o):\n",
" continue\n",
"\n",
" is_the_same: bool = True\n",
" for k in range(size_i):\n",
" if not is_the_same:\n",
" break\n",
" for l in range(size_j):\n",
" if not is_the_same:\n",
" break\n",
" if is_one(mask, k, l, orientation) != is_one(mask, k, l, o):\n",
" is_the_same = False\n",
" if is_the_same:\n",
" return True\n",
" return False\n",
"\n",
"\n",
"def generate_and_solve_problem(pieces: Dict[str, List[List[int]]]) -> None:\n",
" \"\"\"Solves the pentominoes problem.\"\"\"\n",
" box_width = len(pieces)\n",
" box_height = 5\n",
"\n",
" model = cp_model.CpModel()\n",
" position_to_variables: List[List[List[cp_model.IntVar]]] = [\n",
" [[] for _ in range(box_width)] for _ in range(box_height)\n",
" ]\n",
"\n",
" for name, mask in pieces.items():\n",
" all_position_variables = []\n",
" for orientation in range(8):\n",
" if orientation_is_redundant(mask, orientation):\n",
" continue\n",
" piece_width = get_width(mask, orientation)\n",
" piece_height = get_height(mask, orientation)\n",
" for i in range(box_width - piece_width + 1):\n",
" for j in range(box_height - piece_height + 1):\n",
" v = model.new_bool_var(name)\n",
" all_position_variables.append(v)\n",
" for k in range(piece_width):\n",
" for l in range(piece_height):\n",
" if is_one(mask, k, l, orientation):\n",
" position_to_variables[j + l][i + k].append(v)\n",
"\n",
" # Only one combination is selected.\n",
" model.add_exactly_one(all_position_variables)\n",
"\n",
" for one_column in position_to_variables:\n",
" for all_pieces_in_one_position in one_column:\n",
" model.add_exactly_one(all_pieces_in_one_position)\n",
"\n",
" # Solve the model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" status = solver.solve(model)\n",
"\n",
" print(\n",
" f\"Problem {_PIECES.value} solved in {solver.wall_time}s with status\"\n",
" f\" {solver.status_name(status)}\"\n",
" )\n",
"\n",
" # Print the solution.\n",
" if status == cp_model.OPTIMAL:\n",
" for y in range(box_height):\n",
" line = \"\"\n",
" for x in range(box_width):\n",
" for v in position_to_variables[y][x]:\n",
" if solver.BooleanValue(v):\n",
" line += v.name\n",
" break\n",
" print(line)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" # Pieces are stored in a matrix. mask[height][width]\n",
" pieces: Dict[str, List[List[int]]] = {\n",
" \"F\": [[0, 1, 1], [1, 1, 0], [0, 1, 0]],\n",
" \"I\": [[1, 1, 1, 1, 1]],\n",
" \"L\": [[1, 1, 1, 1], [1, 0, 0, 0]],\n",
" \"N\": [[1, 1, 1, 0], [0, 0, 1, 1]],\n",
" \"P\": [[1, 1, 1], [1, 1, 0]],\n",
" \"T\": [[1, 1, 1], [0, 1, 0], [0, 1, 0]],\n",
" \"U\": [[1, 0, 1], [1, 1, 1]],\n",
" \"V\": [[1, 0, 0], [1, 0, 0], [1, 1, 1]],\n",
" \"W\": [[1, 0, 0], [1, 1, 0], [0, 1, 1]],\n",
" \"X\": [[0, 1, 0], [1, 1, 1], [0, 1, 0]],\n",
" \"Y\": [[1, 1, 1, 1], [0, 1, 0, 0]],\n",
" \"Z\": [[1, 1, 0], [0, 1, 0], [0, 1, 1]],\n",
" }\n",
" selected_pieces: Dict[str, List[List[int]]] = {}\n",
" for p in _PIECES.value:\n",
" if p not in pieces:\n",
" print(f\"Piece {p} not found in the list of pieces\")\n",
" return\n",
" selected_pieces[p] = pieces[p]\n",
" generate_and_solve_problem(selected_pieces)\n",
"\n",
"\n",
"main()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -139,14 +139,19 @@
"\n",
"\n",
"# Create a console solution printer.\n",
"def print_solution(solver, visited_nodes, used_arcs, num_nodes):\n",
"def print_solution(\n",
" solver: cp_model.CpSolver,\n",
" visited_nodes: list[cp_model.IntVar],\n",
" used_arcs: dict[tuple[int, int], cp_model.IntVar],\n",
" num_nodes: int,\n",
") -> None:\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" # Display dropped nodes.\n",
" dropped_nodes = \"Dropped nodes:\"\n",
" for i in range(num_nodes):\n",
" if i == 0:\n",
" continue\n",
" if not solver.BooleanValue(visited_nodes[i]):\n",
" if not solver.boolean_value(visited_nodes[i]):\n",
" dropped_nodes += f\" {i}({VISIT_VALUES[i]})\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
@@ -162,7 +167,7 @@
" for node in range(num_nodes):\n",
" if node == current_node:\n",
" continue\n",
" if solver.BooleanValue(used_arcs[current_node, node]):\n",
" if solver.boolean_value(used_arcs[current_node, node]):\n",
" route_distance += DISTANCE_MATRIX[current_node][node]\n",
" current_node = node\n",
" if current_node == 0:\n",
@@ -170,7 +175,7 @@
" break\n",
" plan_output += f\" {current_node}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" plan_output += f\"Value collected: {value_collected}/{sum(VISIT_VALUES)}\\n\"\n",
" plan_output += f\"value collected: {value_collected}/{sum(VISIT_VALUES)}\\n\"\n",
" print(plan_output)\n",
"\n",
"\n",
@@ -191,8 +196,8 @@
" # Create the circuit constraint.\n",
" arcs = []\n",
" for i in all_nodes:\n",
" is_visited = model.NewBoolVar(f\"{i} is visited\")\n",
" arcs.append((i, i, is_visited.Not()))\n",
" is_visited = model.new_bool_var(f\"{i} is visited\")\n",
" arcs.append((i, i, ~is_visited))\n",
"\n",
" obj_vars.append(is_visited)\n",
" obj_coeffs.append(VISIT_VALUES[i])\n",
@@ -200,22 +205,22 @@
"\n",
" for j in all_nodes:\n",
" if i == j:\n",
" used_arcs[i, j] = is_visited.Not()\n",
" used_arcs[i, j] = ~is_visited\n",
" continue\n",
" arc_is_used = model.NewBoolVar(f\"{j} follows {i}\")\n",
" arc_is_used = model.new_bool_var(f\"{j} follows {i}\")\n",
" arcs.append((i, j, arc_is_used))\n",
"\n",
" obj_vars.append(arc_is_used)\n",
" obj_coeffs.append(-DISTANCE_MATRIX[i][j])\n",
" used_arcs[i, j] = arc_is_used\n",
"\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # Node 0 must be visited.\n",
" model.Add(visited_nodes[0] == 1)\n",
" model.add(visited_nodes[0] == 1)\n",
"\n",
" # limit the route distance\n",
" model.Add(\n",
" model.add(\n",
" sum(\n",
" used_arcs[i, j] * DISTANCE_MATRIX[i][j]\n",
" for i in all_nodes\n",
@@ -225,7 +230,7 @@
" )\n",
"\n",
" # Maximize visited node values minus the travelled distance.\n",
" model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
" model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
@@ -234,7 +239,7 @@
" solver.parameters.num_search_workers = 8\n",
" solver.parameters.log_search_progress = True\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
" if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n",
" print_solution(solver, visited_nodes, used_arcs, num_nodes)\n",
"\n",

View File

@@ -139,7 +139,13 @@
"\n",
"\n",
"# Create a console solution printer.\n",
"def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):\n",
"def print_solution(\n",
" solver: cp_model.CpSolver,\n",
" visited_nodes: dict[int, list[cp_model.IntVar]],\n",
" used_arcs: dict[int, dict[tuple[int, int], cp_model.IntVar]],\n",
" num_nodes: int,\n",
" num_vehicles: int,\n",
") -> None:\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" # Display dropped nodes.\n",
" dropped_nodes = \"Dropped nodes:\"\n",
@@ -147,7 +153,7 @@
" if node == 0:\n",
" continue\n",
" is_visited = sum(\n",
" [solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)]\n",
" [solver.boolean_value(visited_nodes[v][node]) for v in range(num_vehicles)]\n",
" )\n",
" if not is_visited:\n",
" dropped_nodes += f\" {node}({VISIT_VALUES[node]})\"\n",
@@ -168,7 +174,7 @@
" for node in range(num_nodes):\n",
" if node == current_node:\n",
" continue\n",
" if solver.BooleanValue(used_arcs[v][current_node, node]):\n",
" if solver.boolean_value(used_arcs[v][current_node, node]):\n",
" route_distance += DISTANCE_MATRIX[current_node][node]\n",
" current_node = node\n",
" if current_node == 0:\n",
@@ -176,12 +182,12 @@
" break\n",
" plan_output += f\" {current_node}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" plan_output += f\"Value collected: {value_collected}\\n\"\n",
" plan_output += f\"value collected: {value_collected}\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" total_value_collected += value_collected\n",
" print(f\"Total Distance: {total_distance}m\")\n",
" print(f\"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}\")\n",
" print(f\"Total value collected: {total_value_collected}/{sum(VISIT_VALUES)}\")\n",
"\n",
"\n",
"def prize_collecting_vrp():\n",
@@ -205,8 +211,8 @@
" used_arcs[v] = {}\n",
" arcs = []\n",
" for i in all_nodes:\n",
" is_visited = model.NewBoolVar(f\"{i} is visited\")\n",
" arcs.append((i, i, is_visited.Not()))\n",
" is_visited = model.new_bool_var(f\"{i} is visited\")\n",
" arcs.append((i, i, ~is_visited))\n",
"\n",
" obj_vars.append(is_visited)\n",
" obj_coeffs.append(VISIT_VALUES[i])\n",
@@ -214,22 +220,22 @@
"\n",
" for j in all_nodes:\n",
" if i == j:\n",
" used_arcs[v][i, j] = is_visited.Not()\n",
" used_arcs[v][i, j] = ~is_visited\n",
" continue\n",
" arc_is_used = model.NewBoolVar(f\"{j} follows {i}\")\n",
" arc_is_used = model.new_bool_var(f\"{j} follows {i}\")\n",
" arcs.append((i, j, arc_is_used))\n",
"\n",
" obj_vars.append(arc_is_used)\n",
" obj_coeffs.append(-DISTANCE_MATRIX[i][j])\n",
" used_arcs[v][i, j] = arc_is_used\n",
"\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # Node 0 must be visited.\n",
" model.Add(visited_nodes[v][0] == 1)\n",
" model.add(visited_nodes[v][0] == 1)\n",
"\n",
" # limit the route distance\n",
" model.Add(\n",
" model.add(\n",
" sum(\n",
" used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]\n",
" for i in all_nodes\n",
@@ -240,10 +246,10 @@
"\n",
" # Each node is visited at most once\n",
" for node in range(1, num_nodes):\n",
" model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)])\n",
" model.add_at_most_one([visited_nodes[v][node] for v in range(num_vehicles)])\n",
"\n",
" # Maximize visited node values minus the travelled distance.\n",
" model.Maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
" model.maximize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
@@ -251,7 +257,7 @@
" solver.parameters.max_time_in_seconds = 15.0\n",
" solver.parameters.log_search_progress = True\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
" if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n",
" print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles)\n",
"\n",

View File

@@ -94,7 +94,7 @@
"\n",
" # Create solver.\n",
" solver = model_builder.ModelSolver(_SOLVER.value)\n",
" if not solver:\n",
" if not solver.solver_is_supported():\n",
" print(f'Cannot create solver with name \\'{_SOLVER.value}\\'')\n",
" return\n",
"\n",
@@ -109,7 +109,8 @@
" solver.solve(model)\n",
"\n",
"\n",
"main()\n"
"main()\n",
"\n"
]
}
],

View File

@@ -83,10 +83,10 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Sequence\n",
"from typing import List, Sequence\n",
"from ortools.sat.python import cp_model\n",
"\n",
"RAW_DATA = [\n",
"RAW_DATA: List[List[float]] = [\n",
" # fmt:off\n",
" [\n",
" 0, 0, 49.774821, -59.5968886, -46.0773896, 0, -65.166109, 0, 0, 0, 0, 0,\n",
@@ -720,15 +720,15 @@
"]\n",
"\n",
"\n",
"def solve_qubo():\n",
" \"\"\"Solve the Qubo problem.\"\"\"\n",
"def solve_qubo() -> None:\n",
" \"\"\"solve the Qubo problem.\"\"\"\n",
"\n",
" # Constraint programming engine\n",
" # Build the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" num_vars = len(RAW_DATA)\n",
" all_vars = range(num_vars)\n",
" variables = [model.NewBoolVar(\"x_%i\" % i) for i in all_vars]\n",
" variables = [model.new_bool_var(\"x_%i\" % i) for i in all_vars]\n",
"\n",
" obj_vars = []\n",
" obj_coeffs = []\n",
@@ -740,10 +740,10 @@
" if coeff == 0.0:\n",
" continue\n",
" x_j = variables[j]\n",
" var = model.NewBoolVar(\"\")\n",
" model.AddBoolOr([x_i.Not(), x_j.Not(), var])\n",
" model.AddImplication(var, x_i)\n",
" model.AddImplication(var, x_j)\n",
" var = model.new_bool_var(\"\")\n",
" model.add_bool_or([~x_i, ~x_j, var])\n",
" model.add_implication(var, x_i)\n",
" model.add_implication(var, x_j)\n",
" obj_vars.append(var)\n",
" obj_coeffs.append(coeff)\n",
"\n",
@@ -753,14 +753,14 @@
" obj_vars.append(variables[i])\n",
" obj_coeffs.append(self_coeff)\n",
"\n",
" model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
" model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_search_workers = 16\n",
" solver.parameters.log_search_progress = True\n",
" solver.parameters.max_time_in_seconds = 30\n",
" solver.Solve(model)\n",
" solver.solve(model)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -80,7 +80,6 @@
"\n",
"Data use in flags:\n",
" http://www.om-db.wi.tum.de/psplib/data.html\n",
"\n",
"\n"
]
},
@@ -92,6 +91,8 @@
"outputs": [],
"source": [
"import collections\n",
"import time\n",
"from typing import Optional\n",
"\n",
"from ortools.sat.colab import flags\n",
"from google.protobuf import text_format\n",
@@ -113,13 +114,13 @@
"_ADD_REDUNDANT_ENERGETIC_CONSTRAINTS = flags.define_bool(\n",
" \"add_redundant_energetic_constraints\",\n",
" False,\n",
" \"Add redundant energetic constraints on the pairs of tasks extracted from\"\n",
" \"add redundant energetic constraints on the pairs of tasks extracted from\"\n",
" + \" precedence graph.\",\n",
")\n",
"_DELAY_TIME_LIMIT = flags.define_float(\n",
" \"delay_time_limit\",\n",
" 20.0,\n",
" \"Time limit when computing min delay between tasks.\"\n",
" \"pairwise_delay_total_time_limit\",\n",
" 120.0,\n",
" \"Total time limit when computing min delay between tasks.\"\n",
" + \" A non-positive time limit disable min delays computation.\",\n",
")\n",
"_PREEMPTIVE_LB_TIME_LIMIT = flags.define_float(\n",
@@ -130,7 +131,7 @@
")\n",
"\n",
"\n",
"def PrintProblemStatistics(problem):\n",
"def print_problem_statistics(problem: rcpsp_pb2.RcpspProblem):\n",
" \"\"\"Display various statistics on the problem.\"\"\"\n",
"\n",
" # Determine problem type.\n",
@@ -175,7 +176,9 @@
" print(f\" - {tasks_with_delay} tasks with successor delays\")\n",
"\n",
"\n",
"def AnalyseDependencyGraph(problem):\n",
"def analyse_dependency_graph(\n",
" problem: rcpsp_pb2.RcpspProblem,\n",
") -> tuple[list[tuple[int, int, list[int]]], dict[int, list[int]]]:\n",
" \"\"\"Analyses the dependency graph to improve the model.\n",
"\n",
" Args:\n",
@@ -211,7 +214,7 @@
" # Search for pair of tasks, containing at least two parallel branch between\n",
" # them in the precedence graph.\n",
" num_candidates = 0\n",
" result = []\n",
" result: list[tuple[int, int, list[int]]] = []\n",
" for source, start_outs in outs.items():\n",
" if len(start_outs) <= 1:\n",
" # Starting with the unique successor of source will be as good.\n",
@@ -244,27 +247,27 @@
" result.append((source, sink, common))\n",
"\n",
" # Sort entries lexicographically by (len(common), source, sink)\n",
" def Price(entry):\n",
" def price(entry):\n",
" return num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] + entry[1]\n",
"\n",
" result.sort(key=Price)\n",
" result.sort(key=price)\n",
" print(f\" - created {len(result)} pairs of nodes to examine\", flush=True)\n",
" return result, after\n",
"\n",
"\n",
"def SolveRcpsp(\n",
" problem,\n",
" proto_file,\n",
" params,\n",
" active_tasks,\n",
" source,\n",
" sink,\n",
" intervals_of_tasks,\n",
" delays,\n",
" in_main_solve=False,\n",
" initial_solution=None,\n",
" lower_bound=0,\n",
"):\n",
"def solve_rcpsp(\n",
" problem: rcpsp_pb2.RcpspProblem,\n",
" proto_file: str,\n",
" params: str,\n",
" active_tasks: set[int],\n",
" source: int,\n",
" sink: int,\n",
" intervals_of_tasks: list[tuple[int, int, list[int]]],\n",
" delays: dict[tuple[int, int], tuple[int, int]],\n",
" in_main_solve: bool = False,\n",
" initial_solution: Optional[rcpsp_pb2.RcpspAssignment] = None,\n",
" lower_bound: int = 0,\n",
") -> tuple[int, int, Optional[rcpsp_pb2.RcpspAssignment]]:\n",
" \"\"\"Parse and solve a given RCPSP problem in proto format.\n",
"\n",
" The model will only look at the tasks {source} + {sink} + active_tasks, and\n",
@@ -291,7 +294,7 @@
" \"\"\"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
" model.SetName(problem.name)\n",
" model.name = problem.name\n",
"\n",
" num_resources = len(problem.resources)\n",
"\n",
@@ -336,16 +339,16 @@
" num_recipes = len(task.recipes)\n",
" all_recipes = range(num_recipes)\n",
"\n",
" start_var = model.NewIntVar(0, horizon, f\"start_of_task_{t}\")\n",
" end_var = model.NewIntVar(0, horizon, f\"end_of_task_{t}\")\n",
" start_var = model.new_int_var(0, horizon, f\"start_of_task_{t}\")\n",
" end_var = model.new_int_var(0, horizon, f\"end_of_task_{t}\")\n",
"\n",
" literals = []\n",
" if num_recipes > 1:\n",
" # Create one literal per recipe.\n",
" literals = [model.NewBoolVar(f\"is_present_{t}_{r}\") for r in all_recipes]\n",
" literals = [model.new_bool_var(f\"is_present_{t}_{r}\") for r in all_recipes]\n",
"\n",
" # Exactly one recipe must be performed.\n",
" model.AddExactlyOne(literals)\n",
" model.add_exactly_one(literals)\n",
"\n",
" else:\n",
" literals = [1]\n",
@@ -360,19 +363,19 @@
" demand_matrix[(resource, recipe_index)] = demand\n",
"\n",
" # Create the duration variable from the accumulated durations.\n",
" duration_var = model.NewIntVarFromDomain(\n",
" cp_model.Domain.FromValues(task_to_recipe_durations[t]),\n",
" duration_var = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_values(task_to_recipe_durations[t]),\n",
" f\"duration_of_task_{t}\",\n",
" )\n",
"\n",
" # Link the recipe literals and the duration_var.\n",
" for r in range(num_recipes):\n",
" model.Add(duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf(\n",
" model.add(duration_var == task_to_recipe_durations[t][r]).only_enforce_if(\n",
" literals[r]\n",
" )\n",
"\n",
" # Create the interval of the task.\n",
" task_interval = model.NewIntervalVar(\n",
" task_interval = model.new_interval_var(\n",
" start_var, duration_var, end_var, f\"task_interval_{t}\"\n",
" )\n",
"\n",
@@ -387,14 +390,14 @@
" for res in all_resources:\n",
" demands = [demand_matrix[(res, recipe)] for recipe in all_recipes]\n",
" task_resource_to_fixed_demands[(t, res)] = demands\n",
" demand_var = model.NewIntVarFromDomain(\n",
" cp_model.Domain.FromValues(demands), f\"demand_{t}_{res}\"\n",
" demand_var = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_values(demands), f\"demand_{t}_{res}\"\n",
" )\n",
" task_to_resource_demands[t].append(demand_var)\n",
"\n",
" # Link the recipe literals and the demand_var.\n",
" for r in all_recipes:\n",
" model.Add(demand_var == demand_matrix[(res, r)]).OnlyEnforceIf(\n",
" model.add(demand_var == demand_matrix[(res, r)]).only_enforce_if(\n",
" literals[r]\n",
" )\n",
"\n",
@@ -415,10 +418,13 @@
" )\n",
"\n",
" # Create makespan variable\n",
" makespan = model.NewIntVar(lower_bound, horizon, \"makespan\")\n",
" makespan_size = model.NewIntVar(1, horizon, \"interval_makespan_size\")\n",
" interval_makespan = model.NewIntervalVar(\n",
" makespan, makespan_size, model.NewConstant(horizon + 1), \"interval_makespan\"\n",
" makespan = model.new_int_var(lower_bound, horizon, \"makespan\")\n",
" makespan_size = model.new_int_var(1, horizon, \"interval_makespan_size\")\n",
" interval_makespan = model.new_interval_var(\n",
" makespan,\n",
" makespan_size,\n",
" model.new_constant(horizon + 1),\n",
" \"interval_makespan\",\n",
" )\n",
"\n",
" # Add precedences.\n",
@@ -437,21 +443,21 @@
" p1 = task_to_presence_literals[task_id][m1]\n",
" if next_id == sink:\n",
" delay = delay_matrix.recipe_delays[m1].min_delays[0]\n",
" model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1)\n",
" model.add(s1 + delay <= makespan).only_enforce_if(p1)\n",
" else:\n",
" for m2 in range(num_next_modes):\n",
" delay = delay_matrix.recipe_delays[m1].min_delays[m2]\n",
" s2 = task_starts[next_id]\n",
" p2 = task_to_presence_literals[next_id][m2]\n",
" model.Add(s1 + delay <= s2).OnlyEnforceIf([p1, p2])\n",
" model.add(s1 + delay <= s2).only_enforce_if([p1, p2])\n",
" else:\n",
" # Normal dependencies (task ends before the start of successors).\n",
" for t in all_active_tasks:\n",
" for n in problem.tasks[t].successors:\n",
" if n == sink:\n",
" model.Add(task_ends[t] <= makespan)\n",
" model.add(task_ends[t] <= makespan)\n",
" elif n in active_tasks:\n",
" model.Add(task_ends[t] <= task_starts[n])\n",
" model.add(task_ends[t] <= task_starts[n])\n",
"\n",
" # Containers for resource investment problems.\n",
" capacities = [] # Capacity variables for all resources.\n",
@@ -471,8 +477,8 @@
" demands = [task_to_resource_demands[t][res] for t in all_active_tasks]\n",
"\n",
" if problem.is_resource_investment:\n",
" capacity = model.NewIntVar(0, c, f\"capacity_of_{res}\")\n",
" model.AddCumulative(intervals, demands, capacity)\n",
" capacity = model.new_int_var(0, c, f\"capacity_of_{res}\")\n",
" model.add_cumulative(intervals, demands, capacity)\n",
" capacities.append(capacity)\n",
" max_cost += c * resource.unit_cost\n",
" else: # Standard renewable resource.\n",
@@ -480,7 +486,7 @@
" intervals.append(interval_makespan)\n",
" demands.append(c)\n",
"\n",
" model.AddCumulative(intervals, demands, c)\n",
" model.add_cumulative(intervals, demands, c)\n",
" else: # Non empty non renewable resource. (single mode only)\n",
" if problem.is_consumer_producer:\n",
" reservoir_starts = []\n",
@@ -491,15 +497,15 @@
" reservoir_demands.append(\n",
" task_resource_to_fixed_demands[(t, res)][0]\n",
" )\n",
" model.AddReservoirConstraint(\n",
" model.add_reservoir_constraint(\n",
" reservoir_starts,\n",
" reservoir_demands,\n",
" resource.min_capacity,\n",
" resource.max_capacity,\n",
" )\n",
" else: # No producer-consumer. We just sum the demands.\n",
" model.Add(\n",
" cp_model.LinearExpr.Sum(\n",
" model.add(\n",
" cp_model.LinearExpr.sum(\n",
" [task_to_resource_demands[t][res] for t in all_active_tasks]\n",
" )\n",
" <= c\n",
@@ -507,8 +513,8 @@
"\n",
" # Objective.\n",
" if problem.is_resource_investment:\n",
" objective = model.NewIntVar(0, max_cost, \"capacity_costs\")\n",
" model.Add(\n",
" objective = model.new_int_var(0, max_cost, \"capacity_costs\")\n",
" model.add(\n",
" objective\n",
" == sum(\n",
" problem.resources[i].unit_cost * capacities[i]\n",
@@ -518,17 +524,17 @@
" else:\n",
" objective = makespan\n",
"\n",
" model.Minimize(objective)\n",
" model.minimize(objective)\n",
"\n",
" # Add min delay constraints.\n",
" if delays is not None:\n",
" for (local_start, local_end), (min_delay, _) in delays.items():\n",
" if local_start == source and local_end in active_tasks:\n",
" model.Add(task_starts[local_end] >= min_delay)\n",
" model.add(task_starts[local_end] >= min_delay)\n",
" elif local_start in active_tasks and local_end == sink:\n",
" model.Add(makespan >= task_ends[local_start] + min_delay)\n",
" model.add(makespan >= task_ends[local_start] + min_delay)\n",
" elif local_start in active_tasks and local_end in active_tasks:\n",
" model.Add(task_starts[local_end] >= task_ends[local_start] + min_delay)\n",
" model.add(task_starts[local_end] >= task_ends[local_start] + min_delay)\n",
"\n",
" problem_is_single_mode = True\n",
" for t in all_active_tasks:\n",
@@ -571,7 +577,7 @@
" if sum_of_max_energies <= c * min_delay:\n",
" ignored_constraits += 1\n",
" continue\n",
" model.Add(\n",
" model.add(\n",
" c * (task_starts[local_end] - task_ends[local_start])\n",
" >= sum(task_resource_to_energy[(t, res)] for t in common)\n",
" )\n",
@@ -585,15 +591,15 @@
" # Add solution hint.\n",
" if initial_solution:\n",
" for t in all_active_tasks:\n",
" model.AddHint(task_starts[t], initial_solution.start_of_task[t])\n",
" model.add_hint(task_starts[t], initial_solution.start_of_task[t])\n",
" if len(task_to_presence_literals[t]) > 1:\n",
" selected = initial_solution.selected_recipe_of_task[t]\n",
" model.AddHint(task_to_presence_literals[t][selected], 1)\n",
" model.add_hint(task_to_presence_literals[t][selected], 1)\n",
"\n",
" # Write model to file.\n",
" if proto_file:\n",
" print(f\"Writing proto to{proto_file}\")\n",
" model.ExportToFile(proto_file)\n",
" model.export_to_file(proto_file)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
@@ -615,28 +621,35 @@
" if in_main_solve:\n",
" solver.parameters.log_search_progress = True\n",
" #\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" assignment = rcpsp_pb2.RcpspAssignment()\n",
" for t, _ in enumerate(problem.tasks):\n",
" if t in task_starts:\n",
" assignment.start_of_task.append(solver.Value(task_starts[t]))\n",
" assignment.start_of_task.append(solver.value(task_starts[t]))\n",
" for r, recipe_literal in enumerate(task_to_presence_literals[t]):\n",
" if solver.BooleanValue(recipe_literal):\n",
" if solver.boolean_value(recipe_literal):\n",
" assignment.selected_recipe_of_task.append(r)\n",
" break\n",
" else: # t is not an active task.\n",
" assignment.start_of_task.append(0)\n",
" assignment.selected_recipe_of_task.append(0)\n",
" return (\n",
" int(solver.BestObjectiveBound()),\n",
" int(solver.ObjectiveValue()),\n",
" int(solver.best_objective_bound),\n",
" int(solver.objective_value),\n",
" assignment,\n",
" )\n",
" return -1, -1, None\n",
"\n",
"\n",
"def ComputeDelaysBetweenNodes(problem, task_intervals):\n",
"def compute_delays_between_nodes(\n",
" problem: rcpsp_pb2.RcpspProblem,\n",
" task_intervals: list[tuple[int, int, list[int]]],\n",
") -> tuple[\n",
" dict[tuple[int, int], tuple[int, int]],\n",
" Optional[rcpsp_pb2.RcpspAssignment],\n",
" bool,\n",
"]:\n",
" \"\"\"Computes the min delays between all pairs of tasks in 'task_intervals'.\n",
"\n",
" Args:\n",
@@ -656,21 +669,30 @@
" ):\n",
" return delays, None, False\n",
"\n",
" time_limit = _DELAY_TIME_LIMIT.value\n",
" complete_problem_assignment = None\n",
" num_optimal_delays = 0\n",
" num_delays_not_found = 0\n",
" optimal_found = True\n",
" for start_task, end_task, active_tasks in task_intervals:\n",
" min_delay, feasible_delay, assignment = SolveRcpsp(\n",
" if time_limit <= 0:\n",
" optimal_found = False\n",
" print(f\" - #timeout ({_DELAY_TIME_LIMIT.value}s) reached\", flush=True)\n",
" break\n",
"\n",
" start_time = time.time()\n",
" min_delay, feasible_delay, assignment = solve_rcpsp(\n",
" problem,\n",
" \"\",\n",
" f\"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}\",\n",
" active_tasks,\n",
" f\"num_search_workers:16,max_time_in_seconds:{time_limit}\",\n",
" set(active_tasks),\n",
" start_task,\n",
" end_task,\n",
" [],\n",
" delays,\n",
" )\n",
" time_limit -= time.time() - start_time\n",
"\n",
" if min_delay != -1:\n",
" delays[(start_task, end_task)] = min_delay, feasible_delay\n",
" if start_task == 0 and end_task == len(problem.tasks) - 1:\n",
@@ -690,7 +712,13 @@
" return delays, complete_problem_assignment, optimal_found\n",
"\n",
"\n",
"def AcceptNewCandidate(problem, after, demand_map, current, candidate):\n",
"def accept_new_candidate(\n",
" problem: rcpsp_pb2.RcpspProblem,\n",
" after: dict[int, list[int]],\n",
" demand_map: dict[tuple[int, int], int],\n",
" current: list[int],\n",
" candidate: int,\n",
") -> bool:\n",
" \"\"\"Check if candidate is compatible with the tasks in current.\"\"\"\n",
" for c in current:\n",
" if candidate in after[c] or c in after[candidate]:\n",
@@ -710,7 +738,11 @@
" return True\n",
"\n",
"\n",
"def ComputePreemptiveLowerBound(problem, after, lower_bound):\n",
"def compute_preemptive_lower_bound(\n",
" problem: rcpsp_pb2.RcpspProblem,\n",
" after: dict[int, list[int]],\n",
" lower_bound: int,\n",
") -> int:\n",
" \"\"\"Computes a preemtive lower bound for the makespan statically.\n",
"\n",
" For this, it breaks all intervals into a set of intervals of size one.\n",
@@ -763,7 +795,7 @@
" new_combinations = [[t]]\n",
"\n",
" for c in all_combinations:\n",
" if AcceptNewCandidate(problem, after, demand_map, c, t):\n",
" if accept_new_candidate(problem, after, demand_map, c, t):\n",
" new_combinations.append(c + [t])\n",
"\n",
" all_combinations.extend(new_combinations)\n",
@@ -772,14 +804,14 @@
" if len(all_combinations) > 5000000:\n",
" return lower_bound # Abort if too large.\n",
"\n",
" # Solve the selection model.\n",
" # solve the selection model.\n",
"\n",
" # TODO(user): a few possible improvements:\n",
" # 1/ use \"dominating\" columns, i.e. if you can add a task to a column, then\n",
" # do not use that column.\n",
" # 2/ Merge all task with exactly same demands into one.\n",
" model = cp_model.CpModel()\n",
" model.SetName(f\"lower_bound_{problem.name}\")\n",
" model.name = f\"lower_bound_{problem.name}\"\n",
"\n",
" vars_per_task = collections.defaultdict(list)\n",
" all_vars = []\n",
@@ -787,29 +819,29 @@
" min_duration = max_duration\n",
" for t in c:\n",
" min_duration = min(min_duration, duration_map[t])\n",
" count = model.NewIntVar(0, min_duration, f\"count_{c}\")\n",
" count = model.new_int_var(0, min_duration, f\"count_{c}\")\n",
" all_vars.append(count)\n",
" for t in c:\n",
" vars_per_task[t].append(count)\n",
"\n",
" # Each task must be performed.\n",
" for t in all_active_tasks:\n",
" model.Add(sum(vars_per_task[t]) >= duration_map[t])\n",
" model.add(sum(vars_per_task[t]) >= duration_map[t])\n",
"\n",
" # Objective\n",
" objective_var = model.NewIntVar(lower_bound, sum_of_demands, \"objective_var\")\n",
" model.Add(objective_var == sum(all_vars))\n",
" objective_var = model.new_int_var(lower_bound, sum_of_demands, \"objective_var\")\n",
" model.add(objective_var == sum(all_vars))\n",
"\n",
" model.Minimize(objective_var)\n",
" model.minimize(objective_var)\n",
"\n",
" # Solve model.\n",
" # solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_search_workers = 16\n",
" solver.parameters.max_time_in_seconds = _PREEMPTIVE_LB_TIME_LIMIT.value\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" status_str = \"optimal\" if status == cp_model.OPTIMAL else \"\"\n",
" lower_bound = max(lower_bound, int(solver.BestObjectiveBound()))\n",
" lower_bound = max(lower_bound, int(solver.best_objective_bound))\n",
" print(f\" - {status_str} static lower bound = {lower_bound}\", flush=True)\n",
"\n",
" return lower_bound\n",
@@ -820,10 +852,10 @@
" rcpsp_parser.parse_file(_INPUT.value)\n",
"\n",
" problem = rcpsp_parser.problem()\n",
" PrintProblemStatistics(problem)\n",
" print_problem_statistics(problem)\n",
"\n",
" intervals_of_tasks, after = AnalyseDependencyGraph(problem)\n",
" delays, initial_solution, optimal_found = ComputeDelaysBetweenNodes(\n",
" intervals_of_tasks, after = analyse_dependency_graph(problem)\n",
" delays, initial_solution, optimal_found = compute_delays_between_nodes(\n",
" problem, intervals_of_tasks\n",
" )\n",
"\n",
@@ -831,9 +863,9 @@
" key = (0, last_task)\n",
" lower_bound = delays[key][0] if key in delays else 0\n",
" if not optimal_found and _PREEMPTIVE_LB_TIME_LIMIT.value > 0.0:\n",
" lower_bound = ComputePreemptiveLowerBound(problem, after, lower_bound)\n",
" lower_bound = compute_preemptive_lower_bound(problem, after, lower_bound)\n",
"\n",
" SolveRcpsp(\n",
" solve_rcpsp(\n",
" problem=problem,\n",
" proto_file=_OUTPUT_PROTO.value,\n",
" params=_PARAMS.value,\n",

View File

@@ -84,8 +84,8 @@
"outputs": [],
"source": [
"from ortools.sat.colab import flags\n",
"from ortools.sat.python import cp_model\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_OUTPUT_PROTO = flags.define_string(\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
@@ -95,7 +95,9 @@
")\n",
"\n",
"\n",
"def negated_bounded_span(works, start, length):\n",
"def negated_bounded_span(\n",
" works: list[cp_model.BoolVarT], start: int, length: int\n",
") -> list[cp_model.BoolVarT]:\n",
" \"\"\"Filters an isolated sub-sequence of variables assined to True.\n",
"\n",
" Extract the span of Boolean variables [start, start + length), negate them,\n",
@@ -113,20 +115,28 @@
" or by the start or end of works.\n",
" \"\"\"\n",
" sequence = []\n",
" # Left border (start of works, or works[start - 1])\n",
" # left border (start of works, or works[start - 1])\n",
" if start > 0:\n",
" sequence.append(works[start - 1])\n",
" for i in range(length):\n",
" sequence.append(works[start + i].Not())\n",
" # Right border (end of works or works[start + length])\n",
" sequence.append(~works[start + i])\n",
" # right border (end of works or works[start + length])\n",
" if start + length < len(works):\n",
" sequence.append(works[start + length])\n",
" return sequence\n",
"\n",
"\n",
"def add_soft_sequence_constraint(\n",
" model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix\n",
"):\n",
" model: cp_model.CpModel,\n",
" works: list[cp_model.BoolVarT],\n",
" hard_min: int,\n",
" soft_min: int,\n",
" min_cost: int,\n",
" soft_max: int,\n",
" hard_max: int,\n",
" max_cost: int,\n",
" prefix: str,\n",
") -> tuple[list[cp_model.BoolVarT], list[int]]:\n",
" \"\"\"Sequence constraint on true variables with soft and hard bounds.\n",
"\n",
" This constraint look at every maximal contiguous sequence of variables\n",
@@ -160,7 +170,7 @@
" # Forbid sequences that are too short.\n",
" for length in range(1, hard_min):\n",
" for start in range(len(works) - length + 1):\n",
" model.AddBoolOr(negated_bounded_span(works, start, length))\n",
" model.add_bool_or(negated_bounded_span(works, start, length))\n",
"\n",
" # Penalize sequences that are below the soft limit.\n",
" if min_cost > 0:\n",
@@ -168,9 +178,9 @@
" for start in range(len(works) - length + 1):\n",
" span = negated_bounded_span(works, start, length)\n",
" name = \": under_span(start=%i, length=%i)\" % (start, length)\n",
" lit = model.NewBoolVar(prefix + name)\n",
" lit = model.new_bool_var(prefix + name)\n",
" span.append(lit)\n",
" model.AddBoolOr(span)\n",
" model.add_bool_or(span)\n",
" cost_literals.append(lit)\n",
" # We filter exactly the sequence with a short length.\n",
" # The penalty is proportional to the delta with soft_min.\n",
@@ -182,23 +192,31 @@
" for start in range(len(works) - length + 1):\n",
" span = negated_bounded_span(works, start, length)\n",
" name = \": over_span(start=%i, length=%i)\" % (start, length)\n",
" lit = model.NewBoolVar(prefix + name)\n",
" lit = model.new_bool_var(prefix + name)\n",
" span.append(lit)\n",
" model.AddBoolOr(span)\n",
" model.add_bool_or(span)\n",
" cost_literals.append(lit)\n",
" # Cost paid is max_cost * excess length.\n",
" cost_coefficients.append(max_cost * (length - soft_max))\n",
"\n",
" # Just forbid any sequence of true variables with length hard_max + 1\n",
" for start in range(len(works) - hard_max):\n",
" model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)])\n",
" model.add_bool_or([~works[i] for i in range(start, start + hard_max + 1)])\n",
" return cost_literals, cost_coefficients\n",
"\n",
"\n",
"def add_soft_sum_constraint(\n",
" model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix\n",
"):\n",
" \"\"\"Sum constraint with soft and hard bounds.\n",
" model: cp_model.CpModel,\n",
" works: list[cp_model.BoolVarT],\n",
" hard_min: int,\n",
" soft_min: int,\n",
" min_cost: int,\n",
" soft_max: int,\n",
" hard_max: int,\n",
" max_cost: int,\n",
" prefix: str,\n",
") -> tuple[list[cp_model.IntVar], list[int]]:\n",
" \"\"\"sum constraint with soft and hard bounds.\n",
"\n",
" This constraint counts the variables assigned to true from works.\n",
" If forbids sum < hard_min or > hard_max.\n",
@@ -227,33 +245,33 @@
" \"\"\"\n",
" cost_variables = []\n",
" cost_coefficients = []\n",
" sum_var = model.NewIntVar(hard_min, hard_max, \"\")\n",
" sum_var = model.new_int_var(hard_min, hard_max, \"\")\n",
" # This adds the hard constraints on the sum.\n",
" model.Add(sum_var == sum(works))\n",
" model.add(sum_var == sum(works))\n",
"\n",
" # Penalize sums below the soft_min target.\n",
" if soft_min > hard_min and min_cost > 0:\n",
" delta = model.NewIntVar(-len(works), len(works), \"\")\n",
" model.Add(delta == soft_min - sum_var)\n",
" delta = model.new_int_var(-len(works), len(works), \"\")\n",
" model.add(delta == soft_min - sum_var)\n",
" # TODO(user): Compare efficiency with only excess >= soft_min - sum_var.\n",
" excess = model.NewIntVar(0, 7, prefix + \": under_sum\")\n",
" model.AddMaxEquality(excess, [delta, 0])\n",
" excess = model.new_int_var(0, 7, prefix + \": under_sum\")\n",
" model.add_max_equality(excess, [delta, 0])\n",
" cost_variables.append(excess)\n",
" cost_coefficients.append(min_cost)\n",
"\n",
" # Penalize sums above the soft_max target.\n",
" if soft_max < hard_max and max_cost > 0:\n",
" delta = model.NewIntVar(-7, 7, \"\")\n",
" model.Add(delta == sum_var - soft_max)\n",
" excess = model.NewIntVar(0, 7, prefix + \": over_sum\")\n",
" model.AddMaxEquality(excess, [delta, 0])\n",
" delta = model.new_int_var(-7, 7, \"\")\n",
" model.add(delta == sum_var - soft_max)\n",
" excess = model.new_int_var(0, 7, prefix + \": over_sum\")\n",
" model.add_max_equality(excess, [delta, 0])\n",
" cost_variables.append(excess)\n",
" cost_coefficients.append(max_cost)\n",
"\n",
" return cost_variables, cost_coefficients\n",
"\n",
"\n",
"def solve_shift_scheduling(params, output_proto):\n",
"def solve_shift_scheduling(params: str, output_proto: str):\n",
" \"\"\"Solves the shift scheduling problem.\"\"\"\n",
" # Data\n",
" num_employees = 8\n",
@@ -348,22 +366,22 @@
" for e in range(num_employees):\n",
" for s in range(num_shifts):\n",
" for d in range(num_days):\n",
" work[e, s, d] = model.NewBoolVar(\"work%i_%i_%i\" % (e, s, d))\n",
" work[e, s, d] = model.new_bool_var(\"work%i_%i_%i\" % (e, s, d))\n",
"\n",
" # Linear terms of the objective in a minimization context.\n",
" obj_int_vars = []\n",
" obj_int_coeffs = []\n",
" obj_bool_vars = []\n",
" obj_bool_coeffs = []\n",
" obj_int_vars: list[cp_model.IntVar] = []\n",
" obj_int_coeffs: list[int] = []\n",
" obj_bool_vars: list[cp_model.BoolVarT] = []\n",
" obj_bool_coeffs: list[int] = []\n",
"\n",
" # Exactly one shift per day.\n",
" for e in range(num_employees):\n",
" for d in range(num_days):\n",
" model.AddExactlyOne(work[e, s, d] for s in range(num_shifts))\n",
" model.add_exactly_one(work[e, s, d] for s in range(num_shifts))\n",
"\n",
" # Fixed assignments.\n",
" for e, s, d in fixed_assignments:\n",
" model.Add(work[e, s, d] == 1)\n",
" model.add(work[e, s, d] == 1)\n",
"\n",
" # Employee requests\n",
" for e, s, d, w in requests:\n",
@@ -415,17 +433,17 @@
" for e in range(num_employees):\n",
" for d in range(num_days - 1):\n",
" transition = [\n",
" work[e, previous_shift, d].Not(),\n",
" work[e, next_shift, d + 1].Not(),\n",
" ~work[e, previous_shift, d],\n",
" ~work[e, next_shift, d + 1],\n",
" ]\n",
" if cost == 0:\n",
" model.AddBoolOr(transition)\n",
" model.add_bool_or(transition)\n",
" else:\n",
" trans_var = model.NewBoolVar(\n",
" trans_var = model.new_bool_var(\n",
" \"transition (employee=%i, day=%i)\" % (e, d)\n",
" )\n",
" transition.append(trans_var)\n",
" model.AddBoolOr(transition)\n",
" model.add_bool_or(transition)\n",
" obj_bool_vars.append(trans_var)\n",
" obj_bool_coeffs.append(cost)\n",
"\n",
@@ -436,18 +454,18 @@
" works = [work[e, s, w * 7 + d] for e in range(num_employees)]\n",
" # Ignore Off shift.\n",
" min_demand = weekly_cover_demands[d][s - 1]\n",
" worked = model.NewIntVar(min_demand, num_employees, \"\")\n",
" model.Add(worked == sum(works))\n",
" worked = model.new_int_var(min_demand, num_employees, \"\")\n",
" model.add(worked == sum(works))\n",
" over_penalty = excess_cover_penalties[s - 1]\n",
" if over_penalty > 0:\n",
" name = \"excess_demand(shift=%i, week=%i, day=%i)\" % (s, w, d)\n",
" excess = model.NewIntVar(0, num_employees - min_demand, name)\n",
" model.Add(excess == worked - min_demand)\n",
" excess = model.new_int_var(0, num_employees - min_demand, name)\n",
" model.add(excess == worked - min_demand)\n",
" obj_int_vars.append(excess)\n",
" obj_int_coeffs.append(over_penalty)\n",
"\n",
" # Objective\n",
" model.Minimize(\n",
" model.minimize(\n",
" sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars)))\n",
" + sum(obj_int_vars[i] * obj_int_coeffs[i] for i in range(len(obj_int_vars)))\n",
" )\n",
@@ -462,7 +480,7 @@
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
@@ -475,32 +493,32 @@
" schedule = \"\"\n",
" for d in range(num_days):\n",
" for s in range(num_shifts):\n",
" if solver.BooleanValue(work[e, s, d]):\n",
" if solver.boolean_value(work[e, s, d]):\n",
" schedule += shifts[s] + \" \"\n",
" print(\"worker %i: %s\" % (e, schedule))\n",
" print()\n",
" print(\"Penalties:\")\n",
" for i, var in enumerate(obj_bool_vars):\n",
" if solver.BooleanValue(var):\n",
" if solver.boolean_value(var):\n",
" penalty = obj_bool_coeffs[i]\n",
" if penalty > 0:\n",
" print(\" %s violated, penalty=%i\" % (var.Name(), penalty))\n",
" print(f\" {var.name} violated, penalty={penalty}\")\n",
" else:\n",
" print(\" %s fulfilled, gain=%i\" % (var.Name(), -penalty))\n",
" print(f\" {var.name} fulfilled, gain={-penalty}\")\n",
"\n",
" for i, var in enumerate(obj_int_vars):\n",
" if solver.Value(var) > 0:\n",
" if solver.value(var) > 0:\n",
" print(\n",
" \" %s violated by %i, linear penalty=%i\"\n",
" % (var.Name(), solver.Value(var), obj_int_coeffs[i])\n",
" % (var.name, solver.value(var), obj_int_coeffs[i])\n",
" )\n",
"\n",
" print()\n",
" print(\"Statistics\")\n",
" print(\" - status : %s\" % solver.StatusName(status))\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - status : %s\" % solver.status_name(status))\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"def main(_):\n",

View File

@@ -95,7 +95,7 @@
")\n",
"_PARAMS = flags.define_string(\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n",
" \"num_search_workers:16,log_search_progress:false,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"_PREPROCESS = flags.define_bool(\n",
@@ -116,7 +116,7 @@
" \"\"\"Called after each new solution found.\"\"\"\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" % (self.__solution_count, self.wall_time, self.objective_value)\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
@@ -501,34 +501,34 @@
" % (job_id, release_date, duration, due_date)\n",
" )\n",
" name_suffix = \"_%i\" % job_id\n",
" start = model.NewIntVar(release_date, due_date, \"s\" + name_suffix)\n",
" end = model.NewIntVar(release_date, due_date, \"e\" + name_suffix)\n",
" interval = model.NewIntervalVar(start, duration, end, \"i\" + name_suffix)\n",
" start = model.new_int_var(release_date, due_date, \"s\" + name_suffix)\n",
" end = model.new_int_var(release_date, due_date, \"e\" + name_suffix)\n",
" interval = model.new_interval_var(start, duration, end, \"i\" + name_suffix)\n",
" starts.append(start)\n",
" ends.append(end)\n",
" intervals.append(interval)\n",
"\n",
" # No overlap constraint.\n",
" model.AddNoOverlap(intervals)\n",
" model.add_no_overlap(intervals)\n",
"\n",
" # ----------------------------------------------------------------------------\n",
" # Transition times using a circuit constraint.\n",
" arcs = []\n",
" for i in all_jobs:\n",
" # Initial arc from the dummy node (0) to a task.\n",
" start_lit = model.NewBoolVar(\"\")\n",
" start_lit = model.new_bool_var(\"\")\n",
" arcs.append((0, i + 1, start_lit))\n",
" # If this task is the first, set to minimum starting time.\n",
" min_start_time = max(release_dates[i], setup_times[0][i])\n",
" model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit)\n",
" model.add(starts[i] == min_start_time).only_enforce_if(start_lit)\n",
" # Final arc from an arc to the dummy node.\n",
" arcs.append((i + 1, 0, model.NewBoolVar(\"\")))\n",
" arcs.append((i + 1, 0, model.new_bool_var(\"\")))\n",
"\n",
" for j in all_jobs:\n",
" if i == j:\n",
" continue\n",
"\n",
" lit = model.NewBoolVar(\"%i follows %i\" % (j, i))\n",
" lit = model.new_bool_var(\"%i follows %i\" % (j, i))\n",
" arcs.append((i + 1, j + 1, lit))\n",
"\n",
" # We add the reified precedence to link the literal with the times of the\n",
@@ -536,27 +536,27 @@
" # If release_dates[j] == 0, we can strenghten this precedence into an\n",
" # equality as we are minimizing the makespan.\n",
" if release_dates[j] == 0:\n",
" model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n",
" model.add(starts[j] == ends[i] + setup_times[i + 1][j]).only_enforce_if(\n",
" lit\n",
" )\n",
" else:\n",
" model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n",
" model.add(starts[j] >= ends[i] + setup_times[i + 1][j]).only_enforce_if(\n",
" lit\n",
" )\n",
"\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # ----------------------------------------------------------------------------\n",
" # Precedences.\n",
" for before, after in precedences:\n",
" print(\"job %i is after job %i\" % (after, before))\n",
" model.Add(ends[before] <= starts[after])\n",
" model.add(ends[before] <= starts[after])\n",
"\n",
" # ----------------------------------------------------------------------------\n",
" # Objective.\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, ends)\n",
" model.Minimize(makespan)\n",
" makespan = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(makespan, ends)\n",
" model.minimize(makespan)\n",
"\n",
" # ----------------------------------------------------------------------------\n",
" # Write problem to file.\n",
@@ -571,11 +571,12 @@
" if parameters:\n",
" text_format.Parse(parameters, solver.parameters)\n",
" solution_printer = SolutionPrinter()\n",
" solver.Solve(model, solution_printer)\n",
" solver.best_bound_callback = lambda a : print(f\"New objective lower bound: {a}\")\n",
" solver.solve(model, solution_printer)\n",
" for job_id in all_jobs:\n",
" print(\n",
" \"job %i starts at %i end ends at %i\"\n",
" % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id]))\n",
" % (job_id, solver.value(starts[job_id]), solver.value(ends[job_id]))\n",
" )\n",
"\n",
"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Maximize the minimum of pairwise distances between n robots in a square space.\n"
"maximize the minimum of pairwise distances between n robots in a square space.\n"
]
},
{
@@ -105,8 +105,8 @@
" model = cp_model.CpModel()\n",
"\n",
" # Create the list of coordinates (x, y) for each robot.\n",
" x = [model.NewIntVar(1, room_size, f\"x_{i}\") for i in range(num_robots)]\n",
" y = [model.NewIntVar(1, room_size, f\"y_{i}\") for i in range(num_robots)]\n",
" x = [model.new_int_var(1, room_size, f\"x_{i}\") for i in range(num_robots)]\n",
" y = [model.new_int_var(1, room_size, f\"y_{i}\") for i in range(num_robots)]\n",
"\n",
" # The specification of the problem is to maximize the minimum euclidian\n",
" # distance between any two robots. Unfortunately, the euclidian distance\n",
@@ -125,7 +125,7 @@
" # forall i:\n",
" # scaled_min_square_distance <= scaling * (x_diff_sq[i] + y_diff_sq[i])\n",
" scaling = 1000\n",
" scaled_min_square_distance = model.NewIntVar(\n",
" scaled_min_square_distance = model.new_int_var(\n",
" 0, 2 * scaling * room_size**2, \"scaled_min_square_distance\"\n",
" )\n",
"\n",
@@ -134,45 +134,45 @@
" for i in range(num_robots - 1):\n",
" for j in range(i + 1, num_robots):\n",
" # Compute the distance on each dimension between robot i and robot j.\n",
" x_diff = model.NewIntVar(-room_size, room_size, f\"x_diff{i}\")\n",
" y_diff = model.NewIntVar(-room_size, room_size, f\"y_diff{i}\")\n",
" model.Add(x_diff == x[i] - x[j])\n",
" model.Add(y_diff == y[i] - y[j])\n",
" x_diff = model.new_int_var(-room_size, room_size, f\"x_diff{i}\")\n",
" y_diff = model.new_int_var(-room_size, room_size, f\"y_diff{i}\")\n",
" model.add(x_diff == x[i] - x[j])\n",
" model.add(y_diff == y[i] - y[j])\n",
"\n",
" # Compute the square of the previous differences.\n",
" x_diff_sq = model.NewIntVar(0, room_size**2, f\"x_diff_sq{i}\")\n",
" y_diff_sq = model.NewIntVar(0, room_size**2, f\"y_diff_sq{i}\")\n",
" model.AddMultiplicationEquality(x_diff_sq, x_diff, x_diff)\n",
" model.AddMultiplicationEquality(y_diff_sq, y_diff, y_diff)\n",
" x_diff_sq = model.new_int_var(0, room_size**2, f\"x_diff_sq{i}\")\n",
" y_diff_sq = model.new_int_var(0, room_size**2, f\"y_diff_sq{i}\")\n",
" model.add_multiplication_equality(x_diff_sq, x_diff, x_diff)\n",
" model.add_multiplication_equality(y_diff_sq, y_diff, y_diff)\n",
"\n",
" # We just need to be <= to the scaled square distance as we are\n",
" # maximizing the min distance, which is equivalent as maximizing the min\n",
" # square distance.\n",
" model.Add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq))\n",
" model.add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq))\n",
"\n",
" # Naive symmetry breaking.\n",
" for i in range(1, num_robots):\n",
" model.Add(x[0] <= x[i])\n",
" model.Add(y[0] <= y[i])\n",
" model.add(x[0] <= x[i])\n",
" model.add(y[0] <= y[i])\n",
"\n",
" # Objective\n",
" model.Maximize(scaled_min_square_distance)\n",
" model.maximize(scaled_min_square_distance)\n",
"\n",
" # Creates a solver and solves the model.\n",
" solver = cp_model.CpSolver()\n",
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Prints the solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(\n",
" f\"Spread {num_robots} with a min pairwise distance of\"\n",
" f\" {math.sqrt(solver.ObjectiveValue() / scaling)}\"\n",
" f\" {math.sqrt(solver.objective_value / scaling)}\"\n",
" )\n",
" for i in range(num_robots):\n",
" print(f\"robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}\")\n",
" print(f\"robot {i}: x={solver.value(x[i])} y={solver.value(y[i])}\")\n",
" else:\n",
" print(\"No solution found.\")\n",
"\n",

View File

@@ -198,22 +198,22 @@
" def on_solution_callback(self):\n",
" \"\"\"Called on each new solution.\"\"\"\n",
" current_time = time.time()\n",
" objective = sum(self.Value(l) for l in self.__loss)\n",
" objective = sum(self.value(l) for l in self.__loss)\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, current_time - self.__start_time, objective)\n",
" )\n",
" self.__solution_count += 1\n",
" orders_in_slab = [\n",
" [o for o in self.__all_orders if self.Value(self.__assign[o][s])]\n",
" [o for o in self.__all_orders if self.value(self.__assign[o][s])]\n",
" for s in self.__all_slabs\n",
" ]\n",
" for s in self.__all_slabs:\n",
" if orders_in_slab[s]:\n",
" line = \" - slab %i, load = %i, loss = %i, orders = [\" % (\n",
" s,\n",
" self.Value(self.__load[s]),\n",
" self.Value(self.__loss[s]),\n",
" self.value(self.__load[s]),\n",
" self.value(self.__loss[s]),\n",
" )\n",
" for o in orders_in_slab[s]:\n",
" line += \"#%i(w%i, c%i) \" % (\n",
@@ -260,44 +260,46 @@
" # Create the model and the decision variables.\n",
" model = cp_model.CpModel()\n",
" assign = [\n",
" [model.NewBoolVar(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n",
" [model.new_bool_var(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n",
" for o in all_orders\n",
" ]\n",
" loads = [model.NewIntVar(0, max_capacity, \"load_of_slab_%i\" % s) for s in all_slabs]\n",
" loads = [\n",
" model.new_int_var(0, max_capacity, \"load_of_slab_%i\" % s) for s in all_slabs\n",
" ]\n",
" color_is_in_slab = [\n",
" [model.NewBoolVar(\"color_%i_in_slab_%i\" % (c + 1, s)) for c in all_colors]\n",
" [model.new_bool_var(\"color_%i_in_slab_%i\" % (c + 1, s)) for c in all_colors]\n",
" for s in all_slabs\n",
" ]\n",
"\n",
" # Compute load of all slabs.\n",
" for s in all_slabs:\n",
" model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n",
" model.add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n",
"\n",
" # Orders are assigned to one slab.\n",
" for o in all_orders:\n",
" model.AddExactlyOne(assign[o])\n",
" model.add_exactly_one(assign[o])\n",
"\n",
" # Redundant constraint (sum of loads == sum of widths).\n",
" model.Add(sum(loads) == sum(widths))\n",
" model.add(sum(loads) == sum(widths))\n",
"\n",
" # Link present_colors and assign.\n",
" for c in all_colors:\n",
" for s in all_slabs:\n",
" for o in orders_per_color[c]:\n",
" model.AddImplication(assign[o][s], color_is_in_slab[s][c])\n",
" model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not())\n",
" model.add_implication(assign[o][s], color_is_in_slab[s][c])\n",
" model.add_implication(~color_is_in_slab[s][c], ~assign[o][s])\n",
"\n",
" # At most two colors per slab.\n",
" for s in all_slabs:\n",
" model.Add(sum(color_is_in_slab[s]) <= 2)\n",
" model.add(sum(color_is_in_slab[s]) <= 2)\n",
"\n",
" # Project previous constraint on unique_color_orders\n",
" for s in all_slabs:\n",
" model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2)\n",
" model.add(sum(assign[o][s] for o in unique_color_orders) <= 2)\n",
"\n",
" # Symmetry breaking.\n",
" for s in range(num_slabs - 1):\n",
" model.Add(loads[s] >= loads[s + 1])\n",
" model.add(loads[s] >= loads[s + 1])\n",
"\n",
" # Collect equivalent orders.\n",
" width_to_unique_color_order = {}\n",
@@ -338,38 +340,38 @@
" positions = {}\n",
" for p in ordered_equivalent_orders:\n",
" if p[0] not in positions:\n",
" positions[p[0]] = model.NewIntVar(\n",
" positions[p[0]] = model.new_int_var(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n",
" )\n",
" model.AddMapDomain(positions[p[0]], assign[p[0]])\n",
" model.add_map_domain(positions[p[0]], assign[p[0]])\n",
" if p[1] not in positions:\n",
" positions[p[1]] = model.NewIntVar(\n",
" positions[p[1]] = model.new_int_var(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n",
" )\n",
" model.AddMapDomain(positions[p[1]], assign[p[1]])\n",
" model.add_map_domain(positions[p[1]], assign[p[1]])\n",
" # Finally add the symmetry breaking constraint.\n",
" model.Add(positions[p[0]] <= positions[p[1]])\n",
" model.add(positions[p[0]] <= positions[p[1]])\n",
"\n",
" # Objective.\n",
" obj = model.NewIntVar(0, num_slabs * max_loss, \"obj\")\n",
" losses = [model.NewIntVar(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n",
" obj = model.new_int_var(0, num_slabs * max_loss, \"obj\")\n",
" losses = [model.new_int_var(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n",
" for s in all_slabs:\n",
" model.AddElement(loads[s], loss_array, losses[s])\n",
" model.Add(obj == sum(losses))\n",
" model.Minimize(obj)\n",
" model.add_element(loads[s], loss_array, losses[s])\n",
" model.add(obj == sum(losses))\n",
" model.minimize(obj)\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" objective_printer = cp_model.ObjectiveSolutionPrinter()\n",
" status = solver.Solve(model, objective_printer)\n",
" status = solver.solve(model, objective_printer)\n",
"\n",
" ### Output the solution.\n",
" if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n",
" print(\n",
" \"Loss = %i, time = %f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" % (solver.objective_value, solver.wall_time, solver.num_conflicts)\n",
" )\n",
" else:\n",
" print(\"No solution\")\n",
@@ -448,11 +450,11 @@
" # Create the model and the decision variables.\n",
" model = cp_model.CpModel()\n",
" assign = [\n",
" [model.NewBoolVar(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n",
" [model.new_bool_var(\"assign_%i_to_slab_%i\" % (o, s)) for s in all_slabs]\n",
" for o in all_orders\n",
" ]\n",
" loads = [model.NewIntVar(0, max_capacity, \"load_%i\" % s) for s in all_slabs]\n",
" losses = [model.NewIntVar(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n",
" loads = [model.new_int_var(0, max_capacity, \"load_%i\" % s) for s in all_slabs]\n",
" losses = [model.new_int_var(0, max_loss, \"loss_%i\" % s) for s in all_slabs]\n",
"\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(\n",
" capacities, colors, widths, loss_array\n",
@@ -461,20 +463,20 @@
" valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
"\n",
" for s in all_slabs:\n",
" model.AddAllowedAssignments(\n",
" model.add_allowed_assignments(\n",
" [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs\n",
" )\n",
"\n",
" # Orders are assigned to one slab.\n",
" for o in all_orders:\n",
" model.AddExactlyOne(assign[o])\n",
" model.add_exactly_one(assign[o])\n",
"\n",
" # Redundant constraint (sum of loads == sum of widths).\n",
" model.Add(sum(loads) == sum(widths))\n",
" model.add(sum(loads) == sum(widths))\n",
"\n",
" # Symmetry breaking.\n",
" for s in range(num_slabs - 1):\n",
" model.Add(loads[s] >= loads[s + 1])\n",
" model.add(loads[s] >= loads[s + 1])\n",
"\n",
" # Collect equivalent orders.\n",
" if break_symmetries:\n",
@@ -520,20 +522,20 @@
" positions = {}\n",
" for p in ordered_equivalent_orders:\n",
" if p[0] not in positions:\n",
" positions[p[0]] = model.NewIntVar(\n",
" positions[p[0]] = model.new_int_var(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n",
" )\n",
" model.AddMapDomain(positions[p[0]], assign[p[0]])\n",
" model.add_map_domain(positions[p[0]], assign[p[0]])\n",
" if p[1] not in positions:\n",
" positions[p[1]] = model.NewIntVar(\n",
" positions[p[1]] = model.new_int_var(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n",
" )\n",
" model.AddMapDomain(positions[p[1]], assign[p[1]])\n",
" model.add_map_domain(positions[p[1]], assign[p[1]])\n",
" # Finally add the symmetry breaking constraint.\n",
" model.Add(positions[p[0]] <= positions[p[1]])\n",
" model.add(positions[p[0]] <= positions[p[1]])\n",
"\n",
" # Objective.\n",
" model.Minimize(sum(losses))\n",
" model.minimize(sum(losses))\n",
"\n",
" print(\"Model created\")\n",
"\n",
@@ -543,13 +545,13 @@
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" ### Output the solution.\n",
" if status == cp_model.OPTIMAL:\n",
" print(\n",
" \"Loss = %i, time = %.2f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" % (solver.objective_value, solver.wall_time, solver.num_conflicts)\n",
" )\n",
" else:\n",
" print(\"No solution\")\n",
@@ -589,21 +591,21 @@
"\n",
" # create model and decision variables.\n",
" model = cp_model.CpModel()\n",
" selected = [model.NewBoolVar(\"selected_%i\" % i) for i in all_valid_slabs]\n",
" selected = [model.new_bool_var(\"selected_%i\" % i) for i in all_valid_slabs]\n",
"\n",
" for order_id in all_orders:\n",
" model.Add(\n",
" model.add(\n",
" sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id])\n",
" == 1\n",
" )\n",
"\n",
" # Redundant constraint (sum of loads == sum of widths).\n",
" model.Add(\n",
" model.add(\n",
" sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)\n",
" )\n",
"\n",
" # Objective.\n",
" model.Minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n",
" model.minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n",
"\n",
" print(\"Model created\")\n",
"\n",
@@ -612,13 +614,13 @@
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" ### Output the solution.\n",
" if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n",
" print(\n",
" \"Loss = %i, time = %.2f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" % (solver.objective_value, solver.wall_time, solver.num_conflicts)\n",
" )\n",
" else:\n",
" print(\"No solution\")\n",

View File

@@ -111,15 +111,15 @@
" grid = {}\n",
" for i in line:\n",
" for j in line:\n",
" grid[(i, j)] = model.NewIntVar(1, line_size, \"grid %i %i\" % (i, j))\n",
" grid[(i, j)] = model.new_int_var(1, line_size, \"grid %i %i\" % (i, j))\n",
"\n",
" # AllDifferent on rows.\n",
" for i in line:\n",
" model.AddAllDifferent(grid[(i, j)] for j in line)\n",
" model.add_all_different(grid[(i, j)] for j in line)\n",
"\n",
" # AllDifferent on columns.\n",
" for j in line:\n",
" model.AddAllDifferent(grid[(i, j)] for i in line)\n",
" model.add_all_different(grid[(i, j)] for i in line)\n",
"\n",
" # AllDifferent on cells.\n",
" for i in cell:\n",
@@ -129,20 +129,20 @@
" for dj in cell:\n",
" one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)])\n",
"\n",
" model.AddAllDifferent(one_cell)\n",
" model.add_all_different(one_cell)\n",
"\n",
" # Initial values.\n",
" for i in line:\n",
" for j in line:\n",
" if initial_grid[i][j]:\n",
" model.Add(grid[(i, j)] == initial_grid[i][j])\n",
" model.add(grid[(i, j)] == initial_grid[i][j])\n",
"\n",
" # Solve and print out the solution.\n",
" # Solves and prints out the solution.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
" if status == cp_model.OPTIMAL:\n",
" for i in line:\n",
" print([int(solver.Value(grid[(i, j)])) for j in line])\n",
" print([int(solver.value(grid[(i, j)])) for j in line])\n",
"\n",
"\n",
"solve_sudoku()\n",

View File

@@ -314,12 +314,12 @@
" assign = {}\n",
" for task in all_tasks:\n",
" for slot in all_slots:\n",
" assign[(task, slot)] = model.NewBoolVar(\"x[%i][%i]\" % (task, slot))\n",
" count = model.NewIntVar(0, nslots, \"count\")\n",
" slot_used = [model.NewBoolVar(\"slot_used[%i]\" % s) for s in all_slots]\n",
" assign[(task, slot)] = model.new_bool_var(\"x[%i][%i]\" % (task, slot))\n",
" count = model.new_int_var(0, nslots, \"count\")\n",
" slot_used = [model.new_bool_var(\"slot_used[%i]\" % s) for s in all_slots]\n",
"\n",
" for task in all_tasks:\n",
" model.Add(\n",
" model.add(\n",
" sum(\n",
" assign[(task, slot)] for slot in all_slots if available[task][slot] == 1\n",
" )\n",
@@ -327,38 +327,38 @@
" )\n",
"\n",
" for slot in all_slots:\n",
" model.Add(\n",
" model.add(\n",
" sum(\n",
" assign[(task, slot)] for task in all_tasks if available[task][slot] == 1\n",
" )\n",
" <= capacity\n",
" )\n",
" model.AddBoolOr(\n",
" model.add_bool_or(\n",
" [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1]\n",
" ).OnlyEnforceIf(slot_used[slot])\n",
" ).only_enforce_if(slot_used[slot])\n",
" for task in all_tasks:\n",
" if available[task][slot] == 1:\n",
" model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not())\n",
" model.add_implication(~slot_used[slot], ~assign[(task, slot)])\n",
" else:\n",
" model.Add(assign[(task, slot)] == 0)\n",
" model.add(assign[(task, slot)] == 0)\n",
"\n",
" model.Add(count == sum(slot_used))\n",
" model.add(count == sum(slot_used))\n",
" # Redundant constraint. This instance is easier if we add this constraint.\n",
" # model.Add(count >= (nslots + capacity - 1) // capacity)\n",
" # model.add(count >= (nslots + capacity - 1) // capacity)\n",
"\n",
" model.Minimize(count)\n",
" model.minimize(count)\n",
"\n",
" # Create a solver and solve the problem.\n",
" solver = cp_model.CpSolver()\n",
" # Uses the portfolion of heuristics.\n",
" solver.parameters.log_search_progress = True\n",
" solver.parameters.num_search_workers = 16\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" print(\"Statistics\")\n",
" print(\" - status =\", solver.StatusName(status))\n",
" print(\" - optimal solution =\", solver.ObjectiveValue())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - status =\", solver.status_name(status))\n",
" print(\" - optimal solution =\", solver.objective_value)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -97,13 +97,13 @@
" def on_solution_callback(self):\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" % (self.__solution_count, self.wall_time, self.objective_value)\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
"def tasks_and_workers_assignment_sat():\n",
" \"\"\"Solve the assignment problem.\"\"\"\n",
" \"\"\"solve the assignment problem.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
" # CP-SAT solver is integer only.\n",
@@ -121,71 +121,71 @@
" x = {}\n",
" for i in all_workers:\n",
" for j in all_groups:\n",
" x[i, j] = model.NewBoolVar(\"x[%i,%i]\" % (i, j))\n",
" x[i, j] = model.new_bool_var(\"x[%i,%i]\" % (i, j))\n",
"\n",
" ## y_kj is 1 if task k is assigned to group j\n",
" y = {}\n",
" for k in all_tasks:\n",
" for j in all_groups:\n",
" y[k, j] = model.NewBoolVar(\"x[%i,%i]\" % (k, j))\n",
" y[k, j] = model.new_bool_var(\"x[%i,%i]\" % (k, j))\n",
"\n",
" # Constraints\n",
"\n",
" # Each task k is assigned to a group and only one.\n",
" for k in all_tasks:\n",
" model.Add(sum(y[k, j] for j in all_groups) == 1)\n",
" model.add(sum(y[k, j] for j in all_groups) == 1)\n",
"\n",
" # Each worker i is assigned to a group and only one.\n",
" for i in all_workers:\n",
" model.Add(sum(x[i, j] for j in all_groups) == 1)\n",
" model.add(sum(x[i, j] for j in all_groups) == 1)\n",
"\n",
" # cost per group\n",
" # Cost per group\n",
" sum_of_costs = sum(task_cost)\n",
" averages = []\n",
" num_workers_in_group = []\n",
" scaled_sum_of_costs_in_group = []\n",
" scaling = 1000 # We introduce scaling to deal with floating point average.\n",
" for j in all_groups:\n",
" n = model.NewIntVar(1, num_workers, \"num_workers_in_group_%i\" % j)\n",
" model.Add(n == sum(x[i, j] for i in all_workers))\n",
" c = model.NewIntVar(0, sum_of_costs * scaling, \"sum_of_costs_of_group_%i\" % j)\n",
" model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks))\n",
" a = model.NewIntVar(0, sum_of_costs * scaling, \"average_cost_of_group_%i\" % j)\n",
" model.AddDivisionEquality(a, c, n)\n",
" n = model.new_int_var(1, num_workers, \"num_workers_in_group_%i\" % j)\n",
" model.add(n == sum(x[i, j] for i in all_workers))\n",
" c = model.new_int_var(0, sum_of_costs * scaling, \"sum_of_costs_of_group_%i\" % j)\n",
" model.add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks))\n",
" a = model.new_int_var(0, sum_of_costs * scaling, \"average_cost_of_group_%i\" % j)\n",
" model.add_division_equality(a, c, n)\n",
"\n",
" averages.append(a)\n",
" num_workers_in_group.append(n)\n",
" scaled_sum_of_costs_in_group.append(c)\n",
"\n",
" # All workers are assigned.\n",
" model.Add(sum(num_workers_in_group) == num_workers)\n",
" model.add(sum(num_workers_in_group) == num_workers)\n",
"\n",
" # Objective.\n",
" obj = model.NewIntVar(0, sum_of_costs * scaling, \"obj\")\n",
" model.AddMaxEquality(obj, averages)\n",
" model.Minimize(obj)\n",
" obj = model.new_int_var(0, sum_of_costs * scaling, \"obj\")\n",
" model.add_max_equality(obj, averages)\n",
" model.minimize(obj)\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.max_time_in_seconds = 60 * 60 * 2\n",
" objective_printer = ObjectivePrinter()\n",
" status = solver.Solve(model, objective_printer)\n",
" print(solver.ResponseStats())\n",
" status = solver.solve(model, objective_printer)\n",
" print(solver.response_stats())\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" for j in all_groups:\n",
" print(\"Group %i\" % j)\n",
" for i in all_workers:\n",
" if solver.BooleanValue(x[i, j]):\n",
" if solver.boolean_value(x[i, j]):\n",
" print(\" - worker %i\" % i)\n",
" for k in all_tasks:\n",
" if solver.BooleanValue(y[k, j]):\n",
" if solver.boolean_value(y[k, j]):\n",
" print(\" - task %i with cost %i\" % (k, task_cost[k]))\n",
" print(\n",
" \" - sum_of_costs = %i\"\n",
" % (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling)\n",
" % (solver.value(scaled_sum_of_costs_in_group[j]) // scaling)\n",
" )\n",
" print(\" - average cost = %f\" % (solver.Value(averages[j]) * 1.0 / scaling))\n",
" print(\" - average cost = %f\" % (solver.value(averages[j]) * 1.0 / scaling))\n",
"\n",
"\n",
"tasks_and_workers_assignment_sat()\n",

View File

@@ -151,17 +151,17 @@
" if i == j:\n",
" continue\n",
"\n",
" lit = model.NewBoolVar(\"%i follows %i\" % (j, i))\n",
" lit = model.new_bool_var(\"%i follows %i\" % (j, i))\n",
" arcs.append((i, j, lit))\n",
" arc_literals[i, j] = lit\n",
"\n",
" obj_vars.append(lit)\n",
" obj_coeffs.append(DISTANCE_MATRIX[i][j])\n",
"\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
" # Minimize weighted sum of arcs. Because this s\n",
" model.Minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
" model.minimize(sum(obj_vars[i] * obj_coeffs[i] for i in range(len(obj_vars))))\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
@@ -169,8 +169,8 @@
" # To benefit from the linearization of the circuit constraint.\n",
" solver.parameters.linearization_level = 2\n",
"\n",
" solver.Solve(model)\n",
" print(solver.ResponseStats())\n",
" solver.solve(model)\n",
" print(solver.response_stats())\n",
"\n",
" current_node = 0\n",
" str_route = \"%i\" % current_node\n",
@@ -180,7 +180,7 @@
" for i in all_nodes:\n",
" if i == current_node:\n",
" continue\n",
" if solver.BooleanValue(arc_literals[current_node, i]):\n",
" if solver.boolean_value(arc_literals[current_node, i]):\n",
" str_route += \" -> %i\" % i\n",
" route_distance += DISTANCE_MATRIX[current_node][i]\n",
" current_node = i\n",

View File

@@ -116,13 +116,13 @@
" for i in range(self.__num_vendors):\n",
" print(\n",
" \" - vendor %i: \" % i,\n",
" self.__possible_schedules[self.Value(self.__selected_schedules[i])],\n",
" self.__possible_schedules[self.value(self.__selected_schedules[i])],\n",
" )\n",
" print()\n",
"\n",
" for j in range(self.__num_hours):\n",
" print(\" - # workers on day%2i: \" % j, end=\" \")\n",
" print(self.Value(self.__hours_stat[j]), end=\" \")\n",
" print(self.value(self.__hours_stat[j]), end=\" \")\n",
" print()\n",
" print()\n",
"\n",
@@ -169,38 +169,40 @@
" all_hours = range(num_hours)\n",
"\n",
" #\n",
" # declare variables\n",
" # Declare variables\n",
" #\n",
" x = {}\n",
"\n",
" for v in all_vendors:\n",
" tmp = []\n",
" for h in all_hours:\n",
" x[v, h] = model.NewIntVar(0, num_work_types, \"x[%i,%i]\" % (v, h))\n",
" x[v, h] = model.new_int_var(0, num_work_types, \"x[%i,%i]\" % (v, h))\n",
" tmp.append(x[v, h])\n",
" selected_schedule = model.NewIntVar(0, num_possible_schedules - 1, \"s[%i]\" % v)\n",
" hours = model.NewIntVar(0, num_hours, \"h[%i]\" % v)\n",
" selected_schedule = model.new_int_var(\n",
" 0, num_possible_schedules - 1, \"s[%i]\" % v\n",
" )\n",
" hours = model.new_int_var(0, num_hours, \"h[%i]\" % v)\n",
" selected_schedules.append(selected_schedule)\n",
" vendors_stat.append(hours)\n",
" tmp.append(selected_schedule)\n",
" tmp.append(hours)\n",
"\n",
" model.AddAllowedAssignments(tmp, possible_schedules)\n",
" model.add_allowed_assignments(tmp, possible_schedules)\n",
"\n",
" #\n",
" # Statistics and constraints for each hour\n",
" #\n",
" for h in all_hours:\n",
" workers = model.NewIntVar(0, 1000, \"workers[%i]\" % h)\n",
" model.Add(workers == sum(x[v, h] for v in all_vendors))\n",
" workers = model.new_int_var(0, 1000, \"workers[%i]\" % h)\n",
" model.add(workers == sum(x[v, h] for v in all_vendors))\n",
" hours_stat.append(workers)\n",
" model.Add(workers * max_traffic_per_vendor >= traffic[h])\n",
" model.add(workers * max_traffic_per_vendor >= traffic[h])\n",
"\n",
" #\n",
" # Redundant constraint: sort selected_schedules\n",
" #\n",
" for v in range(num_vendors - 1):\n",
" model.Add(selected_schedules[v] <= selected_schedules[v + 1])\n",
" model.add(selected_schedules[v] <= selected_schedules[v + 1])\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
@@ -213,13 +215,13 @@
" hours_stat,\n",
" min_vendors,\n",
" )\n",
" status = solver.Solve(model, solution_printer)\n",
" print(\"Status = %s\" % solver.StatusName(status))\n",
" status = solver.solve(model, solution_printer)\n",
" print(\"Status = %s\" % solver.status_name(status))\n",
"\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
" print(\" - number of solutions found: %i\" % solution_printer.solution_count())\n",
"\n",
"\n",

View File

@@ -124,7 +124,7 @@
"\n",
" def on_solution_callback(self):\n",
" current_time = time.time()\n",
" objective = self.ObjectiveValue()\n",
" objective = self.objective_value\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, current_time - self.__start_time, objective)\n",
@@ -134,10 +134,10 @@
" for t in range(self.__num_tables):\n",
" print(\"Table %d: \" % t)\n",
" for g in range(self.__num_guests):\n",
" if self.Value(self.__seats[(t, g)]):\n",
" if self.value(self.__seats[(t, g)]):\n",
" print(\" \" + self.__names[g])\n",
"\n",
" def num_solutions(self):\n",
" def num_solutions(self) -> int:\n",
" return self.__solution_count\n",
"\n",
"\n",
@@ -216,12 +216,12 @@
" seats = {}\n",
" for t in all_tables:\n",
" for g in all_guests:\n",
" seats[(t, g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g, t))\n",
" seats[(t, g)] = model.new_bool_var(\"guest %i seats on table %i\" % (g, t))\n",
"\n",
" colocated = {}\n",
" for g1 in range(num_guests - 1):\n",
" for g2 in range(g1 + 1, num_guests):\n",
" colocated[(g1, g2)] = model.NewBoolVar(\n",
" colocated[(g1, g2)] = model.new_bool_var(\n",
" \"guest %i seats with guest %i\" % (g1, g2)\n",
" )\n",
"\n",
@@ -229,12 +229,12 @@
" for g1 in range(num_guests - 1):\n",
" for g2 in range(g1 + 1, num_guests):\n",
" for t in all_tables:\n",
" same_table[(g1, g2, t)] = model.NewBoolVar(\n",
" same_table[(g1, g2, t)] = model.new_bool_var(\n",
" \"guest %i seats with guest %i on table %i\" % (g1, g2, t)\n",
" )\n",
"\n",
" # Objective\n",
" model.Maximize(\n",
" model.maximize(\n",
" sum(\n",
" connections[g1][g2] * colocated[g1, g2]\n",
" for g1 in range(num_guests - 1)\n",
@@ -249,35 +249,35 @@
"\n",
" # Everybody seats at one table.\n",
" for g in all_guests:\n",
" model.Add(sum(seats[(t, g)] for t in all_tables) == 1)\n",
" model.add(sum(seats[(t, g)] for t in all_tables) == 1)\n",
"\n",
" # Tables have a max capacity.\n",
" for t in all_tables:\n",
" model.Add(sum(seats[(t, g)] for g in all_guests) <= table_capacity)\n",
" model.add(sum(seats[(t, g)] for g in all_guests) <= table_capacity)\n",
"\n",
" # Link colocated with seats\n",
" for g1 in range(num_guests - 1):\n",
" for g2 in range(g1 + 1, num_guests):\n",
" for t in all_tables:\n",
" # Link same_table and seats.\n",
" model.AddBoolOr(\n",
" model.add_bool_or(\n",
" [\n",
" seats[(t, g1)].Not(),\n",
" seats[(t, g2)].Not(),\n",
" ~seats[(t, g1)],\n",
" ~seats[(t, g2)],\n",
" same_table[(g1, g2, t)],\n",
" ]\n",
" )\n",
" model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)])\n",
" model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)])\n",
" model.add_implication(same_table[(g1, g2, t)], seats[(t, g1)])\n",
" model.add_implication(same_table[(g1, g2, t)], seats[(t, g2)])\n",
"\n",
" # Link colocated and same_table.\n",
" model.Add(\n",
" model.add(\n",
" sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1, g2)]\n",
" )\n",
"\n",
" # Min known neighbors rule.\n",
" for g in all_guests:\n",
" model.Add(\n",
" model.add(\n",
" sum(\n",
" same_table[(g, g2, t)]\n",
" for g2 in range(g + 1, num_guests)\n",
@@ -294,17 +294,17 @@
" )\n",
"\n",
" # Symmetry breaking. First guest seats on the first table.\n",
" model.Add(seats[(0, 0)] == 1)\n",
" model.add(seats[(0, 0)] == 1)\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = WeddingChartPrinter(seats, names, num_tables, num_guests)\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" print(\" - conflicts : %i\" % solver.num_conflicts)\n",
" print(\" - branches : %i\" % solver.num_branches)\n",
" print(\" - wall time : %f s\" % solver.wall_time)\n",
" print(\" - num solutions: %i\" % solution_printer.num_solutions())\n",
"\n",
"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a random Weighted Latency problem with the CP-SAT solver.\n"
"solve a random Weighted Latency problem with the CP-SAT solver.\n"
]
},
{
@@ -128,10 +128,12 @@
"\n",
" # because of the manhattan distance, the sum of distances is bounded by this.\n",
" horizon = _GRID_SIZE.value * 2 * _NUM_NODES.value\n",
" times = [model.NewIntVar(0, horizon, f\"x_{i}\") for i in range(_NUM_NODES.value + 1)]\n",
" times = [\n",
" model.new_int_var(0, horizon, f\"x_{i}\") for i in range(_NUM_NODES.value + 1)\n",
" ]\n",
"\n",
" # Node 0 is the start node.\n",
" model.Add(times[0] == 0)\n",
" model.add(times[0] == 0)\n",
"\n",
" # Create the circuit constraint.\n",
" arcs = []\n",
@@ -141,29 +143,29 @@
" continue\n",
" # We use a manhattan distance between nodes.\n",
" distance = abs(x[i] - x[j]) + abs(y[i] - y[j])\n",
" lit = model.NewBoolVar(f\"{i}_to_{j}\")\n",
" lit = model.new_bool_var(f\"{i}_to_{j}\")\n",
" arcs.append((i, j, lit))\n",
"\n",
" # Add transitions between nodes.\n",
" # add transitions between nodes.\n",
" if i == 0:\n",
" # Initial transition\n",
" model.Add(times[j] == distance).OnlyEnforceIf(lit)\n",
" model.add(times[j] == distance).only_enforce_if(lit)\n",
" elif j != 0:\n",
" # We do not care for the last transition.\n",
" model.Add(times[j] == times[i] + distance).OnlyEnforceIf(lit)\n",
" model.AddCircuit(arcs)\n",
" model.add(times[j] == times[i] + distance).only_enforce_if(lit)\n",
" model.add_circuit(arcs)\n",
"\n",
" model.Minimize(cp_model.LinearExpr.WeightedSum(times, profits))\n",
" model.minimize(cp_model.LinearExpr.weighted_sum(times, profits))\n",
"\n",
" if _PROTO_FILE.value:\n",
" model.ExportToFile(_PROTO_FILE.value)\n",
" model.export_to_file(_PROTO_FILE.value)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" solver.parameters.log_search_progress = True\n",
" solver.Solve(model)\n",
" solver.solve(model)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",

View File

@@ -113,77 +113,77 @@
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" red = model.NewIntVar(1, 5, \"red\")\n",
" green = model.NewIntVar(1, 5, \"green\")\n",
" yellow = model.NewIntVar(1, 5, \"yellow\")\n",
" blue = model.NewIntVar(1, 5, \"blue\")\n",
" ivory = model.NewIntVar(1, 5, \"ivory\")\n",
" red = model.new_int_var(1, 5, \"red\")\n",
" green = model.new_int_var(1, 5, \"green\")\n",
" yellow = model.new_int_var(1, 5, \"yellow\")\n",
" blue = model.new_int_var(1, 5, \"blue\")\n",
" ivory = model.new_int_var(1, 5, \"ivory\")\n",
"\n",
" englishman = model.NewIntVar(1, 5, \"englishman\")\n",
" spaniard = model.NewIntVar(1, 5, \"spaniard\")\n",
" japanese = model.NewIntVar(1, 5, \"japanese\")\n",
" ukrainian = model.NewIntVar(1, 5, \"ukrainian\")\n",
" norwegian = model.NewIntVar(1, 5, \"norwegian\")\n",
" englishman = model.new_int_var(1, 5, \"englishman\")\n",
" spaniard = model.new_int_var(1, 5, \"spaniard\")\n",
" japanese = model.new_int_var(1, 5, \"japanese\")\n",
" ukrainian = model.new_int_var(1, 5, \"ukrainian\")\n",
" norwegian = model.new_int_var(1, 5, \"norwegian\")\n",
"\n",
" dog = model.NewIntVar(1, 5, \"dog\")\n",
" snails = model.NewIntVar(1, 5, \"snails\")\n",
" fox = model.NewIntVar(1, 5, \"fox\")\n",
" zebra = model.NewIntVar(1, 5, \"zebra\")\n",
" horse = model.NewIntVar(1, 5, \"horse\")\n",
" dog = model.new_int_var(1, 5, \"dog\")\n",
" snails = model.new_int_var(1, 5, \"snails\")\n",
" fox = model.new_int_var(1, 5, \"fox\")\n",
" zebra = model.new_int_var(1, 5, \"zebra\")\n",
" horse = model.new_int_var(1, 5, \"horse\")\n",
"\n",
" tea = model.NewIntVar(1, 5, \"tea\")\n",
" coffee = model.NewIntVar(1, 5, \"coffee\")\n",
" water = model.NewIntVar(1, 5, \"water\")\n",
" milk = model.NewIntVar(1, 5, \"milk\")\n",
" fruit_juice = model.NewIntVar(1, 5, \"fruit juice\")\n",
" tea = model.new_int_var(1, 5, \"tea\")\n",
" coffee = model.new_int_var(1, 5, \"coffee\")\n",
" water = model.new_int_var(1, 5, \"water\")\n",
" milk = model.new_int_var(1, 5, \"milk\")\n",
" fruit_juice = model.new_int_var(1, 5, \"fruit juice\")\n",
"\n",
" old_gold = model.NewIntVar(1, 5, \"old gold\")\n",
" kools = model.NewIntVar(1, 5, \"kools\")\n",
" chesterfields = model.NewIntVar(1, 5, \"chesterfields\")\n",
" lucky_strike = model.NewIntVar(1, 5, \"lucky strike\")\n",
" parliaments = model.NewIntVar(1, 5, \"parliaments\")\n",
" old_gold = model.new_int_var(1, 5, \"old gold\")\n",
" kools = model.new_int_var(1, 5, \"kools\")\n",
" chesterfields = model.new_int_var(1, 5, \"chesterfields\")\n",
" lucky_strike = model.new_int_var(1, 5, \"lucky strike\")\n",
" parliaments = model.new_int_var(1, 5, \"parliaments\")\n",
"\n",
" model.AddAllDifferent(red, green, yellow, blue, ivory)\n",
" model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian)\n",
" model.AddAllDifferent(dog, snails, fox, zebra, horse)\n",
" model.AddAllDifferent(tea, coffee, water, milk, fruit_juice)\n",
" model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold)\n",
" model.add_all_different(red, green, yellow, blue, ivory)\n",
" model.add_all_different(englishman, spaniard, japanese, ukrainian, norwegian)\n",
" model.add_all_different(dog, snails, fox, zebra, horse)\n",
" model.add_all_different(tea, coffee, water, milk, fruit_juice)\n",
" model.add_all_different(parliaments, kools, chesterfields, lucky_strike, old_gold)\n",
"\n",
" model.Add(englishman == red)\n",
" model.Add(spaniard == dog)\n",
" model.Add(coffee == green)\n",
" model.Add(ukrainian == tea)\n",
" model.Add(green == ivory + 1)\n",
" model.Add(old_gold == snails)\n",
" model.Add(kools == yellow)\n",
" model.Add(milk == 3)\n",
" model.Add(norwegian == 1)\n",
" model.add(englishman == red)\n",
" model.add(spaniard == dog)\n",
" model.add(coffee == green)\n",
" model.add(ukrainian == tea)\n",
" model.add(green == ivory + 1)\n",
" model.add(old_gold == snails)\n",
" model.add(kools == yellow)\n",
" model.add(milk == 3)\n",
" model.add(norwegian == 1)\n",
"\n",
" diff_fox_chesterfields = model.NewIntVar(-4, 4, \"diff_fox_chesterfields\")\n",
" model.Add(diff_fox_chesterfields == fox - chesterfields)\n",
" model.AddAbsEquality(1, diff_fox_chesterfields)\n",
" diff_fox_chesterfields = model.new_int_var(-4, 4, \"diff_fox_chesterfields\")\n",
" model.add(diff_fox_chesterfields == fox - chesterfields)\n",
" model.add_abs_equality(1, diff_fox_chesterfields)\n",
"\n",
" diff_horse_kools = model.NewIntVar(-4, 4, \"diff_horse_kools\")\n",
" model.Add(diff_horse_kools == horse - kools)\n",
" model.AddAbsEquality(1, diff_horse_kools)\n",
" diff_horse_kools = model.new_int_var(-4, 4, \"diff_horse_kools\")\n",
" model.add(diff_horse_kools == horse - kools)\n",
" model.add_abs_equality(1, diff_horse_kools)\n",
"\n",
" model.Add(lucky_strike == fruit_juice)\n",
" model.Add(japanese == parliaments)\n",
" model.add(lucky_strike == fruit_juice)\n",
" model.add(japanese == parliaments)\n",
"\n",
" diff_norwegian_blue = model.NewIntVar(-4, 4, \"diff_norwegian_blue\")\n",
" model.Add(diff_norwegian_blue == norwegian - blue)\n",
" model.AddAbsEquality(1, diff_norwegian_blue)\n",
" diff_norwegian_blue = model.new_int_var(-4, 4, \"diff_norwegian_blue\")\n",
" model.add(diff_norwegian_blue == norwegian - blue)\n",
" model.add_abs_equality(1, diff_norwegian_blue)\n",
"\n",
" # Solve and print out the solution.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" people = [englishman, spaniard, japanese, ukrainian, norwegian]\n",
" water_drinker = [p for p in people if solver.Value(p) == solver.Value(water)][0]\n",
" zebra_owner = [p for p in people if solver.Value(p) == solver.Value(zebra)][0]\n",
" print(\"The\", water_drinker.Name(), \"drinks water.\")\n",
" print(\"The\", zebra_owner.Name(), \"owns the zebra.\")\n",
" water_drinker = [p for p in people if solver.value(p) == solver.value(water)][0]\n",
" zebra_owner = [p for p in people if solver.value(p) == solver.value(zebra)][0]\n",
" print(\"The\", water_drinker.name, \"drinks water.\")\n",
" print(\"The\", zebra_owner.name, \"owns the zebra.\")\n",
" else:\n",
" print(\"No solutions to the zebra problem, this is unusual!\")\n",
"\n",

View File

@@ -130,6 +130,7 @@
" for arc in range(smcf.num_arcs()):\n",
" # Can ignore arcs leading out of source or into sink.\n",
" if smcf.tail(arc) != source and smcf.head(arc) != sink:\n",
"\n",
" # Arcs in the solution have a flow value of 1. Their start and end nodes\n",
" # give an assignment of worker to task.\n",
" if smcf.flow(arc) > 0:\n",

View File

@@ -161,6 +161,7 @@
" and smcf.tail(arc) != 12\n",
" and smcf.head(arc) != sink\n",
" ):\n",
"\n",
" # Arcs in the solution will have a flow value of 1.\n",
" # There start and end nodes give an assignment of worker to task.\n",
" if smcf.flow(arc) > 0:\n",

View File

@@ -165,39 +165,39 @@
"\n",
" # Group1\n",
" constraint_g1 = solver.Constraint(1, 1)\n",
" for i in range(len(group1)):\n",
" for index, _ in enumerate(group1):\n",
" # a*b can be transformed into 0 <= a + b - 2*p <= 1 with p in [0,1]\n",
" # p is True if a AND b, False otherwise\n",
" constraint = solver.Constraint(0, 1)\n",
" constraint.SetCoefficient(work[group1[i][0]], 1)\n",
" constraint.SetCoefficient(work[group1[i][1]], 1)\n",
" p = solver.BoolVar(f\"g1_p{i}\")\n",
" constraint.SetCoefficient(work[group1[index][0]], 1)\n",
" constraint.SetCoefficient(work[group1[index][1]], 1)\n",
" p = solver.BoolVar(f\"g1_p{index}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g1.SetCoefficient(p, 1)\n",
"\n",
" # Group2\n",
" constraint_g2 = solver.Constraint(1, 1)\n",
" for i in range(len(group2)):\n",
" for index, _ in enumerate(group2):\n",
" # a*b can be transformed into 0 <= a + b - 2*p <= 1 with p in [0,1]\n",
" # p is True if a AND b, False otherwise\n",
" constraint = solver.Constraint(0, 1)\n",
" constraint.SetCoefficient(work[group2[i][0]], 1)\n",
" constraint.SetCoefficient(work[group2[i][1]], 1)\n",
" p = solver.BoolVar(f\"g2_p{i}\")\n",
" constraint.SetCoefficient(work[group2[index][0]], 1)\n",
" constraint.SetCoefficient(work[group2[index][1]], 1)\n",
" p = solver.BoolVar(f\"g2_p{index}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g2.SetCoefficient(p, 1)\n",
"\n",
" # Group3\n",
" constraint_g3 = solver.Constraint(1, 1)\n",
" for i in range(len(group3)):\n",
" for index, _ in enumerate(group3):\n",
" # a*b can be transformed into 0 <= a + b - 2*p <= 1 with p in [0,1]\n",
" # p is True if a AND b, False otherwise\n",
" constraint = solver.Constraint(0, 1)\n",
" constraint.SetCoefficient(work[group3[i][0]], 1)\n",
" constraint.SetCoefficient(work[group3[i][1]], 1)\n",
" p = solver.BoolVar(f\"g3_p{i}\")\n",
" constraint.SetCoefficient(work[group3[index][0]], 1)\n",
" constraint.SetCoefficient(work[group3[index][1]], 1)\n",
" p = solver.BoolVar(f\"g3_p{index}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g3.SetCoefficient(p, 1)\n",

View File

@@ -83,43 +83,66 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.init.python import init\n",
"from ortools.linear_solver import pywraplp\n",
"\n",
"\n",
"def main():\n",
" print(\"Google OR-Tools version:\", init.OrToolsVersion.version_string())\n",
"\n",
" # Create the linear solver with the GLOP backend.\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\n",
" print(\"Could not create solver GLOP\")\n",
" return\n",
"\n",
" # Create the variables x and y.\n",
" x = solver.NumVar(0, 1, \"x\")\n",
" y = solver.NumVar(0, 2, \"y\")\n",
" x_var = solver.NumVar(0, 1, \"x\")\n",
" y_var = solver.NumVar(0, 2, \"y\")\n",
"\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # Create a linear constraint, 0 <= x + y <= 2.\n",
" ct = solver.Constraint(0, 2, \"ct\")\n",
" ct.SetCoefficient(x, 1)\n",
" ct.SetCoefficient(y, 1)\n",
" infinity = solver.infinity()\n",
" # Create a linear constraint, x + y <= 2.\n",
" constraint = solver.Constraint(-infinity, 2, \"ct\")\n",
" constraint.SetCoefficient(x_var, 1)\n",
" constraint.SetCoefficient(y_var, 1)\n",
"\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Create the objective function, 3 * x + y.\n",
" objective = solver.Objective()\n",
" objective.SetCoefficient(x, 3)\n",
" objective.SetCoefficient(y, 1)\n",
" objective.SetCoefficient(x_var, 3)\n",
" objective.SetCoefficient(y_var, 1)\n",
" objective.SetMaximization()\n",
"\n",
" print(f\"Solving with {solver.SolverVersion()}\")\n",
" solver.Solve()\n",
" result_status = solver.Solve()\n",
"\n",
" print(f\"Status: {result_status}\")\n",
" if result_status != pywraplp.Solver.OPTIMAL:\n",
" print(\"The problem does not have an optimal solution!\")\n",
" if result_status == pywraplp.Solver.FEASIBLE:\n",
" print(\"A potentially suboptimal solution was found\")\n",
" else:\n",
" print(\"The solver could not solve the problem.\")\n",
" return\n",
"\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", objective.Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
" print(\"x =\", x_var.solution_value())\n",
" print(\"y =\", y_var.solution_value())\n",
"\n",
" print(\"Advanced usage:\")\n",
" print(f\"Problem solved in {solver.wall_time():d} milliseconds\")\n",
" print(f\"Problem solved in {solver.iterations():d} iterations\")\n",
"\n",
"\n",
"init.CppBridge.init_logging(\"basic_example.py\")\n",
"cpp_flags = init.CppFlags()\n",
"cpp_flags.stderrthreshold = True\n",
"cpp_flags.log_prefix = False\n",
"init.CppBridge.set_flags(cpp_flags)\n",
"main()\n",
"\n"
]

View File

@@ -90,7 +90,7 @@
"from ortools.linear_solver.python import model_builder\n",
"\n",
"\n",
"def create_data_model():\n",
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:\n",
" \"\"\"Create the data for the example.\"\"\"\n",
"\n",
" items_str = \"\"\"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"MIP example that uses a variable array."
"MIP example that uses a variable array.\n"
]
},
{

View File

@@ -144,7 +144,8 @@
" for i in data[\"all_items\"]:\n",
" if x[i, b].solution_value() > 0:\n",
" print(\n",
" f\"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}\"\n",
" f\"Item {i} weight: {data['weights'][i]} value:\"\n",
" f\" {data['values'][i]}\"\n",
" )\n",
" bin_weight += data[\"weights\"][i]\n",
" bin_value += data[\"values\"][i]\n",

View File

@@ -87,7 +87,7 @@
"\n",
"\n",
"def main():\n",
" # Create the mip solver with the SCIP backend.\n",
" # Create the mip solver with the CP-SAT backend.\n",
" solver = pywraplp.Solver.CreateSolver(\"SAT\")\n",
" if not solver:\n",
" return\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve assignment problem for given group of workers."
"Solves an assignment problem for given group of workers."
]
},
{
@@ -86,7 +86,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # Data\n",
" costs = [\n",
" [90, 76, 75, 70, 50, 74],\n",
@@ -137,48 +137,48 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n",
" x[worker, task] = model.new_bool_var(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
" for worker in range(num_workers):\n",
" model.AddAtMostOne(x[worker, task] for task in range(num_tasks))\n",
" model.add_at_most_one(x[worker, task] for task in range(num_tasks))\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" model.AddExactlyOne(x[worker, task] for worker in range(num_workers))\n",
" model.add_exactly_one(x[worker, task] for worker in range(num_workers))\n",
"\n",
" # Create variables for each worker, indicating whether they work on some task.\n",
" work = {}\n",
" for worker in range(num_workers):\n",
" work[worker] = model.NewBoolVar(f\"work[{worker}]\")\n",
" work[worker] = model.new_bool_var(f\"work[{worker}]\")\n",
"\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" model.Add(work[worker] == sum(x[worker, task] for task in range(num_tasks)))\n",
" model.add(work[worker] == sum(x[worker, task] for task in range(num_tasks)))\n",
"\n",
" # Define the allowed groups of worders\n",
" model.AddAllowedAssignments([work[0], work[1], work[2], work[3]], group1)\n",
" model.AddAllowedAssignments([work[4], work[5], work[6], work[7]], group2)\n",
" model.AddAllowedAssignments([work[8], work[9], work[10], work[11]], group3)\n",
" model.add_allowed_assignments([work[0], work[1], work[2], work[3]], group1)\n",
" model.add_allowed_assignments([work[4], work[5], work[6], work[7]], group2)\n",
" model.add_allowed_assignments([work[8], work[9], work[10], work[11]], group3)\n",
"\n",
" # Objective\n",
" objective_terms = []\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" objective_terms.append(costs[worker][task] * x[worker, task])\n",
" model.Minimize(sum(objective_terms))\n",
" model.minimize(sum(objective_terms))\n",
"\n",
" # Solve\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n",
" print(f\"Total cost = {solver.objective_value}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" if solver.boolean_value(x[worker, task]):\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a simple assignment problem with CP-SAT."
"Solves a simple assignment problem with CP-SAT.\n"
]
},
{
@@ -90,7 +90,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # Data\n",
" data_str = \"\"\"\n",
" worker task cost\n",
@@ -122,28 +122,28 @@
" model = cp_model.CpModel()\n",
"\n",
" # Variables\n",
" x = model.NewBoolVarSeries(name=\"x\", index=data.index)\n",
" x = model.new_bool_var_series(name=\"x\", index=data.index)\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
" for unused_name, tasks in data.groupby(\"worker\"):\n",
" model.AddAtMostOne(x[tasks.index])\n",
" model.add_at_most_one(x[tasks.index])\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for unused_name, workers in data.groupby(\"task\"):\n",
" model.AddExactlyOne(x[workers.index])\n",
" model.add_exactly_one(x[workers.index])\n",
"\n",
" # Objective\n",
" model.Minimize(data.cost.dot(x))\n",
" model.minimize(data.cost.dot(x))\n",
"\n",
" # Solve\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n",
" selected = data.loc[solver.BooleanValues(x).loc[lambda x: x].index]\n",
" print(f\"Total cost = {solver.objective_value}\\n\")\n",
" selected = data.loc[solver.boolean_values(x).loc[lambda x: x].index]\n",
" for unused_index, row in selected.iterrows():\n",
" print(f\"{row.task} assigned to {row.worker} with a cost of {row.cost}\")\n",
" elif status == cp_model.INFEASIBLE:\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a simple assignment problem."
"Solves a simple assignment problem."
]
},
{
@@ -86,7 +86,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # Data\n",
" costs = [\n",
" [90, 76, 75, 70, 50, 74, 12, 68],\n",
@@ -114,37 +114,37 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n",
" x[worker, task] = model.new_bool_var(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
" for worker in range(num_workers):\n",
" model.Add(\n",
" model.add(\n",
" sum(task_sizes[task] * x[worker, task] for task in range(num_tasks))\n",
" <= total_size_max\n",
" )\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" model.AddExactlyOne(x[worker, task] for worker in range(num_workers))\n",
" model.add_exactly_one(x[worker, task] for worker in range(num_workers))\n",
"\n",
" # Objective\n",
" objective_terms = []\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" objective_terms.append(costs[worker][task] * x[worker, task])\n",
" model.Minimize(sum(objective_terms))\n",
" model.minimize(sum(objective_terms))\n",
"\n",
" # Solve\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n",
" print(f\"Total cost = {solver.objective_value}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" if solver.boolean_value(x[worker, task]):\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a simple assignment problem."
"Solves a simple assignment problem."
]
},
{
@@ -86,7 +86,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # Data\n",
" costs = [\n",
" [90, 76, 75, 70],\n",
@@ -111,47 +111,47 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = model.NewBoolVar(f\"x[{worker},{task}]\")\n",
" x[worker, task] = model.new_bool_var(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
" for worker in range(num_workers):\n",
" model.AddAtMostOne(x[worker, task] for task in range(num_tasks))\n",
" model.add_at_most_one(x[worker, task] for task in range(num_tasks))\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" model.AddExactlyOne(x[worker, task] for worker in range(num_workers))\n",
" model.add_exactly_one(x[worker, task] for worker in range(num_workers))\n",
"\n",
" # Each team takes at most two tasks.\n",
" team1_tasks = []\n",
" for worker in team1:\n",
" for task in range(num_tasks):\n",
" team1_tasks.append(x[worker, task])\n",
" model.Add(sum(team1_tasks) <= team_max)\n",
" model.add(sum(team1_tasks) <= team_max)\n",
"\n",
" team2_tasks = []\n",
" for worker in team2:\n",
" for task in range(num_tasks):\n",
" team2_tasks.append(x[worker, task])\n",
" model.Add(sum(team2_tasks) <= team_max)\n",
" model.add(sum(team2_tasks) <= team_max)\n",
"\n",
" # Objective\n",
" objective_terms = []\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" objective_terms.append(costs[worker][task] * x[worker, task])\n",
" model.Minimize(sum(objective_terms))\n",
" model.minimize(sum(objective_terms))\n",
"\n",
" # Solve\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n",
" print(f\"Total cost = {solver.objective_value}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" if solver.boolean_value(x[worker, task]):\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",

View File

@@ -86,37 +86,37 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" \"\"\"Showcases assumptions.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" x = model.NewIntVar(0, 10, \"x\")\n",
" y = model.NewIntVar(0, 10, \"y\")\n",
" z = model.NewIntVar(0, 10, \"z\")\n",
" a = model.NewBoolVar(\"a\")\n",
" b = model.NewBoolVar(\"b\")\n",
" c = model.NewBoolVar(\"c\")\n",
" x = model.new_int_var(0, 10, \"x\")\n",
" y = model.new_int_var(0, 10, \"y\")\n",
" z = model.new_int_var(0, 10, \"z\")\n",
" a = model.new_bool_var(\"a\")\n",
" b = model.new_bool_var(\"b\")\n",
" c = model.new_bool_var(\"c\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(x > y).OnlyEnforceIf(a)\n",
" model.Add(y > z).OnlyEnforceIf(b)\n",
" model.Add(z > x).OnlyEnforceIf(c)\n",
" model.add(x > y).only_enforce_if(a)\n",
" model.add(y > z).only_enforce_if(b)\n",
" model.add(z > x).only_enforce_if(c)\n",
"\n",
" # Add assumptions\n",
" model.AddAssumptions([a, b, c])\n",
" model.add_assumptions([a, b, c])\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" if status == cp_model.INFEASIBLE:\n",
" print(\n",
" \"SufficientAssumptionsForInfeasibility = \"\n",
" f\"{solver.SufficientAssumptionsForInfeasibility()}\"\n",
" \"sufficient_assumptions_for_infeasibility = \"\n",
" f\"{solver.sufficient_assumptions_for_infeasibility()}\"\n",
" )\n",
"\n",
"\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a simple bin packing problem using CP-SAT."
"Solves a simple bin packing problem using CP-SAT.\n"
]
},
{
@@ -124,7 +124,7 @@
" return items, bins\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" items, bins = create_data_model()\n",
"\n",
" # Create the model.\n",
@@ -135,49 +135,52 @@
" items_x_bins = pd.MultiIndex.from_product(\n",
" [items.index, bins.index], names=[\"item\", \"bin\"]\n",
" )\n",
" x = model.NewBoolVarSeries(name=\"x\", index=items_x_bins)\n",
" x = model.new_bool_var_series(name=\"x\", index=items_x_bins)\n",
"\n",
" # y[j] = 1 if bin j is used.\n",
" y = model.NewBoolVarSeries(name=\"y\", index=bins.index)\n",
" y = model.new_bool_var_series(name=\"y\", index=bins.index)\n",
"\n",
" # Constraints\n",
" # Each item must be in exactly one bin.\n",
" for unused_name, all_copies in x.groupby(\"item\"):\n",
" model.AddExactlyOne(x[all_copies.index])\n",
" model.add_exactly_one(x[all_copies.index])\n",
"\n",
" # The amount packed in each bin cannot exceed its capacity.\n",
" for selected_bin in bins.index:\n",
" items_in_bin = x.xs(selected_bin, level=\"bin\")\n",
" model.Add(\n",
" model.add(\n",
" items_in_bin.dot(items.weight)\n",
" <= bins.loc[selected_bin].capacity * y[selected_bin]\n",
" )\n",
"\n",
" # Objective: minimize the number of bins used.\n",
" model.Minimize(y.sum())\n",
" model.minimize(y.sum())\n",
"\n",
" # Create the solver and solve the model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Number of bins used = {solver.ObjectiveValue()}\")\n",
" print(f\"Number of bins used = {solver.objective_value}\")\n",
"\n",
" x_values = solver.BooleanValues(x)\n",
" y_values = solver.BooleanValues(y)\n",
" x_values = solver.boolean_values(x)\n",
" y_values = solver.boolean_values(y)\n",
" active_bins = y_values.loc[lambda x: x].index\n",
"\n",
" for b in active_bins:\n",
" print(f\"Bin {b}\")\n",
" items_in_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x].index\n",
" for item in items_in_bin:\n",
" items_in_active_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x].index\n",
" for item in items_in_active_bin:\n",
" print(f\" Item {item} - weight {items.loc[item].weight}\")\n",
" print(f\" Packed items weight: {items.loc[items_in_bin].sum().to_string()}\")\n",
" print(\n",
" \" Packed items weight:\"\n",
" f\" {items.loc[items_in_active_bin].sum().to_string()}\"\n",
" )\n",
" print()\n",
"\n",
" print(f\"Total packed weight: {items.weight.sum()}\")\n",
" print()\n",
" print(f\"Time = {solver.WallTime()} seconds\")\n",
" print(f\"Time = {solver.wall_time} seconds\")\n",
" elif status == cp_model.INFEASIBLE:\n",
" print(\"No solution found\")\n",
" else:\n",

View File

@@ -87,7 +87,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def BinpackingProblemSat():\n",
"def binpacking_problem_sat():\n",
" \"\"\"Solves a bin-packing problem using the CP-SAT solver.\"\"\"\n",
" # Data.\n",
" bin_capacity = 100\n",
@@ -107,46 +107,46 @@
" for i in all_items:\n",
" num_copies = items[i][1]\n",
" for b in all_bins:\n",
" x[(i, b)] = model.NewIntVar(0, num_copies, f\"x[{i},{b}]\")\n",
" x[(i, b)] = model.new_int_var(0, num_copies, f\"x[{i},{b}]\")\n",
"\n",
" # Load variables.\n",
" load = [model.NewIntVar(0, bin_capacity, f\"load[{b}]\") for b in all_bins]\n",
" load = [model.new_int_var(0, bin_capacity, f\"load[{b}]\") for b in all_bins]\n",
"\n",
" # Slack variables.\n",
" slacks = [model.NewBoolVar(f\"slack[{b}]\") for b in all_bins]\n",
" slacks = [model.new_bool_var(f\"slack[{b}]\") for b in all_bins]\n",
"\n",
" # Links load and x.\n",
" for b in all_bins:\n",
" model.Add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items))\n",
" model.add(load[b] == sum(x[(i, b)] * items[i][0] for i in all_items))\n",
"\n",
" # Place all items.\n",
" for i in all_items:\n",
" model.Add(sum(x[(i, b)] for b in all_bins) == items[i][1])\n",
" model.add(sum(x[(i, b)] for b in all_bins) == items[i][1])\n",
"\n",
" # Links load and slack through an equivalence relation.\n",
" safe_capacity = bin_capacity - slack_capacity\n",
" for b in all_bins:\n",
" # slack[b] => load[b] <= safe_capacity.\n",
" model.Add(load[b] <= safe_capacity).OnlyEnforceIf(slacks[b])\n",
" model.add(load[b] <= safe_capacity).only_enforce_if(slacks[b])\n",
" # not(slack[b]) => load[b] > safe_capacity.\n",
" model.Add(load[b] > safe_capacity).OnlyEnforceIf(slacks[b].Not())\n",
" model.add(load[b] > safe_capacity).only_enforce_if(~slacks[b])\n",
"\n",
" # Maximize sum of slacks.\n",
" model.Maximize(sum(slacks))\n",
" model.maximize(sum(slacks))\n",
"\n",
" # Solves and prints out the solution.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" print(f\"Solve status: {solver.StatusName(status)}\")\n",
" status = solver.solve(model)\n",
" print(f\"solve status: {solver.status_name(status)}\")\n",
" if status == cp_model.OPTIMAL:\n",
" print(f\"Optimal objective value: {solver.ObjectiveValue()}\")\n",
" print(f\"Optimal objective value: {solver.objective_value}\")\n",
" print(\"Statistics\")\n",
" print(f\" - conflicts : {solver.NumConflicts()}\")\n",
" print(f\" - branches : {solver.NumBranches()}\")\n",
" print(f\" - wall time : {solver.WallTime()}s\")\n",
" print(f\" - conflicts : {solver.num_conflicts}\")\n",
" print(f\" - branches : {solver.num_branches}\")\n",
" print(f\" - wall time : {solver.wall_time}s\")\n",
"\n",
"\n",
"BinpackingProblemSat()\n",
"binpacking_problem_sat()\n",
"\n"
]
}

View File

@@ -0,0 +1,150 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# bool_and_int_var_product_sample_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/sat/bool_and_int_var_product_sample_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/sat/samples/bool_and_int_var_product_sample_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Code sample that encodes the product of a Boolean and an integer variable.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
"\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
"\n",
"def build_product_var(\n",
" model: cp_model.CpModel, b: cp_model.IntVar, x: cp_model.IntVar, name: str\n",
") -> cp_model.IntVar:\n",
" \"\"\"Builds the product of a Boolean variable and an integer variable.\"\"\"\n",
" p = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_flat_intervals(x.proto.domain).union_with(\n",
" cp_model.Domain(0, 0)\n",
" ),\n",
" name,\n",
" )\n",
" model.add(p == x).only_enforce_if(b)\n",
" model.add(p == 0).only_enforce_if(~b)\n",
" return p\n",
"\n",
"\n",
"def bool_and_int_var_product_sample_sat():\n",
" \"\"\"Encoding of the product of two Boolean variables.\n",
"\n",
" p == x * y, which is the same as p <=> x and y\n",
" \"\"\"\n",
" model = cp_model.CpModel()\n",
" b = model.new_bool_var(\"b\")\n",
" x = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_values([1, 2, 3, 5, 6, 7, 9, 10]), \"x\"\n",
" )\n",
" p = build_product_var(model, b, x, \"p\")\n",
"\n",
" # Search for x and b values in increasing order.\n",
" model.add_decision_strategy(\n",
" [b, x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" )\n",
"\n",
" # Create a solver and solve.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = VarArraySolutionPrinter([x, b, p])\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solver.parameters.search_branching = cp_model.FIXED_SEARCH\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"bool_and_int_var_product_sample_sat()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -87,16 +87,19 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def BoolOrSampleSat():\n",
"def bool_or_sample_sat():\n",
" model = cp_model.CpModel()\n",
"\n",
" x = model.NewBoolVar(\"x\")\n",
" y = model.NewBoolVar(\"y\")\n",
" x = model.new_bool_var(\"x\")\n",
" y = model.new_bool_var(\"y\")\n",
"\n",
" model.AddBoolOr([x, y.Not()])\n",
" model.add_bool_or([x, y.negated()])\n",
" # The [] is not mandatory.\n",
" # ~y is equivalent to y.negated()\n",
" model.add_bool_or(x, ~y)\n",
"\n",
"\n",
"BoolOrSampleSat()\n",
"bool_or_sample_sat()\n",
"\n"
]
}

View File

@@ -87,31 +87,31 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def BooleanProductSampleSat():\n",
"def boolean_product_sample_sat():\n",
" \"\"\"Encoding of the product of two Boolean variables.\n",
"\n",
" p == x * y, which is the same as p <=> x and y\n",
" \"\"\"\n",
" model = cp_model.CpModel()\n",
" x = model.NewBoolVar(\"x\")\n",
" y = model.NewBoolVar(\"y\")\n",
" p = model.NewBoolVar(\"p\")\n",
" x = model.new_bool_var(\"x\")\n",
" y = model.new_bool_var(\"y\")\n",
" p = model.new_bool_var(\"p\")\n",
"\n",
" # x and y implies p, rewrite as not(x and y) or p.\n",
" model.AddBoolOr(x.Not(), y.Not(), p)\n",
" model.add_bool_or(~x, ~y, p)\n",
"\n",
" # p implies x and y, expanded into two implications.\n",
" model.AddImplication(p, x)\n",
" model.AddImplication(p, y)\n",
" model.add_implication(p, x)\n",
" model.add_implication(p, y)\n",
"\n",
" # Create a solver and solve.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = cp_model.VarArraySolutionPrinter([x, y, p])\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"BooleanProductSampleSat()\n",
"boolean_product_sample_sat()\n",
"\n"
]
}

View File

@@ -90,46 +90,41 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" self.__solution_count += 1\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" return self.__solution_count\n",
"\n",
"\n",
"def ChannelingSampleSat():\n",
"def channeling_sample_sat():\n",
" \"\"\"Demonstrates how to link integer constraints together.\"\"\"\n",
"\n",
" # Create the CP-SAT model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Declare our two primary variables.\n",
" x = model.NewIntVar(0, 10, \"x\")\n",
" y = model.NewIntVar(0, 10, \"y\")\n",
" x = model.new_int_var(0, 10, \"x\")\n",
" y = model.new_int_var(0, 10, \"y\")\n",
"\n",
" # Declare our intermediate boolean variable.\n",
" b = model.NewBoolVar(\"b\")\n",
" b = model.new_bool_var(\"b\")\n",
"\n",
" # Implement b == (x >= 5).\n",
" model.Add(x >= 5).OnlyEnforceIf(b)\n",
" model.Add(x < 5).OnlyEnforceIf(b.Not())\n",
" model.add(x >= 5).only_enforce_if(b)\n",
" model.add(x < 5).only_enforce_if(~b)\n",
"\n",
" # Create our two half-reified constraints.\n",
" # First, b implies (y == 10 - x).\n",
" model.Add(y == 10 - x).OnlyEnforceIf(b)\n",
" model.add(y == 10 - x).only_enforce_if(b)\n",
" # Second, not(b) implies y == 0.\n",
" model.Add(y == 0).OnlyEnforceIf(b.Not())\n",
" model.add(y == 0).only_enforce_if(~b)\n",
"\n",
" # Search for x values in increasing order.\n",
" model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
" model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
@@ -141,10 +136,10 @@
"\n",
" # Search and print out all solutions.\n",
" solution_printer = VarArraySolutionPrinter([x, y, b])\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"ChannelingSampleSat()\n",
"channeling_sample_sat()\n",
"\n"
]
}

View File

@@ -86,44 +86,44 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def CloneModelSampleSat():\n",
"def clone_model_sample_sat():\n",
" \"\"\"Showcases cloning a model.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" model.Maximize(x + 2 * y + 3 * z)\n",
" model.maximize(x + 2 * y + 3 * z)\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Optimal value of the original model: {}\".format(solver.ObjectiveValue()))\n",
" print(\"Optimal value of the original model: {}\".format(solver.objective_value))\n",
"\n",
" # Clone the model.\n",
" copy = model.Clone()\n",
" # Clones the model.\n",
" copy = model.clone()\n",
"\n",
" copy_x = copy.GetIntVarFromProtoIndex(x.Index())\n",
" copy_y = copy.GetIntVarFromProtoIndex(y.Index())\n",
" copy_x = copy.get_int_var_from_proto_index(x.index)\n",
" copy_y = copy.get_int_var_from_proto_index(y.index)\n",
"\n",
" copy.Add(copy_x + copy_y <= 1)\n",
" copy.add(copy_x + copy_y <= 1)\n",
"\n",
" status = solver.Solve(copy)\n",
" status = solver.solve(copy)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Optimal value of the modified model: {}\".format(solver.ObjectiveValue()))\n",
" print(\"Optimal value of the modified model: {}\".format(solver.objective_value))\n",
"\n",
"\n",
"CloneModelSampleSat()\n",
"clone_model_sample_sat()\n",
"\n"
]
}

View File

@@ -94,38 +94,39 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" self.__solution_count += 1\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Solve the CP+IS+FUN==TRUE cryptarithm.\"\"\"\n",
"def main() -> None:\n",
" \"\"\"solve the CP+IS+FUN==TRUE cryptarithm.\"\"\"\n",
" # Constraint programming engine\n",
" model = cp_model.CpModel()\n",
"\n",
" base = 10\n",
"\n",
" c = model.NewIntVar(1, base - 1, \"C\")\n",
" p = model.NewIntVar(0, base - 1, \"P\")\n",
" i = model.NewIntVar(1, base - 1, \"I\")\n",
" s = model.NewIntVar(0, base - 1, \"S\")\n",
" f = model.NewIntVar(1, base - 1, \"F\")\n",
" u = model.NewIntVar(0, base - 1, \"U\")\n",
" n = model.NewIntVar(0, base - 1, \"N\")\n",
" t = model.NewIntVar(1, base - 1, \"T\")\n",
" r = model.NewIntVar(0, base - 1, \"R\")\n",
" e = model.NewIntVar(0, base - 1, \"E\")\n",
" c = model.new_int_var(1, base - 1, \"C\")\n",
" p = model.new_int_var(0, base - 1, \"P\")\n",
" i = model.new_int_var(1, base - 1, \"I\")\n",
" s = model.new_int_var(0, base - 1, \"S\")\n",
" f = model.new_int_var(1, base - 1, \"F\")\n",
" u = model.new_int_var(0, base - 1, \"U\")\n",
" n = model.new_int_var(0, base - 1, \"N\")\n",
" t = model.new_int_var(1, base - 1, \"T\")\n",
" r = model.new_int_var(0, base - 1, \"R\")\n",
" e = model.new_int_var(0, base - 1, \"E\")\n",
"\n",
" # We need to group variables in a list to use the constraint AllDifferent.\n",
" letters = [c, p, i, s, f, u, n, t, r, e]\n",
@@ -134,10 +135,10 @@
" assert base >= len(letters)\n",
"\n",
" # Define constraints.\n",
" model.AddAllDifferent(letters)\n",
" model.add_all_different(letters)\n",
"\n",
" # CP + IS + FUN = TRUE\n",
" model.Add(\n",
" model.add(\n",
" c * base + p + i * base + s + f * base * base + u * base + n\n",
" == t * base * base * base + r * base * base + u * base + e\n",
" )\n",
@@ -148,15 +149,15 @@
" # Enumerate all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" # Solve.\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" status : {solver.StatusName(status)}\")\n",
" print(f\" conflicts: {solver.NumConflicts()}\")\n",
" print(f\" branches : {solver.NumBranches()}\")\n",
" print(f\" wall time: {solver.WallTime()} s\")\n",
" print(f\" sol found: {solution_printer.solution_count()}\")\n",
" print(f\" status : {solver.status_name(status)}\")\n",
" print(f\" conflicts: {solver.num_conflicts}\")\n",
" print(f\" branches : {solver.num_branches}\")\n",
" print(f\" wall time: {solver.wall_time} s\")\n",
" print(f\" sol found: {solution_printer.solution_count}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -86,42 +86,42 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" var_upper_bound = max(50, 45, 37)\n",
" x = model.NewIntVar(0, var_upper_bound, \"x\")\n",
" y = model.NewIntVar(0, var_upper_bound, \"y\")\n",
" z = model.NewIntVar(0, var_upper_bound, \"z\")\n",
" x = model.new_int_var(0, var_upper_bound, \"x\")\n",
" y = model.new_int_var(0, var_upper_bound, \"y\")\n",
" z = model.new_int_var(0, var_upper_bound, \"z\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(2 * x + 7 * y + 3 * z <= 50)\n",
" model.Add(3 * x - 5 * y + 7 * z <= 45)\n",
" model.Add(5 * x + 2 * y - 6 * z <= 37)\n",
" model.add(2 * x + 7 * y + 3 * z <= 50)\n",
" model.add(3 * x - 5 * y + 7 * z <= 45)\n",
" model.add(5 * x + 2 * y - 6 * z <= 37)\n",
"\n",
" model.Maximize(2 * x + 2 * y + 3 * z)\n",
" model.maximize(2 * x + 2 * y + 3 * z)\n",
"\n",
" # Creates a solver and solves the model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"Maximum of objective function: {solver.ObjectiveValue()}\\n\")\n",
" print(f\"x = {solver.Value(x)}\")\n",
" print(f\"y = {solver.Value(y)}\")\n",
" print(f\"z = {solver.Value(z)}\")\n",
" print(f\"Maximum of objective function: {solver.objective_value}\\n\")\n",
" print(f\"x = {solver.value(x)}\")\n",
" print(f\"y = {solver.value(y)}\")\n",
" print(f\"z = {solver.value(z)}\")\n",
" else:\n",
" print(\"No solution found.\")\n",
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" status : {solver.StatusName(status)}\")\n",
" print(f\" conflicts: {solver.NumConflicts()}\")\n",
" print(f\" branches : {solver.NumBranches()}\")\n",
" print(f\" wall time: {solver.WallTime()} s\")\n",
" print(f\" status : {solver.status_name(status)}\")\n",
" print(f\" conflicts: {solver.num_conflicts}\")\n",
" print(f\" branches : {solver.num_branches}\")\n",
" print(f\" wall time: {solver.wall_time} s\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solve a simple scheduling problem with a variable work load."
"Solves a scheduling problem with a min and max profile for the work load.\n"
]
},
{
@@ -90,22 +90,38 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:\n",
" \"\"\"Creates the two dataframes that describes the model.\"\"\"\n",
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:\n",
" \"\"\"Creates the dataframes that describes the model.\"\"\"\n",
"\n",
" capacity_str: str = \"\"\"\n",
" start_hour capacity\n",
" max_load_str: str = \"\"\"\n",
" start_hour max_load\n",
" 0 0\n",
" 2 0\n",
" 4 1\n",
" 6 3\n",
" 8 6\n",
" 4 3\n",
" 6 6\n",
" 8 8\n",
" 10 12\n",
" 12 8\n",
" 14 12\n",
" 16 10\n",
" 18 4\n",
" 20 2\n",
" 18 6\n",
" 20 4\n",
" 22 0\n",
" \"\"\"\n",
"\n",
" min_load_str: str = \"\"\"\n",
" start_hour min_load\n",
" 0 0\n",
" 2 0\n",
" 4 0\n",
" 6 0\n",
" 8 3\n",
" 10 3\n",
" 12 1\n",
" 14 3\n",
" 16 3\n",
" 18 1\n",
" 20 1\n",
" 22 0\n",
" \"\"\"\n",
"\n",
@@ -143,33 +159,78 @@
" t30 90 4 2\n",
" \"\"\"\n",
"\n",
" capacity_df = pd.read_table(io.StringIO(capacity_str), sep=r\"\\s+\")\n",
" max_load_df = pd.read_table(io.StringIO(max_load_str), sep=r\"\\s+\")\n",
" min_load_df = pd.read_table(io.StringIO(min_load_str), sep=r\"\\s+\")\n",
" tasks_df = pd.read_table(io.StringIO(tasks_str), index_col=0, sep=r\"\\s+\")\n",
" return capacity_df, tasks_df\n",
" return max_load_df, min_load_df, tasks_df\n",
"\n",
"\n",
"def main():\n",
"def check_solution(\n",
" tasks: list[tuple[int, int, int]],\n",
" min_load_df: pd.DataFrame,\n",
" max_load_df: pd.DataFrame,\n",
" period_length: int,\n",
" horizon: int,\n",
") -> bool:\n",
" \"\"\"Checks the solution validity against the min and max load constraints.\"\"\"\n",
" minutes_per_hour = 60\n",
" actual_load_profile = [0 for _ in range(horizon)]\n",
" min_load_profile = [0 for _ in range(horizon)]\n",
" max_load_profile = [0 for _ in range(horizon)]\n",
"\n",
" # The complexity of the checker is linear in the number of time points, and\n",
" # should be improved.\n",
" for task in tasks:\n",
" for t in range(task[1]):\n",
" actual_load_profile[task[0] + t] += task[2]\n",
" for row in max_load_df.itertuples():\n",
" for t in range(period_length):\n",
" max_load_profile[row.start_hour * minutes_per_hour + t] = row.max_load\n",
" for row in min_load_df.itertuples():\n",
" for t in range(period_length):\n",
" min_load_profile[row.start_hour * minutes_per_hour + t] = row.min_load\n",
"\n",
" for time in range(horizon):\n",
" if actual_load_profile[time] > max_load_profile[time]:\n",
" print(\n",
" f\"actual load {actual_load_profile[time]} at time {time} is greater\"\n",
" f\" than max load {max_load_profile[time]}\"\n",
" )\n",
" return False\n",
" if actual_load_profile[time] < min_load_profile[time]:\n",
" print(\n",
" f\"actual load {actual_load_profile[time]} at time {time} is\"\n",
" f\" less than min load {min_load_profile[time]}\"\n",
" )\n",
" return False\n",
" return True\n",
"\n",
"\n",
"def main(_) -> None:\n",
" \"\"\"Create the model and solves it.\"\"\"\n",
" capacity_df, tasks_df = create_data_model()\n",
" max_load_df, min_load_df, tasks_df = create_data_model()\n",
"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Get the max capacity from the capacity dataframe.\n",
" max_capacity = capacity_df.capacity.max()\n",
" print(f\"Max capacity = {max_capacity}\")\n",
" max_load = max_load_df.max_load.max()\n",
" print(f\"Max capacity = {max_load}\")\n",
" print(f\"#tasks = {len(tasks_df)}\")\n",
"\n",
" minutes_per_period: int = 120\n",
" minutes_per_hour: int = 60\n",
" horizon: int = 24 * 60\n",
"\n",
" # Variables\n",
" starts = model.NewIntVarSeries(\n",
" name=\"starts\", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index\n",
" starts = model.new_int_var_series(\n",
" name=\"starts\",\n",
" lower_bounds=0,\n",
" upper_bounds=horizon - tasks_df.duration,\n",
" index=tasks_df.index,\n",
" )\n",
" performed = model.NewBoolVarSeries(name=\"performed\", index=tasks_df.index)\n",
" performed = model.new_bool_var_series(name=\"performed\", index=tasks_df.index)\n",
"\n",
" intervals = model.NewOptionalFixedSizeIntervalVarSeries(\n",
" intervals = model.new_optional_fixed_size_interval_var_series(\n",
" name=\"intervals\",\n",
" index=tasks_df.index,\n",
" starts=starts,\n",
@@ -177,43 +238,108 @@
" are_present=performed,\n",
" )\n",
"\n",
" # Set up the profile. We use fixed (intervals, demands) to fill in the space\n",
" # between the actual load profile and the max capacity.\n",
" time_period_intervals = model.NewFixedSizeIntervalVarSeries(\n",
" name=\"time_period_intervals\",\n",
" index=capacity_df.index,\n",
" starts=capacity_df.start_hour * minutes_per_period,\n",
" sizes=minutes_per_period,\n",
" # Set up the max profile. We use fixed (intervals, demands) to fill in the\n",
" # space between the actual max load profile and the max capacity.\n",
" time_period_max_intervals = model.new_fixed_size_interval_var_series(\n",
" name=\"time_period_max_intervals\",\n",
" index=max_load_df.index,\n",
" starts=max_load_df.start_hour * minutes_per_hour,\n",
" sizes=minutes_per_hour * 2,\n",
" )\n",
" time_period_heights = max_capacity - capacity_df.capacity\n",
" time_period_max_heights = max_load - max_load_df.max_load\n",
"\n",
" # Cumulative constraint.\n",
" model.AddCumulative(\n",
" intervals.to_list() + time_period_intervals.to_list(),\n",
" tasks_df.load.to_list() + time_period_heights.to_list(),\n",
" max_capacity,\n",
" # Cumulative constraint for the max profile.\n",
" model.add_cumulative(\n",
" intervals.to_list() + time_period_max_intervals.to_list(),\n",
" tasks_df.load.to_list() + time_period_max_heights.to_list(),\n",
" max_load,\n",
" )\n",
"\n",
" # Set up complemented intervals (from 0 to start, and from start + size to\n",
" # horizon).\n",
" prefix_intervals = model.new_optional_interval_var_series(\n",
" name=\"prefix_intervals\",\n",
" index=tasks_df.index,\n",
" starts=0,\n",
" sizes=starts,\n",
" ends=starts,\n",
" are_present=performed,\n",
" )\n",
"\n",
" suffix_intervals = model.new_optional_interval_var_series(\n",
" name=\"suffix_intervals\",\n",
" index=tasks_df.index,\n",
" starts=starts + tasks_df.duration,\n",
" sizes=horizon - starts - tasks_df.duration,\n",
" ends=horizon,\n",
" are_present=performed,\n",
" )\n",
"\n",
" # Set up the min profile. We use complemented intervals to maintain the\n",
" # complement of the work load, and fixed intervals to enforce the min\n",
" # number of active workers per time period.\n",
" #\n",
" # Note that this works only if the max load cumulative is also added to the\n",
" # model.\n",
" time_period_min_intervals = model.new_fixed_size_interval_var_series(\n",
" name=\"time_period_min_intervals\",\n",
" index=min_load_df.index,\n",
" starts=min_load_df.start_hour * minutes_per_hour,\n",
" sizes=minutes_per_hour * 2,\n",
" )\n",
" time_period_min_heights = min_load_df.min_load\n",
"\n",
" # We take into account optional intervals. The actual capacity of the min load\n",
" # cumulative is the sum of all the active demands.\n",
" sum_of_demands = sum(tasks_df.load)\n",
" complement_capacity = model.new_int_var(0, sum_of_demands, \"complement_capacity\")\n",
" model.add(complement_capacity == performed.dot(tasks_df.load))\n",
"\n",
" # Cumulative constraint for the min profile.\n",
" model.add_cumulative(\n",
" prefix_intervals.to_list()\n",
" + suffix_intervals.to_list()\n",
" + time_period_min_intervals.to_list(),\n",
" tasks_df.load.to_list()\n",
" + tasks_df.load.to_list()\n",
" + time_period_min_heights.to_list(),\n",
" complement_capacity,\n",
" )\n",
"\n",
" # Objective: maximize the value of performed intervals.\n",
" # 1 is the max priority.\n",
" max_priority = max(tasks_df.priority)\n",
" model.Maximize(sum(performed * (max_priority + 1 - tasks_df.priority)))\n",
" model.maximize(sum(performed * (max_priority + 1 - tasks_df.priority)))\n",
"\n",
" # Create the solver and solve the model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.log_search_progress = True\n",
" solver.parameters.num_workers = 8\n",
" solver.parameters.num_workers = 16\n",
" solver.parameters.max_time_in_seconds = 30.0\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" start_values = solver.Values(starts)\n",
" performed_values = solver.BooleanValues(performed)\n",
" start_values = solver.values(starts)\n",
" performed_values = solver.boolean_values(performed)\n",
" tasks: list[tuple[int, int, int]] = []\n",
" for task in tasks_df.index:\n",
" if performed_values[task]:\n",
" print(f\"task {task} starts at {start_values[task]}\")\n",
" print(\n",
" f'task {task} duration={tasks_df[\"duration\"][task]} '\n",
" f'load={tasks_df[\"load\"][task]} starts at {start_values[task]}'\n",
" )\n",
" tasks.append(\n",
" (start_values[task], tasks_df.duration[task], tasks_df.load[task])\n",
" )\n",
" else:\n",
" print(f\"task {task} is not performed\")\n",
" assert check_solution(\n",
" tasks=tasks,\n",
" min_load_df=min_load_df,\n",
" max_load_df=max_load_df,\n",
" period_length=2 * minutes_per_hour,\n",
" horizon=horizon,\n",
" )\n",
" elif status == cp_model.INFEASIBLE:\n",
" print(\"No solution found\")\n",
" else:\n",

View File

@@ -90,20 +90,15 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" self.__solution_count += 1\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" return self.__solution_count\n",
"\n",
"\n",
"def earliness_tardiness_cost_sample_sat():\n",
" \"\"\"Encode the piecewise linear expression.\"\"\"\n",
@@ -117,7 +112,7 @@
" model = cp_model.CpModel()\n",
"\n",
" # Declare our primary variable.\n",
" x = model.NewIntVar(0, 20, \"x\")\n",
" x = model.new_int_var(0, 20, \"x\")\n",
"\n",
" # Create the expression variable and implement the piecewise linear function.\n",
" #\n",
@@ -126,24 +121,24 @@
" # ed ld\n",
" #\n",
" large_constant = 1000\n",
" expr = model.NewIntVar(0, large_constant, \"expr\")\n",
" expr = model.new_int_var(0, large_constant, \"expr\")\n",
"\n",
" # First segment.\n",
" s1 = model.NewIntVar(-large_constant, large_constant, \"s1\")\n",
" model.Add(s1 == earliness_cost * (earliness_date - x))\n",
" s1 = model.new_int_var(-large_constant, large_constant, \"s1\")\n",
" model.add(s1 == earliness_cost * (earliness_date - x))\n",
"\n",
" # Second segment.\n",
" s2 = 0\n",
"\n",
" # Third segment.\n",
" s3 = model.NewIntVar(-large_constant, large_constant, \"s3\")\n",
" model.Add(s3 == lateness_cost * (x - lateness_date))\n",
" s3 = model.new_int_var(-large_constant, large_constant, \"s3\")\n",
" model.add(s3 == lateness_cost * (x - lateness_date))\n",
"\n",
" # Link together expr and x through s1, s2, and s3.\n",
" model.AddMaxEquality(expr, [s1, s2, s3])\n",
" model.add_max_equality(expr, [s1, s2, s3])\n",
"\n",
" # Search for x values in increasing order.\n",
" model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
" model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
@@ -155,7 +150,7 @@
"\n",
" # Search and print out all solutions.\n",
" solution_printer = VarArraySolutionPrinter([x, expr])\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"earliness_tardiness_cost_sample_sat()\n",

View File

@@ -0,0 +1,150 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# index_first_boolvar_true_sample_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/sat/index_first_boolvar_true_sample_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/sat/samples/index_first_boolvar_true_sample_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Compute the index of the first Boolean variable set to true.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, index: cp_model.IntVar, boolvars: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__index = index\n",
" self.__boolvars = boolvars\n",
"\n",
" def on_solution_callback(self) -> None:\n",
" line = \"\"\n",
" for v in self.__boolvars:\n",
" line += f\"{self.value(v)}\"\n",
" line += f\" -> {self.value(self.__index)}\"\n",
" print(line)\n",
"\n",
"\n",
"def index_of_first_bool_at_true_sample_sat():\n",
" \"\"\"Compute the index of the first Boolean variable set to true.\"\"\"\n",
"\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Variables\n",
" num_bool_vars = 5\n",
" bool_vars = [model.new_bool_var(f\"{i}\") for i in range(num_bool_vars)]\n",
" index = model.new_int_var(0, num_bool_vars, \"index\")\n",
"\n",
" # Channeling between the index and the Boolean variables.\n",
" model.add_min_equality(\n",
" index,\n",
" [\n",
" num_bool_vars - bool_vars[i] * (num_bool_vars - i)\n",
" for i in range(num_bool_vars)\n",
" ],\n",
" )\n",
"\n",
" # Flip bool_vars in increasing order.\n",
" model.add_decision_strategy(\n",
" bool_vars, cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" )\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
"\n",
" # Force the solver to follow the decision strategy exactly.\n",
" solver.parameters.search_branching = cp_model.FIXED_SEARCH\n",
"\n",
" # Search and print out all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solution_printer = VarArraySolutionPrinter(index, bool_vars)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"index_of_first_bool_at_true_sample_sat()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -87,32 +87,32 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def IntervalSampleSat():\n",
"def interval_sample_sat():\n",
" \"\"\"Showcases how to build interval variables.\"\"\"\n",
" model = cp_model.CpModel()\n",
" horizon = 100\n",
"\n",
" # An interval can be created from three affine expressions.\n",
" start_var = model.NewIntVar(0, horizon, \"start\")\n",
" start_var = model.new_int_var(0, horizon, \"start\")\n",
" duration = 10 # Python cp/sat code accept integer variables or constants.\n",
" end_var = model.NewIntVar(0, horizon, \"end\")\n",
" interval_var = model.NewIntervalVar(start_var, duration, end_var + 2, \"interval\")\n",
" end_var = model.new_int_var(0, horizon, \"end\")\n",
" interval_var = model.new_interval_var(start_var, duration, end_var + 2, \"interval\")\n",
"\n",
" print(f\"interval = {repr(interval_var)}\")\n",
"\n",
" # If the size is fixed, a simpler version uses the start expression and the\n",
" # size.\n",
" fixed_size_interval_var = model.NewFixedSizeIntervalVar(\n",
" fixed_size_interval_var = model.new_fixed_size_interval_var(\n",
" start_var, 10, \"fixed_size_interval_var\"\n",
" )\n",
" print(f\"fixed_size_interval_var = {repr(fixed_size_interval_var)}\")\n",
"\n",
" # A fixed interval can be created using the same API.\n",
" fixed_interval = model.NewFixedSizeIntervalVar(5, 10, \"fixed_interval\")\n",
" fixed_interval = model.new_fixed_size_interval_var(5, 10, \"fixed_interval\")\n",
" print(f\"fixed_interval = {repr(fixed_interval)}\")\n",
"\n",
"\n",
"IntervalSampleSat()\n",
"interval_sample_sat()\n",
"\n"
]
}

View File

@@ -87,15 +87,15 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def LiteralSampleSat():\n",
"def literal_sample_sat():\n",
" model = cp_model.CpModel()\n",
" x = model.NewBoolVar(\"x\")\n",
" not_x = x.Not()\n",
" x = model.new_bool_var(\"x\")\n",
" not_x = ~x\n",
" print(x)\n",
" print(not_x)\n",
"\n",
"\n",
"LiteralSampleSat()\n",
"literal_sample_sat()\n",
"\n"
]
}

View File

@@ -87,7 +87,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" \"\"\"Minimal jobshop problem.\"\"\"\n",
" # Data.\n",
" jobs_data = [ # task = (machine_id, processing_time).\n",
@@ -119,9 +119,9 @@
" for task_id, task in enumerate(job):\n",
" machine, duration = task\n",
" suffix = f\"_{job_id}_{task_id}\"\n",
" start_var = model.NewIntVar(0, horizon, \"start\" + suffix)\n",
" end_var = model.NewIntVar(0, horizon, \"end\" + suffix)\n",
" interval_var = model.NewIntervalVar(\n",
" start_var = model.new_int_var(0, horizon, \"start\" + suffix)\n",
" end_var = model.new_int_var(0, horizon, \"end\" + suffix)\n",
" interval_var = model.new_interval_var(\n",
" start_var, duration, end_var, \"interval\" + suffix\n",
" )\n",
" all_tasks[job_id, task_id] = task_type(\n",
@@ -131,26 +131,26 @@
"\n",
" # Create and add disjunctive constraints.\n",
" for machine in all_machines:\n",
" model.AddNoOverlap(machine_to_intervals[machine])\n",
" model.add_no_overlap(machine_to_intervals[machine])\n",
"\n",
" # Precedences inside a job.\n",
" for job_id, job in enumerate(jobs_data):\n",
" for task_id in range(len(job) - 1):\n",
" model.Add(\n",
" model.add(\n",
" all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end\n",
" )\n",
"\n",
" # Makespan objective.\n",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(\n",
" obj_var,\n",
" [all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)],\n",
" )\n",
" model.Minimize(obj_var)\n",
" model.minimize(obj_var)\n",
"\n",
" # Creates the solver and solve.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(\"Solution:\")\n",
@@ -161,7 +161,7 @@
" machine = task[0]\n",
" assigned_jobs[machine].append(\n",
" assigned_task_type(\n",
" start=solver.Value(all_tasks[job_id, task_id].start),\n",
" start=solver.value(all_tasks[job_id, task_id].start),\n",
" job=job_id,\n",
" index=task_id,\n",
" duration=task[1],\n",
@@ -178,13 +178,13 @@
"\n",
" for assigned_task in assigned_jobs[machine]:\n",
" name = f\"job_{assigned_task.job}_task_{assigned_task.index}\"\n",
" # Add spaces to output to align columns.\n",
" # add spaces to output to align columns.\n",
" sol_line_tasks += f\"{name:15}\"\n",
"\n",
" start = assigned_task.start\n",
" duration = assigned_task.duration\n",
" sol_tmp = f\"[{start},{start + duration}]\"\n",
" # Add spaces to output to align columns.\n",
" # add spaces to output to align columns.\n",
" sol_line += f\"{sol_tmp:15}\"\n",
"\n",
" sol_line += \"\\n\"\n",
@@ -193,16 +193,16 @@
" output += sol_line\n",
"\n",
" # Finally print the solution found.\n",
" print(f\"Optimal Schedule Length: {solver.ObjectiveValue()}\")\n",
" print(f\"Optimal Schedule Length: {solver.objective_value}\")\n",
" print(output)\n",
" else:\n",
" print(\"No solution found.\")\n",
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" - conflicts: {solver.NumConflicts()}\")\n",
" print(f\" - branches : {solver.NumBranches()}\")\n",
" print(f\" - wall time: {solver.WallTime()}s\")\n",
" print(f\" - conflicts: {solver.num_conflicts}\")\n",
" print(f\" - branches : {solver.num_branches}\")\n",
" print(f\" - wall time: {solver.wall_time}s\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -86,61 +86,61 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" data = {}\n",
" data[\"weights\"] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n",
" data[\"values\"] = [10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25]\n",
" assert len(data[\"weights\"]) == len(data[\"values\"])\n",
" data[\"num_items\"] = len(data[\"weights\"])\n",
" data[\"all_items\"] = range(data[\"num_items\"])\n",
" num_items = len(data[\"weights\"])\n",
" all_items = range(num_items)\n",
"\n",
" data[\"bin_capacities\"] = [100, 100, 100, 100, 100]\n",
" data[\"num_bins\"] = len(data[\"bin_capacities\"])\n",
" data[\"all_bins\"] = range(data[\"num_bins\"])\n",
" num_bins = len(data[\"bin_capacities\"])\n",
" all_bins = range(num_bins)\n",
"\n",
" model = cp_model.CpModel()\n",
"\n",
" # Variables.\n",
" # x[i, b] = 1 if item i is packed in bin b.\n",
" x = {}\n",
" for i in data[\"all_items\"]:\n",
" for b in data[\"all_bins\"]:\n",
" x[i, b] = model.NewBoolVar(f\"x_{i}_{b}\")\n",
" for i in all_items:\n",
" for b in all_bins:\n",
" x[i, b] = model.new_bool_var(f\"x_{i}_{b}\")\n",
"\n",
" # Constraints.\n",
" # Each item is assigned to at most one bin.\n",
" for i in data[\"all_items\"]:\n",
" model.AddAtMostOne(x[i, b] for b in data[\"all_bins\"])\n",
" for i in all_items:\n",
" model.add_at_most_one(x[i, b] for b in all_bins)\n",
"\n",
" # The amount packed in each bin cannot exceed its capacity.\n",
" for b in data[\"all_bins\"]:\n",
" model.Add(\n",
" sum(x[i, b] * data[\"weights\"][i] for i in data[\"all_items\"])\n",
" for b in all_bins:\n",
" model.add(\n",
" sum(x[i, b] * data[\"weights\"][i] for i in all_items)\n",
" <= data[\"bin_capacities\"][b]\n",
" )\n",
"\n",
" # Objective.\n",
" # Maximize total value of packed items.\n",
" # maximize total value of packed items.\n",
" objective = []\n",
" for i in data[\"all_items\"]:\n",
" for b in data[\"all_bins\"]:\n",
" objective.append(cp_model.LinearExpr.Term(x[i, b], data[\"values\"][i]))\n",
" model.Maximize(cp_model.LinearExpr.Sum(objective))\n",
" for i in all_items:\n",
" for b in all_bins:\n",
" objective.append(cp_model.LinearExpr.term(x[i, b], data[\"values\"][i]))\n",
" model.maximize(cp_model.LinearExpr.sum(objective))\n",
"\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(f\"Total packed value: {solver.ObjectiveValue()}\")\n",
" print(f\"Total packed value: {solver.objective_value}\")\n",
" total_weight = 0\n",
" for b in data[\"all_bins\"]:\n",
" for b in all_bins:\n",
" print(f\"Bin {b}\")\n",
" bin_weight = 0\n",
" bin_value = 0\n",
" for i in data[\"all_items\"]:\n",
" if solver.Value(x[i, b]) > 0:\n",
" for i in all_items:\n",
" if solver.value(x[i, b]) > 0:\n",
" print(\n",
" f\"Item {i} weight: {data['weights'][i]} value: {data['values'][i]}\"\n",
" f'Item:{i} weight:{data[\"weights\"][i]} value:{data[\"values\"][i]}'\n",
" )\n",
" bin_weight += data[\"weights\"][i]\n",
" bin_value += data[\"values\"][i]\n",

View File

@@ -86,56 +86,56 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def NoOverlapSampleSat():\n",
"def no_overlap_sample_sat():\n",
" \"\"\"No overlap sample with fixed activities.\"\"\"\n",
" model = cp_model.CpModel()\n",
" horizon = 21 # 3 weeks.\n",
"\n",
" # Task 0, duration 2.\n",
" start_0 = model.NewIntVar(0, horizon, \"start_0\")\n",
" start_0 = model.new_int_var(0, horizon, \"start_0\")\n",
" duration_0 = 2 # Python cp/sat code accepts integer variables or constants.\n",
" end_0 = model.NewIntVar(0, horizon, \"end_0\")\n",
" task_0 = model.NewIntervalVar(start_0, duration_0, end_0, \"task_0\")\n",
" end_0 = model.new_int_var(0, horizon, \"end_0\")\n",
" task_0 = model.new_interval_var(start_0, duration_0, end_0, \"task_0\")\n",
" # Task 1, duration 4.\n",
" start_1 = model.NewIntVar(0, horizon, \"start_1\")\n",
" start_1 = model.new_int_var(0, horizon, \"start_1\")\n",
" duration_1 = 4 # Python cp/sat code accepts integer variables or constants.\n",
" end_1 = model.NewIntVar(0, horizon, \"end_1\")\n",
" task_1 = model.NewIntervalVar(start_1, duration_1, end_1, \"task_1\")\n",
" end_1 = model.new_int_var(0, horizon, \"end_1\")\n",
" task_1 = model.new_interval_var(start_1, duration_1, end_1, \"task_1\")\n",
"\n",
" # Task 2, duration 3.\n",
" start_2 = model.NewIntVar(0, horizon, \"start_2\")\n",
" start_2 = model.new_int_var(0, horizon, \"start_2\")\n",
" duration_2 = 3 # Python cp/sat code accepts integer variables or constants.\n",
" end_2 = model.NewIntVar(0, horizon, \"end_2\")\n",
" task_2 = model.NewIntervalVar(start_2, duration_2, end_2, \"task_2\")\n",
" end_2 = model.new_int_var(0, horizon, \"end_2\")\n",
" task_2 = model.new_interval_var(start_2, duration_2, end_2, \"task_2\")\n",
"\n",
" # Weekends.\n",
" weekend_0 = model.NewIntervalVar(5, 2, 7, \"weekend_0\")\n",
" weekend_1 = model.NewIntervalVar(12, 2, 14, \"weekend_1\")\n",
" weekend_2 = model.NewIntervalVar(19, 2, 21, \"weekend_2\")\n",
" weekend_0 = model.new_interval_var(5, 2, 7, \"weekend_0\")\n",
" weekend_1 = model.new_interval_var(12, 2, 14, \"weekend_1\")\n",
" weekend_2 = model.new_interval_var(19, 2, 21, \"weekend_2\")\n",
"\n",
" # No Overlap constraint.\n",
" model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2])\n",
" model.add_no_overlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2])\n",
"\n",
" # Makespan objective.\n",
" obj = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(obj, [end_0, end_1, end_2])\n",
" model.Minimize(obj)\n",
" obj = model.new_int_var(0, horizon, \"makespan\")\n",
" model.add_max_equality(obj, [end_0, end_1, end_2])\n",
" model.minimize(obj)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" # Print out makespan and the start times for all tasks.\n",
" print(f\"Optimal Schedule Length: {solver.ObjectiveValue()}\")\n",
" print(f\"Task 0 starts at {solver.Value(start_0)}\")\n",
" print(f\"Task 1 starts at {solver.Value(start_1)}\")\n",
" print(f\"Task 2 starts at {solver.Value(start_2)}\")\n",
" print(f\"Optimal Schedule Length: {solver.objective_value}\")\n",
" print(f\"Task 0 starts at {solver.value(start_0)}\")\n",
" print(f\"Task 1 starts at {solver.value(start_1)}\")\n",
" print(f\"Task 2 starts at {solver.value(start_2)}\")\n",
" else:\n",
" print(f\"Solver exited with nonoptimal status: {status}\")\n",
"\n",
"\n",
"NoOverlapSampleSat()\n",
"no_overlap_sample_sat()\n",
"\n"
]
}

View File

@@ -76,7 +76,7 @@
"Non linear example.\n",
"\n",
"Finds a rectangle with maximum available area for given perimeter using\n",
"AddMultiplicationEquality().\n",
"add_multiplication_equality().\n",
"\n"
]
},
@@ -96,23 +96,23 @@
"\n",
" model = cp_model.CpModel()\n",
"\n",
" x = model.NewIntVar(0, perimeter, \"x\")\n",
" y = model.NewIntVar(0, perimeter, \"y\")\n",
" model.Add(2 * (x + y) == perimeter)\n",
" x = model.new_int_var(0, perimeter, \"x\")\n",
" y = model.new_int_var(0, perimeter, \"y\")\n",
" model.add(2 * (x + y) == perimeter)\n",
"\n",
" area = model.NewIntVar(0, perimeter * perimeter, \"s\")\n",
" model.AddMultiplicationEquality(area, x, y)\n",
" area = model.new_int_var(0, perimeter * perimeter, \"s\")\n",
" model.add_multiplication_equality(area, x, y)\n",
"\n",
" model.Maximize(area)\n",
" model.maximize(area)\n",
"\n",
" solver = cp_model.CpSolver()\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"x = {solver.Value(x)}\")\n",
" print(f\"y = {solver.Value(y)}\")\n",
" print(f\"s = {solver.Value(area)}\")\n",
" print(f\"x = {solver.value(x)}\")\n",
" print(f\"y = {solver.value(y)}\")\n",
" print(f\"s = {solver.value(area)}\")\n",
" else:\n",
" print(\"No solution found.\")\n",
"\n",

View File

@@ -91,13 +91,14 @@
"class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, queens):\n",
" def __init__(self, queens: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__queens = queens\n",
" self.__solution_count = 0\n",
" self.__start_time = time.time()\n",
"\n",
" def solution_count(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
" def on_solution_callback(self):\n",
@@ -111,7 +112,7 @@
" all_queens = range(len(self.__queens))\n",
" for i in all_queens:\n",
" for j in all_queens:\n",
" if self.Value(self.__queens[j]) == i:\n",
" if self.value(self.__queens[j]) == i:\n",
" # There is a queen in column j, row i.\n",
" print(\"Q\", end=\" \")\n",
" else:\n",
@@ -121,35 +122,35 @@
"\n",
"\n",
"\n",
"def main(board_size):\n",
"def main(board_size: int) -> None:\n",
" # Creates the solver.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" # There are `board_size` number of variables, one for a queen in each column\n",
" # of the board. The value of each variable is the row that the queen is in.\n",
" queens = [model.NewIntVar(0, board_size - 1, f\"x_{i}\") for i in range(board_size)]\n",
" queens = [model.new_int_var(0, board_size - 1, f\"x_{i}\") for i in range(board_size)]\n",
"\n",
" # Creates the constraints.\n",
" # All rows must be different.\n",
" model.AddAllDifferent(queens)\n",
" model.add_all_different(queens)\n",
"\n",
" # No two queens can be on the same diagonal.\n",
" model.AddAllDifferent(queens[i] + i for i in range(board_size))\n",
" model.AddAllDifferent(queens[i] - i for i in range(board_size))\n",
" model.add_all_different(queens[i] + i for i in range(board_size))\n",
" model.add_all_different(queens[i] - i for i in range(board_size))\n",
"\n",
" # Solve the model.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = NQueenSolutionPrinter(queens)\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" conflicts : {solver.NumConflicts()}\")\n",
" print(f\" branches : {solver.NumBranches()}\")\n",
" print(f\" wall time : {solver.WallTime()} s\")\n",
" print(f\" solutions found: {solution_printer.solution_count()}\")\n",
" print(f\" conflicts : {solver.num_conflicts}\")\n",
" print(f\" branches : {solver.num_branches}\")\n",
" print(f\" wall time : {solver.wall_time} s\")\n",
" print(f\" solutions found: {solution_printer.solution_count}\")\n",
"\n",
"\n",
"# By default, solve the 8x8 problem.\n",

View File

@@ -86,7 +86,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # Data.\n",
" num_nurses = 4\n",
" num_shifts = 3\n",
@@ -104,17 +104,17 @@
" for n in all_nurses:\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" shifts[(n, d, s)] = model.NewBoolVar(f\"shift_n{n}_d{d}_s{s}\")\n",
" shifts[(n, d, s)] = model.new_bool_var(f\"shift_n{n}_d{d}_s{s}\")\n",
"\n",
" # Each shift is assigned to exactly one nurse in the schedule period.\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses)\n",
" model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses)\n",
"\n",
" # Each nurse works at most one shift per day.\n",
" for n in all_nurses:\n",
" for d in all_days:\n",
" model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts)\n",
" model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts)\n",
"\n",
" # Try to distribute the shifts evenly, so that each nurse works\n",
" # min_shifts_per_nurse shifts. If this is not possible, because the total\n",
@@ -130,8 +130,8 @@
" for d in all_days:\n",
" for s in all_shifts:\n",
" shifts_worked.append(shifts[(n, d, s)])\n",
" model.Add(min_shifts_per_nurse <= sum(shifts_worked))\n",
" model.Add(sum(shifts_worked) <= max_shifts_per_nurse)\n",
" model.add(min_shifts_per_nurse <= sum(shifts_worked))\n",
" model.add(sum(shifts_worked) <= max_shifts_per_nurse)\n",
"\n",
" # Creates the solver and solve.\n",
" solver = cp_model.CpSolver()\n",
@@ -159,16 +159,16 @@
" for n in range(self._num_nurses):\n",
" is_working = False\n",
" for s in range(self._num_shifts):\n",
" if self.Value(self._shifts[(n, d, s)]):\n",
" if self.value(self._shifts[(n, d, s)]):\n",
" is_working = True\n",
" print(f\" Nurse {n} works shift {s}\")\n",
" if not is_working:\n",
" print(f\" Nurse {n} does not work\")\n",
" if self._solution_count >= self._solution_limit:\n",
" print(f\"Stop search after {self._solution_limit} solutions\")\n",
" self.StopSearch()\n",
" self.stop_search()\n",
"\n",
" def solution_count(self):\n",
" def solutionCount(self):\n",
" return self._solution_count\n",
"\n",
" # Display the first five solutions.\n",
@@ -177,14 +177,14 @@
" shifts, num_nurses, num_days, num_shifts, solution_limit\n",
" )\n",
"\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" - conflicts : {solver.NumConflicts()}\")\n",
" print(f\" - branches : {solver.NumBranches()}\")\n",
" print(f\" - wall time : {solver.WallTime()} s\")\n",
" print(f\" - solutions found: {solution_printer.solution_count()}\")\n",
" print(f\" - conflicts : {solver.num_conflicts}\")\n",
" print(f\" - branches : {solver.num_branches}\")\n",
" print(f\" - wall time : {solver.wall_time} s\")\n",
" print(f\" - solutions found: {solution_printer.solutionCount()}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -86,17 +86,17 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def OptionalIntervalSampleSat():\n",
"def optional_interval_sample_sat():\n",
" \"\"\"Showcases how to build optional interval variables.\"\"\"\n",
" model = cp_model.CpModel()\n",
" horizon = 100\n",
"\n",
" # An interval can be created from three affine expressions.\n",
" start_var = model.NewIntVar(0, horizon, \"start\")\n",
" start_var = model.new_int_var(0, horizon, \"start\")\n",
" duration = 10 # Python cp/sat code accept integer variables or constants.\n",
" end_var = model.NewIntVar(0, horizon, \"end\")\n",
" presence_var = model.NewBoolVar(\"presence\")\n",
" interval_var = model.NewOptionalIntervalVar(\n",
" end_var = model.new_int_var(0, horizon, \"end\")\n",
" presence_var = model.new_bool_var(\"presence\")\n",
" interval_var = model.new_optional_interval_var(\n",
" start_var, duration, end_var + 2, presence_var, \"interval\"\n",
" )\n",
"\n",
@@ -104,19 +104,19 @@
"\n",
" # If the size is fixed, a simpler version uses the start expression and the\n",
" # size.\n",
" fixed_size_interval_var = model.NewOptionalFixedSizeIntervalVar(\n",
" fixed_size_interval_var = model.new_optional_fixed_size_interval_var(\n",
" start_var, 10, presence_var, \"fixed_size_interval_var\"\n",
" )\n",
" print(f\"fixed_size_interval_var = {repr(fixed_size_interval_var)}\")\n",
"\n",
" # A fixed interval can be created using the same API.\n",
" fixed_interval = model.NewOptionalFixedSizeIntervalVar(\n",
" fixed_interval = model.new_optional_fixed_size_interval_var(\n",
" 5, 10, presence_var, \"fixed_interval\"\n",
" )\n",
" print(f\"fixed_interval = {repr(fixed_interval)}\")\n",
"\n",
"\n",
"OptionalIntervalSampleSat()\n",
"optional_interval_sample_sat()\n",
"\n"
]
}

View File

@@ -89,67 +89,64 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" self.__solution_count += 1\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" return self.__solution_count\n",
"\n",
"\n",
"def OverlappingIntervals():\n",
"def overlapping_interval_sample_sat():\n",
" \"\"\"Create the overlapping Boolean variables and enumerate all states.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
" horizon = 7\n",
"\n",
" # First interval.\n",
" start_var_a = model.NewIntVar(0, horizon, \"start_a\")\n",
" start_var_a = model.new_int_var(0, horizon, \"start_a\")\n",
" duration_a = 3\n",
" end_var_a = model.NewIntVar(0, horizon, \"end_a\")\n",
" unused_interval_var_a = model.NewIntervalVar(\n",
" end_var_a = model.new_int_var(0, horizon, \"end_a\")\n",
" unused_interval_var_a = model.new_interval_var(\n",
" start_var_a, duration_a, end_var_a, \"interval_a\"\n",
" )\n",
"\n",
" # Second interval.\n",
" start_var_b = model.NewIntVar(0, horizon, \"start_b\")\n",
" start_var_b = model.new_int_var(0, horizon, \"start_b\")\n",
" duration_b = 2\n",
" end_var_b = model.NewIntVar(0, horizon, \"end_b\")\n",
" unused_interval_var_b = model.NewIntervalVar(\n",
" end_var_b = model.new_int_var(0, horizon, \"end_b\")\n",
" unused_interval_var_b = model.new_interval_var(\n",
" start_var_b, duration_b, end_var_b, \"interval_b\"\n",
" )\n",
"\n",
" # a_after_b Boolean variable.\n",
" a_after_b = model.NewBoolVar(\"a_after_b\")\n",
" model.Add(start_var_a >= end_var_b).OnlyEnforceIf(a_after_b)\n",
" model.Add(start_var_a < end_var_b).OnlyEnforceIf(a_after_b.Not())\n",
" a_after_b = model.new_bool_var(\"a_after_b\")\n",
" model.add(start_var_a >= end_var_b).only_enforce_if(a_after_b)\n",
" model.add(start_var_a < end_var_b).only_enforce_if(~a_after_b)\n",
"\n",
" # b_after_a Boolean variable.\n",
" b_after_a = model.NewBoolVar(\"b_after_a\")\n",
" model.Add(start_var_b >= end_var_a).OnlyEnforceIf(b_after_a)\n",
" model.Add(start_var_b < end_var_a).OnlyEnforceIf(b_after_a.Not())\n",
" b_after_a = model.new_bool_var(\"b_after_a\")\n",
" model.add(start_var_b >= end_var_a).only_enforce_if(b_after_a)\n",
" model.add(start_var_b < end_var_a).only_enforce_if(~b_after_a)\n",
"\n",
" # Result Boolean variable.\n",
" a_overlaps_b = model.NewBoolVar(\"a_overlaps_b\")\n",
" a_overlaps_b = model.new_bool_var(\"a_overlaps_b\")\n",
"\n",
" # Option a: using only clauses\n",
" model.AddBoolOr(a_after_b, b_after_a, a_overlaps_b)\n",
" model.AddImplication(a_after_b, a_overlaps_b.Not())\n",
" model.AddImplication(b_after_a, a_overlaps_b.Not())\n",
" model.add_bool_or(a_after_b, b_after_a, a_overlaps_b)\n",
" model.add_implication(a_after_b, ~a_overlaps_b)\n",
" model.add_implication(b_after_a, ~a_overlaps_b)\n",
"\n",
" # Option b: using an exactly one constraint.\n",
" # model.AddExactlyOne(a_after_b, b_after_a, a_overlaps_b)\n",
" # model.add_exactly_one(a_after_b, b_after_a, a_overlaps_b)\n",
"\n",
" # Search for start values in increasing order for the two intervals.\n",
" model.AddDecisionStrategy(\n",
" [start_var_a, start_var_b], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" model.add_decision_strategy(\n",
" [start_var_a, start_var_b],\n",
" cp_model.CHOOSE_FIRST,\n",
" cp_model.SELECT_MIN_VALUE,\n",
" )\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
@@ -162,10 +159,10 @@
"\n",
" # Search and print out all solutions.\n",
" solution_printer = VarArraySolutionPrinter([start_var_a, start_var_b, a_overlaps_b])\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"OverlappingIntervals()\n",
"overlapping_interval_sample_sat()\n",
"\n"
]
}

View File

@@ -86,27 +86,27 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def RabbitsAndPheasantsSat():\n",
"def rabbits_and_pheasants_sat():\n",
" \"\"\"Solves the rabbits + pheasants problem.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
" r = model.NewIntVar(0, 100, \"r\")\n",
" p = model.NewIntVar(0, 100, \"p\")\n",
" r = model.new_int_var(0, 100, \"r\")\n",
" p = model.new_int_var(0, 100, \"p\")\n",
"\n",
" # 20 heads.\n",
" model.Add(r + p == 20)\n",
" model.add(r + p == 20)\n",
" # 56 legs.\n",
" model.Add(4 * r + 2 * p == 56)\n",
" model.add(4 * r + 2 * p == 56)\n",
"\n",
" # Solves and prints out the solution.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(f\"{solver.Value(r)} rabbits and {solver.Value(p)} pheasants\")\n",
" print(f\"{solver.value(r)} rabbits and {solver.value(p)} pheasants\")\n",
"\n",
"\n",
"RabbitsAndPheasantsSat()\n",
"rabbits_and_pheasants_sat()\n",
"\n"
]
}

View File

@@ -109,11 +109,10 @@
" Each task i will be associated with id i + 1, and an arc between i + 1 and j +\n",
" 1 indicates that j is the immediate successor of i.\n",
"\n",
" The circuit constraint ensures there is at most 1 hamiltonian path of\n",
" The circuit constraint ensures there is at most 1 hamiltonian cycle of\n",
" length > 1. If no such path exists, then no tasks are active.\n",
"\n",
" The multiple enforced linear constraints are meant to ensure the compatibility\n",
" between the order of starts and the order of ranks,\n",
" We also need to enforce that any hamiltonian cycle of size > 1 must contain\n",
" the node 0. And thus, there is a self loop on node 0 iff the circuit is empty.\n",
"\n",
" Args:\n",
" model: The CpModel to add the constraints to.\n",
@@ -129,26 +128,26 @@
" arcs: List[cp_model.ArcT] = []\n",
" for i in all_tasks:\n",
" # if node i is first.\n",
" start_lit = model.NewBoolVar(f\"start_{i}\")\n",
" start_lit = model.new_bool_var(f\"start_{i}\")\n",
" arcs.append((0, i + 1, start_lit))\n",
" model.Add(ranks[i] == 0).OnlyEnforceIf(start_lit)\n",
" model.add(ranks[i] == 0).only_enforce_if(start_lit)\n",
"\n",
" # As there are no other constraints on the problem, we can add this\n",
" # redundant constraint.\n",
" model.Add(starts[i] == 0).OnlyEnforceIf(start_lit)\n",
" model.add(starts[i] == 0).only_enforce_if(start_lit)\n",
"\n",
" # if node i is last.\n",
" end_lit = model.NewBoolVar(f\"end_{i}\")\n",
" end_lit = model.new_bool_var(f\"end_{i}\")\n",
" arcs.append((i + 1, 0, end_lit))\n",
"\n",
" for j in all_tasks:\n",
" if i == j:\n",
" arcs.append((i + 1, i + 1, presences[i].Not()))\n",
" model.Add(ranks[i] == -1).OnlyEnforceIf(presences[i].Not())\n",
" arcs.append((i + 1, i + 1, ~presences[i]))\n",
" model.add(ranks[i] == -1).only_enforce_if(~presences[i])\n",
" else:\n",
" literal = model.NewBoolVar(f\"arc_{i}_to_{j}\")\n",
" literal = model.new_bool_var(f\"arc_{i}_to_{j}\")\n",
" arcs.append((i + 1, j + 1, literal))\n",
" model.Add(ranks[j] == ranks[i] + 1).OnlyEnforceIf(literal)\n",
" model.add(ranks[j] == ranks[i] + 1).only_enforce_if(literal)\n",
"\n",
" # To perform the transitive reduction from precedences to successors,\n",
" # we need to tie the starts of the tasks with 'literal'.\n",
@@ -157,17 +156,19 @@
" #\n",
" # Note that we could use this literal to penalize the transition, add an\n",
" # extra delay to the precedence.\n",
" model.Add(starts[j] >= starts[i] + durations[i]).OnlyEnforceIf(literal)\n",
" model.add(starts[j] >= starts[i] + durations[i]).only_enforce_if(\n",
" literal\n",
" )\n",
"\n",
" # Manage the empty circuit\n",
" empty = model.NewBoolVar(\"empty\")\n",
" empty = model.new_bool_var(\"empty\")\n",
" arcs.append((0, 0, empty))\n",
"\n",
" for i in all_tasks:\n",
" model.AddImplication(empty, presences[i].Not())\n",
" model.add_implication(empty, ~presences[i])\n",
"\n",
" # Add the circuit constraint.\n",
" model.AddCircuit(arcs)\n",
" model.add_circuit(arcs)\n",
"\n",
"\n",
"def ranking_sample_sat():\n",
@@ -186,14 +187,14 @@
"\n",
" # Creates intervals, half of them are optional.\n",
" for t in all_tasks:\n",
" start = model.NewIntVar(0, horizon, f\"start[{t}]\")\n",
" start = model.new_int_var(0, horizon, f\"start[{t}]\")\n",
" duration = t + 1\n",
" presence = model.NewBoolVar(f\"presence[{t}]\")\n",
" interval = model.NewOptionalFixedSizeIntervalVar(\n",
" presence = model.new_bool_var(f\"presence[{t}]\")\n",
" interval = model.new_optional_fixed_size_interval_var(\n",
" start, duration, presence, f\"opt_interval[{t}]\"\n",
" )\n",
" if t < num_tasks // 2:\n",
" model.Add(presence == 1)\n",
" model.add(presence == 1)\n",
"\n",
" starts.append(start)\n",
" durations.append(duration)\n",
@@ -201,45 +202,44 @@
" presences.append(presence)\n",
"\n",
" # Ranks = -1 if and only if the tasks is not performed.\n",
" ranks.append(model.NewIntVar(-1, num_tasks - 1, f\"rank[{t}]\"))\n",
" ranks.append(model.new_int_var(-1, num_tasks - 1, f\"rank[{t}]\"))\n",
"\n",
" # Adds NoOverlap constraint.\n",
" model.AddNoOverlap(intervals)\n",
" model.add_no_overlap(intervals)\n",
"\n",
" # Adds ranking constraint.\n",
" rank_tasks_with_circuit(model, starts, durations, presences, ranks)\n",
"\n",
" # Adds a constraint on ranks.\n",
" model.Add(ranks[0] < ranks[1])\n",
" model.add(ranks[0] < ranks[1])\n",
"\n",
" # Creates makespan variable.\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" makespan = model.new_int_var(0, horizon, \"makespan\")\n",
" for t in all_tasks:\n",
" model.Add(starts[t] + durations[t] <= makespan).OnlyEnforceIf(presences[t])\n",
" model.add(starts[t] + durations[t] <= makespan).only_enforce_if(presences[t])\n",
"\n",
" # Minimizes makespan - fixed gain per tasks performed.\n",
" # As the fixed cost is less that the duration of the last interval,\n",
" # the solver will not perform the last interval.\n",
" model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks))\n",
" model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks))\n",
"\n",
" # Solves the model model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" # Prints out the makespan and the start times and ranks of all tasks.\n",
" print(f\"Optimal cost: {solver.ObjectiveValue()}\")\n",
" print(f\"Makespan: {solver.Value(makespan)}\")\n",
" print(f\"Optimal cost: {solver.objective_value}\")\n",
" print(f\"Makespan: {solver.value(makespan)}\")\n",
" for t in all_tasks:\n",
" if solver.Value(presences[t]):\n",
" if solver.value(presences[t]):\n",
" print(\n",
" f\"Task {t} starts at {solver.Value(starts[t])} \"\n",
" f\"with rank {solver.Value(ranks[t])}\"\n",
" f\"Task {t} starts at {solver.value(starts[t])} \"\n",
" f\"with rank {solver.value(ranks[t])}\"\n",
" )\n",
" else:\n",
" print(\n",
" f\"Task {t} in not performed \"\n",
" f\"and ranked at {solver.Value(ranks[t])}\"\n",
" f\"Task {t} in not performed and ranked at {solver.value(ranks[t])}\"\n",
" )\n",
" else:\n",
" print(f\"Solver exited with nonoptimal status: {status}\")\n",

View File

@@ -86,7 +86,12 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def RankTasks(model, starts, presences, ranks):\n",
"def rank_tasks(\n",
" model: cp_model.CpModel,\n",
" starts: list[cp_model.IntVar],\n",
" presences: list[cp_model.BoolVarT],\n",
" ranks: list[cp_model.IntVar],\n",
") -> None:\n",
" \"\"\"This method adds constraints and variables to links tasks and ranks.\n",
"\n",
" This method assumes that all starts are disjoint, meaning that all tasks have\n",
@@ -96,7 +101,7 @@
" Args:\n",
" model: The CpModel to add the constraints to.\n",
" starts: The array of starts variables of all tasks.\n",
" presences: The array of presence variables of all tasks.\n",
" presences: The array of presence variables or constants of all tasks.\n",
" ranks: The array of rank variables of all tasks.\n",
" \"\"\"\n",
"\n",
@@ -104,45 +109,48 @@
" all_tasks = range(num_tasks)\n",
"\n",
" # Creates precedence variables between pairs of intervals.\n",
" precedences = {}\n",
" precedences: dict[tuple[int, int], cp_model.BoolVarT] = {}\n",
" for i in all_tasks:\n",
" for j in all_tasks:\n",
" if i == j:\n",
" precedences[(i, j)] = presences[i]\n",
" else:\n",
" prec = model.NewBoolVar(f\"{i} before {j}\")\n",
" prec = model.new_bool_var(f\"{i} before {j}\")\n",
" precedences[(i, j)] = prec\n",
" model.Add(starts[i] < starts[j]).OnlyEnforceIf(prec)\n",
" model.add(starts[i] < starts[j]).only_enforce_if(prec)\n",
"\n",
" # Treats optional intervals.\n",
" for i in range(num_tasks - 1):\n",
" for j in range(i + 1, num_tasks):\n",
" tmp_array = [precedences[(i, j)], precedences[(j, i)]]\n",
" if not cp_model.ObjectIsATrueLiteral(presences[i]):\n",
" tmp_array.append(presences[i].Not())\n",
" tmp_array: list[cp_model.BoolVarT] = [\n",
" precedences[(i, j)],\n",
" precedences[(j, i)],\n",
" ]\n",
" if not cp_model.object_is_a_true_literal(presences[i]):\n",
" tmp_array.append(~presences[i])\n",
" # Makes sure that if i is not performed, all precedences are false.\n",
" model.AddImplication(presences[i].Not(), precedences[(i, j)].Not())\n",
" model.AddImplication(presences[i].Not(), precedences[(j, i)].Not())\n",
" if not cp_model.ObjectIsATrueLiteral(presences[j]):\n",
" tmp_array.append(presences[j].Not())\n",
" model.add_implication(~presences[i], ~precedences[(i, j)])\n",
" model.add_implication(~presences[i], ~precedences[(j, i)])\n",
" if not cp_model.object_is_a_true_literal(presences[j]):\n",
" tmp_array.append(~presences[j])\n",
" # Makes sure that if j is not performed, all precedences are false.\n",
" model.AddImplication(presences[j].Not(), precedences[(i, j)].Not())\n",
" model.AddImplication(presences[j].Not(), precedences[(j, i)].Not())\n",
" model.add_implication(~presences[j], ~precedences[(i, j)])\n",
" model.add_implication(~presences[j], ~precedences[(j, i)])\n",
" # The following bool_or will enforce that for any two intervals:\n",
" # i precedes j or j precedes i or at least one interval is not\n",
" # performed.\n",
" model.AddBoolOr(tmp_array)\n",
" model.add_bool_or(tmp_array)\n",
" # Redundant constraint: it propagates early that at most one precedence\n",
" # is true.\n",
" model.AddImplication(precedences[(i, j)], precedences[(j, i)].Not())\n",
" model.AddImplication(precedences[(j, i)], precedences[(i, j)].Not())\n",
" model.add_implication(precedences[(i, j)], ~precedences[(j, i)])\n",
" model.add_implication(precedences[(j, i)], ~precedences[(i, j)])\n",
"\n",
" # Links precedences and ranks.\n",
" for i in all_tasks:\n",
" model.Add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1)\n",
" model.add(ranks[i] == sum(precedences[(j, i)] for j in all_tasks) - 1)\n",
"\n",
"\n",
"def RankingSampleSat():\n",
"def ranking_sample_sat() -> None:\n",
" \"\"\"Ranks tasks in a NoOverlap constraint.\"\"\"\n",
"\n",
" model = cp_model.CpModel()\n",
@@ -153,20 +161,20 @@
" starts = []\n",
" ends = []\n",
" intervals = []\n",
" presences = []\n",
" presences: list[cp_model.BoolVarT] = []\n",
" ranks = []\n",
"\n",
" # Creates intervals, half of them are optional.\n",
" for t in all_tasks:\n",
" start = model.NewIntVar(0, horizon, f\"start[{t}]\")\n",
" start = model.new_int_var(0, horizon, f\"start[{t}]\")\n",
" duration = t + 1\n",
" end = model.NewIntVar(0, horizon, f\"end[{t}]\")\n",
" end = model.new_int_var(0, horizon, f\"end[{t}]\")\n",
" if t < num_tasks // 2:\n",
" interval = model.NewIntervalVar(start, duration, end, f\"interval[{t}]\")\n",
" presence = True\n",
" interval = model.new_interval_var(start, duration, end, f\"interval[{t}]\")\n",
" presence = model.new_constant(1)\n",
" else:\n",
" presence = model.NewBoolVar(f\"presence[{t}]\")\n",
" interval = model.NewOptionalIntervalVar(\n",
" presence = model.new_bool_var(f\"presence[{t}]\")\n",
" interval = model.new_optional_interval_var(\n",
" start, duration, end, presence, f\"o_interval[{t}]\"\n",
" )\n",
" starts.append(start)\n",
@@ -175,51 +183,50 @@
" presences.append(presence)\n",
"\n",
" # Ranks = -1 if and only if the tasks is not performed.\n",
" ranks.append(model.NewIntVar(-1, num_tasks - 1, f\"rank[{t}]\"))\n",
" ranks.append(model.new_int_var(-1, num_tasks - 1, f\"rank[{t}]\"))\n",
"\n",
" # Adds NoOverlap constraint.\n",
" model.AddNoOverlap(intervals)\n",
" model.add_no_overlap(intervals)\n",
"\n",
" # Adds ranking constraint.\n",
" RankTasks(model, starts, presences, ranks)\n",
" rank_tasks(model, starts, presences, ranks)\n",
"\n",
" # Adds a constraint on ranks.\n",
" model.Add(ranks[0] < ranks[1])\n",
" model.add(ranks[0] < ranks[1])\n",
"\n",
" # Creates makespan variable.\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" makespan = model.new_int_var(0, horizon, \"makespan\")\n",
" for t in all_tasks:\n",
" model.Add(ends[t] <= makespan).OnlyEnforceIf(presences[t])\n",
" model.add(ends[t] <= makespan).only_enforce_if(presences[t])\n",
"\n",
" # Minimizes makespan - fixed gain per tasks performed.\n",
" # As the fixed cost is less that the duration of the last interval,\n",
" # the solver will not perform the last interval.\n",
" model.Minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks))\n",
" model.minimize(2 * makespan - 7 * sum(presences[t] for t in all_tasks))\n",
"\n",
" # Solves the model model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" # Prints out the makespan and the start times and ranks of all tasks.\n",
" print(f\"Optimal cost: {solver.ObjectiveValue()}\")\n",
" print(f\"Makespan: {solver.Value(makespan)}\")\n",
" print(f\"Optimal cost: {solver.objective_value}\")\n",
" print(f\"Makespan: {solver.value(makespan)}\")\n",
" for t in all_tasks:\n",
" if solver.Value(presences[t]):\n",
" if solver.value(presences[t]):\n",
" print(\n",
" f\"Task {t} starts at {solver.Value(starts[t])} \"\n",
" f\"with rank {solver.Value(ranks[t])}\"\n",
" f\"Task {t} starts at {solver.value(starts[t])} \"\n",
" f\"with rank {solver.value(ranks[t])}\"\n",
" )\n",
" else:\n",
" print(\n",
" f\"Task {t} in not performed \"\n",
" f\"and ranked at {solver.Value(ranks[t])}\"\n",
" f\"Task {t} in not performed and ranked at {solver.value(ranks[t])}\"\n",
" )\n",
" else:\n",
" print(f\"Solver exited with nonoptimal status: {status}\")\n",
"\n",
"\n",
"RankingSampleSat()\n",
"ranking_sample_sat()\n",
"\n"
]
}

View File

@@ -86,27 +86,27 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def ReifiedSampleSat():\n",
"def reified_sample_sat():\n",
" \"\"\"Showcase creating a reified constraint.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
" x = model.NewBoolVar(\"x\")\n",
" y = model.NewBoolVar(\"y\")\n",
" b = model.NewBoolVar(\"b\")\n",
" x = model.new_bool_var(\"x\")\n",
" y = model.new_bool_var(\"y\")\n",
" b = model.new_bool_var(\"b\")\n",
"\n",
" # First version using a half-reified bool and.\n",
" model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b)\n",
" model.add_bool_and(x, ~y).only_enforce_if(b)\n",
"\n",
" # Second version using implications.\n",
" model.AddImplication(b, x)\n",
" model.AddImplication(b, y.Not())\n",
" model.add_implication(b, x)\n",
" model.add_implication(b, ~y)\n",
"\n",
" # Third version using bool or.\n",
" model.AddBoolOr(b.Not(), x)\n",
" model.AddBoolOr(b.Not(), y.Not())\n",
" model.add_bool_or(~b, x)\n",
" model.add_bool_or(~b, ~y)\n",
"\n",
"\n",
"ReifiedSampleSat()\n",
"reified_sample_sat()\n",
"\n"
]
}

View File

@@ -83,10 +83,12 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Union\n",
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
"def main() -> None:\n",
" # This program tries to find an optimal assignment of nurses to shifts\n",
" # (3 shifts per day, for 7 days), subject to some constraints (see below).\n",
" # Each nurse can request to be assigned to specific shifts.\n",
@@ -114,17 +116,17 @@
" for n in all_nurses:\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" shifts[(n, d, s)] = model.NewBoolVar(f\"shift_n{n}_d{d}_s{s}\")\n",
" shifts[(n, d, s)] = model.new_bool_var(f\"shift_n{n}_d{d}_s{s}\")\n",
"\n",
" # Each shift is assigned to exactly one nurse in .\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses)\n",
" model.add_exactly_one(shifts[(n, d, s)] for n in all_nurses)\n",
"\n",
" # Each nurse works at most one shift per day.\n",
" for n in all_nurses:\n",
" for d in all_days:\n",
" model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts)\n",
" model.add_at_most_one(shifts[(n, d, s)] for s in all_shifts)\n",
"\n",
" # Try to distribute the shifts evenly, so that each nurse works\n",
" # min_shifts_per_nurse shifts. If this is not possible, because the total\n",
@@ -136,15 +138,14 @@
" else:\n",
" max_shifts_per_nurse = min_shifts_per_nurse + 1\n",
" for n in all_nurses:\n",
" num_shifts_worked = 0\n",
" num_shifts_worked: Union[cp_model.LinearExpr, int] = 0\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" num_shifts_worked += shifts[(n, d, s)]\n",
" model.Add(min_shifts_per_nurse <= num_shifts_worked)\n",
" model.Add(num_shifts_worked <= max_shifts_per_nurse)\n",
" model.add(min_shifts_per_nurse <= num_shifts_worked)\n",
" model.add(num_shifts_worked <= max_shifts_per_nurse)\n",
"\n",
" # pylint: disable=g-complex-comprehension\n",
" model.Maximize(\n",
" model.maximize(\n",
" sum(\n",
" shift_requests[n][d][s] * shifts[(n, d, s)]\n",
" for n in all_nurses\n",
@@ -155,7 +156,7 @@
"\n",
" # Creates the solver and solve.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(\"Solution:\")\n",
@@ -163,14 +164,14 @@
" print(\"Day\", d)\n",
" for n in all_nurses:\n",
" for s in all_shifts:\n",
" if solver.Value(shifts[(n, d, s)]) == 1:\n",
" if solver.value(shifts[(n, d, s)]) == 1:\n",
" if shift_requests[n][d][s] == 1:\n",
" print(\"Nurse\", n, \"works shift\", s, \"(requested).\")\n",
" else:\n",
" print(\"Nurse\", n, \"works shift\", s, \"(not requested).\")\n",
" print()\n",
" print(\n",
" f\"Number of shift requests met = {solver.ObjectiveValue()}\",\n",
" f\"Number of shift requests met = {solver.objective_value}\",\n",
" f\"(out of {num_nurses * min_shifts_per_nurse})\",\n",
" )\n",
" else:\n",
@@ -178,9 +179,9 @@
"\n",
" # Statistics.\n",
" print(\"\\nStatistics\")\n",
" print(f\" - conflicts: {solver.NumConflicts()}\")\n",
" print(f\" - branches : {solver.NumBranches()}\")\n",
" print(f\" - wall time: {solver.WallTime()}s\")\n",
" print(f\" - conflicts: {solver.num_conflicts}\")\n",
" print(f\" - branches : {solver.num_branches}\")\n",
" print(f\" - wall time: {solver.wall_time}s\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -89,22 +89,17 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" self.__solution_count += 1\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" return self.__solution_count\n",
"\n",
"\n",
"def SchedulingWithCalendarSampleSat():\n",
"def scheduling_with_calendar_sample_sat():\n",
" \"\"\"Interval spanning across a lunch break.\"\"\"\n",
" model = cp_model.CpModel()\n",
"\n",
@@ -116,25 +111,27 @@
" # Because the duration is at least 3 hours, work cannot start after 15h.\n",
" # Because of the break, work cannot start at 13h.\n",
"\n",
" start = model.NewIntVarFromDomain(\n",
" cp_model.Domain.FromIntervals([(8, 12), (14, 15)]), \"start\"\n",
" start = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_intervals([(8, 12), (14, 15)]), \"start\"\n",
" )\n",
" duration = model.NewIntVar(3, 4, \"duration\")\n",
" end = model.NewIntVar(8, 18, \"end\")\n",
" unused_interval = model.NewIntervalVar(start, duration, end, \"interval\")\n",
" duration = model.new_int_var(3, 4, \"duration\")\n",
" end = model.new_int_var(8, 18, \"end\")\n",
" unused_interval = model.new_interval_var(start, duration, end, \"interval\")\n",
"\n",
" # We have 2 states (spanning across lunch or not)\n",
" across = model.NewBoolVar(\"across\")\n",
" non_spanning_hours = cp_model.Domain.FromValues([8, 9, 10, 14, 15])\n",
" model.AddLinearExpressionInDomain(start, non_spanning_hours).OnlyEnforceIf(\n",
" across.Not()\n",
" across = model.new_bool_var(\"across\")\n",
" non_spanning_hours = cp_model.Domain.from_values([8, 9, 10, 14, 15])\n",
" model.add_linear_expression_in_domain(start, non_spanning_hours).only_enforce_if(\n",
" ~across\n",
" )\n",
" model.AddLinearConstraint(start, 11, 12).OnlyEnforceIf(across)\n",
" model.Add(duration == 3).OnlyEnforceIf(across.Not())\n",
" model.Add(duration == 4).OnlyEnforceIf(across)\n",
" model.add_linear_constraint(start, 11, 12).only_enforce_if(across)\n",
" model.add(duration == 3).only_enforce_if(~across)\n",
" model.add(duration == 4).only_enforce_if(across)\n",
"\n",
" # Search for x values in increasing order.\n",
" model.AddDecisionStrategy([start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
" model.add_decision_strategy(\n",
" [start], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" )\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
@@ -146,10 +143,10 @@
"\n",
" # Search and print all solutions.\n",
" solution_printer = VarArraySolutionPrinter([start, duration, across])\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"SchedulingWithCalendarSampleSat()\n",
"scheduling_with_calendar_sample_sat()\n",
"\n"
]
}

View File

@@ -89,34 +89,35 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" self.__solution_count += 1\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
"\n",
"def SearchForAllSolutionsSampleSat():\n",
"def search_for_all_solutions_sample_sat():\n",
" \"\"\"Showcases calling the solver to search for all solutions.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Create the constraints.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" # Create a solver and solve.\n",
" solver = cp_model.CpSolver()\n",
@@ -124,13 +125,13 @@
" # Enumerate all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" # Solve.\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count}\")\n",
"\n",
"\n",
"SearchForAllSolutionsSampleSat()\n",
"search_for_all_solutions_sample_sat()\n",
"\n"
]
}

View File

@@ -86,33 +86,33 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def SimpleSatProgram():\n",
"def simple_sat_program():\n",
" \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" # Creates a solver and solves the model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f\"x = {solver.Value(x)}\")\n",
" print(f\"y = {solver.Value(y)}\")\n",
" print(f\"z = {solver.Value(z)}\")\n",
" print(f\"x = {solver.value(x)}\")\n",
" print(f\"y = {solver.value(y)}\")\n",
" print(f\"z = {solver.value(z)}\")\n",
" else:\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"SimpleSatProgram()\n",
"simple_sat_program()\n",
"\n"
]
}

View File

@@ -86,36 +86,36 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def SolutionHintingSampleSat():\n",
"def solution_hinting_sample_sat():\n",
" \"\"\"Showcases solution hinting.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" model.Maximize(x + 2 * y + 3 * z)\n",
" model.maximize(x + 2 * y + 3 * z)\n",
"\n",
" # Solution hinting: x <- 1, y <- 2\n",
" model.AddHint(x, 1)\n",
" model.AddHint(y, 2)\n",
" model.add_hint(x, 1)\n",
" model.add_hint(y, 2)\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z])\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count}\")\n",
"\n",
"\n",
"SolutionHintingSampleSat()\n",
"solution_hinting_sample_sat()\n",
"\n"
]
}

View File

@@ -90,49 +90,50 @@
"class VarArrayAndObjectiveSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" print(f\"Solution {self.__solution_count}\")\n",
" print(f\" objective value = {self.ObjectiveValue()}\")\n",
" print(f\" objective value = {self.objective_value}\")\n",
" for v in self.__variables:\n",
" print(f\" {v}={self.Value(v)}\", end=\" \")\n",
" print(f\" {v}={self.value(v)}\", end=\" \")\n",
" print()\n",
" self.__solution_count += 1\n",
"\n",
" def solution_count(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
"\n",
"def SolveAndPrintIntermediateSolutionsSampleSat():\n",
"def solve_and_print_intermediate_solutions_sample_sat():\n",
" \"\"\"Showcases printing intermediate solutions found during search.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Creates the constraints.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" model.Maximize(x + 2 * y + 3 * z)\n",
" model.maximize(x + 2 * y + 3 * z)\n",
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = VarArrayAndObjectiveSolutionPrinter([x, y, z])\n",
" status = solver.Solve(model, solution_printer)\n",
" status = solver.solve(model, solution_printer)\n",
"\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count}\")\n",
"\n",
"\n",
"SolveAndPrintIntermediateSolutionsSampleSat()\n",
"solve_and_print_intermediate_solutions_sample_sat()\n",
"\n"
]
}

View File

@@ -86,17 +86,17 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def SolveWithTimeLimitSampleSat():\n",
"def solve_with_time_limit_sample_sat():\n",
" \"\"\"Minimal CP-SAT example to showcase calling the solver.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
" # Adds an all-different constraint.\n",
" model.Add(x != y)\n",
" model.add(x != y)\n",
"\n",
" # Creates a solver and solves the model.\n",
" solver = cp_model.CpSolver()\n",
@@ -104,15 +104,15 @@
" # Sets a time limit of 10 seconds.\n",
" solver.parameters.max_time_in_seconds = 10.0\n",
"\n",
" status = solver.Solve(model)\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(f\"x = {solver.Value(x)}\")\n",
" print(f\"y = {solver.Value(y)}\")\n",
" print(f\"z = {solver.Value(z)}\")\n",
" print(f\"x = {solver.value(x)}\")\n",
" print(f\"y = {solver.value(y)}\")\n",
" print(f\"z = {solver.value(z)}\")\n",
"\n",
"\n",
"SolveWithTimeLimitSampleSat()\n",
"solve_with_time_limit_sample_sat()\n",
"\n"
]
}

View File

@@ -89,20 +89,15 @@
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables):\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" self.__solution_count += 1\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
" def solution_count(self):\n",
" return self.__solution_count\n",
"\n",
"\n",
"def step_function_sample_sat():\n",
" \"\"\"Encode the step function.\"\"\"\n",
@@ -111,7 +106,7 @@
" model = cp_model.CpModel()\n",
"\n",
" # Declare our primary variable.\n",
" x = model.NewIntVar(0, 20, \"x\")\n",
" x = model.new_int_var(0, 20, \"x\")\n",
"\n",
" # Create the expression variable and implement the step function\n",
" # Note it is not defined for x == 2.\n",
@@ -122,32 +117,32 @@
" # -- --- 0\n",
" # 0 ================ 20\n",
" #\n",
" expr = model.NewIntVar(0, 3, \"expr\")\n",
" expr = model.new_int_var(0, 3, \"expr\")\n",
"\n",
" # expr == 0 on [5, 6] U [8, 10]\n",
" b0 = model.NewBoolVar(\"b0\")\n",
" model.AddLinearExpressionInDomain(\n",
" x, cp_model.Domain.FromIntervals([(5, 6), (8, 10)])\n",
" ).OnlyEnforceIf(b0)\n",
" model.Add(expr == 0).OnlyEnforceIf(b0)\n",
" b0 = model.new_bool_var(\"b0\")\n",
" model.add_linear_expression_in_domain(\n",
" x, cp_model.Domain.from_intervals([(5, 6), (8, 10)])\n",
" ).only_enforce_if(b0)\n",
" model.add(expr == 0).only_enforce_if(b0)\n",
"\n",
" # expr == 2 on [0, 1] U [3, 4] U [11, 20]\n",
" b2 = model.NewBoolVar(\"b2\")\n",
" model.AddLinearExpressionInDomain(\n",
" x, cp_model.Domain.FromIntervals([(0, 1), (3, 4), (11, 20)])\n",
" ).OnlyEnforceIf(b2)\n",
" model.Add(expr == 2).OnlyEnforceIf(b2)\n",
" b2 = model.new_bool_var(\"b2\")\n",
" model.add_linear_expression_in_domain(\n",
" x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)])\n",
" ).only_enforce_if(b2)\n",
" model.add(expr == 2).only_enforce_if(b2)\n",
"\n",
" # expr == 3 when x == 7\n",
" b3 = model.NewBoolVar(\"b3\")\n",
" model.Add(x == 7).OnlyEnforceIf(b3)\n",
" model.Add(expr == 3).OnlyEnforceIf(b3)\n",
" b3 = model.new_bool_var(\"b3\")\n",
" model.add(x == 7).only_enforce_if(b3)\n",
" model.add(expr == 3).only_enforce_if(b3)\n",
"\n",
" # At least one bi is true. (we could use an exactly one constraint).\n",
" model.AddBoolOr(b0, b2, b3)\n",
" model.add_bool_or(b0, b2, b3)\n",
"\n",
" # Search for x values in increasing order.\n",
" model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
" model.add_decision_strategy([x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE)\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
@@ -159,7 +154,7 @@
"\n",
" # Search and print out all solutions.\n",
" solution_printer = VarArraySolutionPrinter([x, expr])\n",
" solver.Solve(model, solution_printer)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"step_function_sample_sat()\n",

View File

@@ -89,34 +89,35 @@
"class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables, limit):\n",
" def __init__(self, variables: list[cp_model.IntVar], limit: int):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
" self.__solution_count = 0\n",
" self.__solution_limit = limit\n",
"\n",
" def on_solution_callback(self):\n",
" def on_solution_callback(self) -> None:\n",
" self.__solution_count += 1\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.Value(v)}\", end=\" \")\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
" if self.__solution_count >= self.__solution_limit:\n",
" print(f\"Stop search after {self.__solution_limit} solutions\")\n",
" self.StopSearch()\n",
" self.stop_search()\n",
"\n",
" def solution_count(self):\n",
" @property\n",
" def solution_count(self) -> int:\n",
" return self.__solution_count\n",
"\n",
"\n",
"def StopAfterNSolutionsSampleSat():\n",
"def stop_after_n_solutions_sample_sat():\n",
" \"\"\"Showcases calling the solver to search for small number of solutions.\"\"\"\n",
" # Creates the model.\n",
" model = cp_model.CpModel()\n",
" # Creates the variables.\n",
" num_vals = 3\n",
" x = model.NewIntVar(0, num_vals - 1, \"x\")\n",
" y = model.NewIntVar(0, num_vals - 1, \"y\")\n",
" z = model.NewIntVar(0, num_vals - 1, \"z\")\n",
" x = model.new_int_var(0, num_vals - 1, \"x\")\n",
" y = model.new_int_var(0, num_vals - 1, \"y\")\n",
" z = model.new_int_var(0, num_vals - 1, \"z\")\n",
"\n",
" # Create a solver and solve.\n",
" solver = cp_model.CpSolver()\n",
@@ -124,13 +125,13 @@
" # Enumerate all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" # Solve.\n",
" status = solver.Solve(model, solution_printer)\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count()}\")\n",
" assert solution_printer.solution_count() == 5\n",
" status = solver.solve(model, solution_printer)\n",
" print(f\"Status = {solver.status_name(status)}\")\n",
" print(f\"Number of solutions found: {solution_printer.solution_count}\")\n",
" assert solution_printer.solution_count == 5\n",
"\n",
"\n",
"StopAfterNSolutionsSampleSat()\n",
"stop_after_n_solutions_sample_sat()\n",
"\n"
]
}