update notebooks
This commit is contained in:
committed by
Corentin Le Molgat
parent
df5c9411af
commit
e3c7c30b89
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
264
examples/notebook/examples/pentominoes_sat.ipynb
Normal file
264
examples/notebook/examples/pentominoes_sat.ipynb
Normal 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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n",
|
||||
"MIP example that uses a variable array."
|
||||
"MIP example that uses a variable array.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
150
examples/notebook/sat/bool_and_int_var_product_sample_sat.ipynb
Normal file
150
examples/notebook/sat/bool_and_int_var_product_sample_sat.ipynb
Normal 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
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
150
examples/notebook/sat/index_first_boolvar_true_sample_sat.ipynb
Normal file
150
examples/notebook/sat/index_first_boolvar_true_sample_sat.ipynb
Normal 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
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user