examples: Bump notebooks

This commit is contained in:
Corentin Le Molgat
2023-07-13 14:34:43 +02:00
parent 56f69732ed
commit 9179bad0ae
130 changed files with 6534 additions and 6362 deletions

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"A simple knapsack problem."
]
},
@@ -82,43 +83,48 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.algorithms import pywrapknapsack_solver\n",
"from ortools.algorithms.python import knapsack_solver\n",
"\n",
"\n",
"def main():\n",
" # Create the solver.\n",
" solver = pywrapknapsack_solver.KnapsackSolver(\n",
" pywrapknapsack_solver.KnapsackSolver.\n",
" KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, 'KnapsackExample')\n",
" solver = knapsack_solver.KnapsackSolver(\n",
" knapsack_solver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,\n",
" \"KnapsackExample\",\n",
" )\n",
"\n",
" values = [\n",
" 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,\n",
" 78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,\n",
" 87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,\n",
" 312\n",
" # fmt:off\n",
" 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147,\n",
" 78, 256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28,\n",
" 87, 73, 78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276,\n",
" 312\n",
" # fmt:on\n",
" ]\n",
" weights = [\n",
" # fmt: off\n",
" [7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,\n",
" 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71, 3,\n",
" 86, 66, 31, 65, 0, 79, 20, 65, 52, 13],\n",
" # fmt: on\n",
" ]\n",
" weights = [[\n",
" 7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15, 42, 9, 0,\n",
" 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56, 7, 29, 93, 44, 71,\n",
" 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13\n",
" ]]\n",
" capacities = [850]\n",
"\n",
" solver.Init(values, weights, capacities)\n",
" computed_value = solver.Solve()\n",
" solver.init(values, weights, capacities)\n",
" computed_value = solver.solve()\n",
"\n",
" packed_items = []\n",
" packed_weights = []\n",
" total_weight = 0\n",
" print('Total value =', computed_value)\n",
" print(\"Total value =\", computed_value)\n",
" for i in range(len(values)):\n",
" if solver.BestSolutionContains(i):\n",
" if solver.best_solution_contains(i):\n",
" packed_items.append(i)\n",
" packed_weights.append(weights[0][i])\n",
" total_weight += weights[0][i]\n",
" print('Total weight:', total_weight)\n",
" print('Packed items:', packed_items)\n",
" print('Packed_weights:', packed_weights)\n",
" print(\"Total weight:\", total_weight)\n",
" print(\"Packed items:\", packed_items)\n",
" print(\"Packed_weights:\", packed_weights)\n",
"\n",
"\n",
"main()\n",

View File

@@ -83,27 +83,29 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.algorithms import pywrapknapsack_solver\n",
"from ortools.algorithms.python import knapsack_solver\n",
"\n",
"\n",
"def main():\n",
" # Create the solver.\n",
" solver = pywrapknapsack_solver.KnapsackSolver(\n",
" pywrapknapsack_solver.KnapsackSolver.\n",
" KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, \"test\")\n",
" solver = knapsack_solver.KnapsackSolver(\n",
" knapsack_solver.SolverType.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER,\n",
" \"test\",\n",
" )\n",
"\n",
" weights = [[\n",
" 565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293,\n",
" 712, 147, 421, 255\n",
" ]]\n",
" weights = [\n",
" # fmt:off\n",
" [565, 406, 194, 130, 435, 367, 230, 315, 393, 125, 670, 892, 600, 293, 712, 147, 421, 255],\n",
" # fmt:on\n",
" ]\n",
" capacities = [850]\n",
" values = weights[0]\n",
"\n",
" solver.Init(values, weights, capacities)\n",
" computed_value = solver.Solve()\n",
" solver.init(values, weights, capacities)\n",
" computed_value = solver.solve()\n",
"\n",
" packed_items = [\n",
" x for x in range(0, len(weights[0])) if solver.BestSolutionContains(x)\n",
" x for x in range(0, len(weights[0])) if solver.best_solution_contains(x)\n",
" ]\n",
" packed_weights = [weights[0][i] for i in packed_items]\n",
"\n",

View File

@@ -93,23 +93,23 @@
"\n",
"def main():\n",
" # Constraint programming engine\n",
" solver = pywrapcp.Solver('CP is fun!')\n",
" solver = pywrapcp.Solver(\"CP is fun!\")\n",
"\n",
" base = 10\n",
"\n",
" # Decision variables.\n",
" digits = list(range(0, base))\n",
" digits_without_zero = list(range(1, base))\n",
" c = solver.IntVar(digits_without_zero, 'C')\n",
" p = solver.IntVar(digits, 'P')\n",
" i = solver.IntVar(digits_without_zero, 'I')\n",
" s = solver.IntVar(digits, 'S')\n",
" f = solver.IntVar(digits_without_zero, 'F')\n",
" u = solver.IntVar(digits, 'U')\n",
" n = solver.IntVar(digits, 'N')\n",
" t = solver.IntVar(digits_without_zero, 'T')\n",
" r = solver.IntVar(digits, 'R')\n",
" e = solver.IntVar(digits, 'E')\n",
" c = solver.IntVar(digits_without_zero, \"C\")\n",
" p = solver.IntVar(digits, \"P\")\n",
" i = solver.IntVar(digits_without_zero, \"I\")\n",
" s = solver.IntVar(digits, \"S\")\n",
" f = solver.IntVar(digits_without_zero, \"F\")\n",
" u = solver.IntVar(digits, \"U\")\n",
" n = solver.IntVar(digits, \"N\")\n",
" t = solver.IntVar(digits_without_zero, \"T\")\n",
" r = solver.IntVar(digits, \"R\")\n",
" e = solver.IntVar(digits, \"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",
@@ -121,8 +121,10 @@
" solver.Add(solver.AllDifferent(letters))\n",
"\n",
" # CP + IS + FUN = TRUE\n",
" solver.Add(p + s + n + base * (c + i + u) + base * base * f == e +\n",
" base * u + base * base * r + base * base * base * t)\n",
" solver.Add(\n",
" p + s + n + base * (c + i + u) + base * base * f\n",
" == e + base * u + base * base * r + base * base * base * t\n",
" )\n",
"\n",
" solution_count = 0\n",
" db = solver.Phase(letters, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)\n",
@@ -130,13 +132,22 @@
" while solver.NextSolution():\n",
" print(letters)\n",
" # Is CP + IS + FUN = TRUE?\n",
" assert (base * c.Value() + p.Value() + base * i.Value() + s.Value() +\n",
" base * base * f.Value() + base * u.Value() +\n",
" n.Value() == base * base * base * t.Value() +\n",
" base * base * r.Value() + base * u.Value() + e.Value())\n",
" assert (\n",
" base * c.Value()\n",
" + p.Value()\n",
" + base * i.Value()\n",
" + s.Value()\n",
" + base * base * f.Value()\n",
" + base * u.Value()\n",
" + n.Value()\n",
" == base * base * base * t.Value()\n",
" + base * base * r.Value()\n",
" + base * u.Value()\n",
" + e.Value()\n",
" )\n",
" solution_count += 1\n",
" solver.EndSearch()\n",
" print(f'Number of solutions found: {solution_count}')\n",
" print(f\"Number of solutions found: {solution_count}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -100,81 +100,91 @@
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" # Locations in block unit\n",
" locations_ = \\\n",
" [(4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8)]\n",
" locations_ = [\n",
" # fmt: off\n",
" (4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8),\n",
" # fmt: on\n",
" ]\n",
" # Compute locations in meters using the block dimension defined as follow\n",
" # Manhattan average block: 750ft x 264ft -> 228m x 80m\n",
" # here we use: 114m x 80m city block\n",
" # src: https://nyti.ms/2GDoRIe \"NY Times: Know Your distance\"\n",
" data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations_]\n",
" data['numlocations_'] = len(data['locations'])\n",
" data['time_windows'] = \\\n",
" [(0, 0),\n",
" (75, 85), (75, 85), # 1, 2\n",
" (60, 70), (45, 55), # 3, 4\n",
" (0, 8), (50, 60), # 5, 6\n",
" (0, 10), (10, 20), # 7, 8\n",
" (0, 10), (75, 85), # 9, 10\n",
" (85, 95), (5, 15), # 11, 12\n",
" (15, 25), (10, 20), # 13, 14\n",
" (45, 55), (30, 40)] # 15, 16\n",
" data['demands'] = \\\n",
" [0, # depot\n",
" 1, 1, # 1, 2\n",
" 2, 4, # 3, 4\n",
" 2, 4, # 5, 6\n",
" 8, 8, # 7, 8\n",
" 1, 2, # 9,10\n",
" 1, 2, # 11,12\n",
" 4, 4, # 13, 14\n",
" 8, 8] # 15, 16\n",
" data['time_per_demand_unit'] = 5 # 5 minutes/unit\n",
" data['num_vehicles'] = 4\n",
" data['breaks'] = [(2, False), (2, False), (2, False), (2, False)]\n",
" data['vehicle_capacity'] = 15\n",
" data['vehicle_speed'] = 83 # Travel speed: 5km/h converted in m/min\n",
" data['depot'] = 0\n",
" data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations_]\n",
" data[\"numlocations_\"] = len(data[\"locations\"])\n",
" data[\"time_windows\"] = [\n",
" # fmt: off\n",
" (0, 0), # depot\n",
" (75, 85), (75, 85), # 1, 2\n",
" (60, 70), (45, 55), # 3, 4\n",
" (0, 8), (50, 60), # 5, 6\n",
" (0, 10), (10, 20), # 7, 8\n",
" (0, 10), (75, 85), # 9, 10\n",
" (85, 95), (5, 15), # 11, 12\n",
" (15, 25), (10, 20), # 13, 14\n",
" (45, 55), (30, 40),\n",
" # 15, 16\n",
" # fmt: on\n",
" ]\n",
" data[\"demands\"] = [\n",
" # fmt: off\n",
" 0, # depot\n",
" 1, 1, # 1, 2\n",
" 2, 4, # 3, 4\n",
" 2, 4, # 5, 6\n",
" 8, 8, # 7, 8\n",
" 1, 2, # 9, 10\n",
" 1, 2, # 11, 12\n",
" 4, 4, # 13, 14\n",
" 8, 8,\n",
" # 15, 16\n",
" # fmt: on\n",
" ]\n",
" data[\"time_per_demand_unit\"] = 5 # 5 minutes/unit\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"breaks\"] = [(2, False), (2, False), (2, False), (2, False)]\n",
" data[\"vehicle_capacity\"] = 15\n",
" data[\"vehicle_speed\"] = 83 # Travel speed: 5km/h converted in m/min\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def manhattan_distance(position_1, position_2):\n",
" \"\"\"Computes the Manhattan distance between two points.\"\"\"\n",
" return (abs(position_1[0] - position_2[0]) +\n",
" abs(position_1[1] - position_2[1]))\n",
" return abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])\n",
"\n",
"\n",
"def create_distance_evaluator(data):\n",
" \"\"\"Creates callback to return distance between points.\"\"\"\n",
" distances_ = {}\n",
" # precompute distance between location to have distance callback in O(1)\n",
" for from_node in range(data['numlocations_']):\n",
" for from_node in range(data[\"numlocations_\"]):\n",
" distances_[from_node] = {}\n",
" for to_node in range(data['numlocations_']):\n",
" for to_node in range(data[\"numlocations_\"]):\n",
" if from_node == to_node:\n",
" distances_[from_node][to_node] = 0\n",
" else:\n",
" distances_[from_node][to_node] = (manhattan_distance(\n",
" data['locations'][from_node], data['locations'][to_node]))\n",
" distances_[from_node][to_node] = manhattan_distance(\n",
" data[\"locations\"][from_node], data[\"locations\"][to_node]\n",
" )\n",
"\n",
" def distance_evaluator(manager, from_node, to_node):\n",
" \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n",
" return distances_[manager.IndexToNode(from_node)][manager.IndexToNode(\n",
" to_node)]\n",
" return distances_[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n",
"\n",
" return distance_evaluator\n",
"\n",
"\n",
"def create_demand_evaluator(data):\n",
" \"\"\"Creates callback to get demands at each location.\"\"\"\n",
" demands_ = data['demands']\n",
" demands_ = data[\"demands\"]\n",
"\n",
" def demand_evaluator(manager, node):\n",
" \"\"\"Returns the demand of the current node.\"\"\"\n",
@@ -185,13 +195,14 @@
"\n",
"def add_capacity_constraints(routing, data, demand_evaluator_index):\n",
" \"\"\"Adds capacity constraint.\"\"\"\n",
" capacity = 'Capacity'\n",
" capacity = \"Capacity\"\n",
" routing.AddDimension(\n",
" demand_evaluator_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_capacity'],\n",
" data[\"vehicle_capacity\"],\n",
" True, # start cumul to zero\n",
" capacity)\n",
" capacity,\n",
" )\n",
"\n",
"\n",
"def create_time_evaluator(data):\n",
@@ -199,63 +210,68 @@
"\n",
" def service_time(data, node):\n",
" \"\"\"Gets the service time for the specified location.\"\"\"\n",
" return data['demands'][node] * data['time_per_demand_unit']\n",
" return data[\"demands\"][node] * data[\"time_per_demand_unit\"]\n",
"\n",
" def travel_time(data, from_node, to_node):\n",
" \"\"\"Gets the travel times between two locations.\"\"\"\n",
" if from_node == to_node:\n",
" travel_time = 0\n",
" else:\n",
" travel_time = manhattan_distance(\n",
" data['locations'][from_node],\n",
" data['locations'][to_node]) / data['vehicle_speed']\n",
" travel_time = (\n",
" manhattan_distance(\n",
" data[\"locations\"][from_node], data[\"locations\"][to_node]\n",
" )\n",
" / data[\"vehicle_speed\"]\n",
" )\n",
" return travel_time\n",
"\n",
" total_time_ = {}\n",
" # precompute total time to have time callback in O(1)\n",
" for from_node in range(data['numlocations_']):\n",
" for from_node in range(data[\"numlocations_\"]):\n",
" total_time_[from_node] = {}\n",
" for to_node in range(data['numlocations_']):\n",
" for to_node in range(data[\"numlocations_\"]):\n",
" if from_node == to_node:\n",
" total_time_[from_node][to_node] = 0\n",
" else:\n",
" total_time_[from_node][to_node] = int(\n",
" service_time(data, from_node) +\n",
" travel_time(data, from_node, to_node))\n",
" service_time(data, from_node)\n",
" + travel_time(data, from_node, to_node)\n",
" )\n",
"\n",
" def time_evaluator(manager, from_node, to_node):\n",
" \"\"\"Returns the total time between the two nodes.\"\"\"\n",
" return total_time_[manager.IndexToNode(from_node)][manager.IndexToNode(\n",
" to_node)]\n",
" return total_time_[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n",
"\n",
" return time_evaluator\n",
"\n",
"\n",
"def add_time_window_constraints(routing, manager, data, time_evaluator_index):\n",
" \"\"\"Add Global Span constraint.\"\"\"\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" horizon = 120\n",
" routing.AddDimension(\n",
" time_evaluator_index,\n",
" horizon, # allow waiting time\n",
" horizon, # maximum time per vehicle\n",
" False, # don't force start cumul to zero\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot\n",
" # and 'copy' the slack var in the solution object (aka Assignment) to print it\n",
" for location_idx, time_window in enumerate(data['time_windows']):\n",
" if location_idx == data['depot']:\n",
" for location_idx, time_window in enumerate(data[\"time_windows\"]):\n",
" if location_idx == data[\"depot\"]:\n",
" continue\n",
" index = manager.NodeToIndex(location_idx)\n",
" time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n",
" routing.AddToAssignment(time_dimension.SlackVar(index))\n",
" # Add time window constraints for each vehicle start node\n",
" # and 'copy' the slack var in the solution object (aka Assignment) to print it\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n",
" data['time_windows'][0][1])\n",
" time_dimension.CumulVar(index).SetRange(\n",
" data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n",
" )\n",
" routing.AddToAssignment(time_dimension.SlackVar(index))\n",
" # The time window at the end node was impliclty set in the time dimension\n",
" # definition to be [0, horizon].\n",
@@ -263,28 +279,32 @@
" # be added to the assignment.\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n",
"def print_solution(\n",
" data, manager, routing, assignment\n",
"): # pylint:disable=too-many-locals\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
"\n",
" print('Breaks:')\n",
" print(\"Breaks:\")\n",
" intervals = assignment.IntervalVarContainer()\n",
" for i in range(intervals.Size()):\n",
" brk = intervals.Element(i)\n",
" if brk.PerformedValue() == 1:\n",
" print(f'{brk.Var().Name()}:'\n",
" f' Start({brk.StartValue()}) Duration({brk.DurationValue()})')\n",
" print(\n",
" f\"{brk.Var().Name()}:\"\n",
" f\" Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n",
" )\n",
" else:\n",
" print(f'{brk.Var().Name()}: Unperformed')\n",
" print(f\"{brk.Var().Name()}: Unperformed\")\n",
"\n",
" total_distance = 0\n",
" total_load = 0\n",
" total_time = 0\n",
" capacity_dimension = routing.GetDimensionOrDie('Capacity')\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" capacity_dimension = routing.GetDimensionOrDie(\"Capacity\")\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" distance = 0\n",
" while not routing.IsEnd(index):\n",
" load_var = capacity_dimension.CumulVar(index)\n",
@@ -292,32 +312,33 @@
" slack_var = time_dimension.SlackVar(index)\n",
" node = manager.IndexToNode(index)\n",
" plan_output += (\n",
" f' {node}'\n",
" f' Load({assignment.Value(load_var)})'\n",
" f' Time({assignment.Min(time_var)}, {assignment.Max(time_var)})'\n",
" f' Slack({assignment.Min(slack_var)}, {assignment.Max(slack_var)})'\n",
" ' ->')\n",
" f\" {node}\"\n",
" f\" Load({assignment.Value(load_var)})\"\n",
" f\" Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\"\n",
" f\" Slack({assignment.Min(slack_var)}, {assignment.Max(slack_var)})\"\n",
" \" ->\"\n",
" )\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" distance += routing.GetArcCostForVehicle(previous_index, index,\n",
" vehicle_id)\n",
" distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)\n",
" load_var = capacity_dimension.CumulVar(index)\n",
" time_var = time_dimension.CumulVar(index)\n",
" node = manager.IndexToNode(index)\n",
" plan_output += (\n",
" f' {node}'\n",
" f' Load({assignment.Value(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)}\\n'\n",
" f\" {node}\"\n",
" f\" Load({assignment.Value(load_var)})\"\n",
" f\" Time({assignment.Min(time_var)}, {assignment.Max(time_var)})\\n\"\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)}\\n\"\n",
" print(plan_output)\n",
" total_distance += distance\n",
" total_load += assignment.Value(load_var)\n",
" total_time += assignment.Value(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",
" 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",
"\n",
"\n",
"def main():\n",
@@ -326,50 +347,57 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager\n",
" manager = pywrapcp.RoutingIndexManager(data['numlocations_'],\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" data[\"numlocations_\"], data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
" # Define weight of each edge\n",
" distance_evaluator_index = routing.RegisterTransitCallback(\n",
" functools.partial(create_distance_evaluator(data), manager))\n",
" functools.partial(create_distance_evaluator(data), manager)\n",
" )\n",
" routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n",
"\n",
" # Add Capacity constraint\n",
" demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n",
" functools.partial(create_demand_evaluator(data), manager))\n",
" functools.partial(create_demand_evaluator(data), manager)\n",
" )\n",
" add_capacity_constraints(routing, data, demand_evaluator_index)\n",
"\n",
" # Add Time Window constraint\n",
" time_evaluator_index = routing.RegisterTransitCallback(\n",
" functools.partial(create_time_evaluator(data), manager))\n",
" functools.partial(create_time_evaluator(data), manager)\n",
" )\n",
" add_time_window_constraints(routing, manager, data, time_evaluator_index)\n",
"\n",
" # Add breaks\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" node_visit_transit = {}\n",
" for index in range(routing.Size()):\n",
" node = manager.IndexToNode(index)\n",
" node_visit_transit[index] = int(data['demands'][node] *\n",
" data['time_per_demand_unit'])\n",
" node_visit_transit[index] = int(\n",
" data[\"demands\"][node] * data[\"time_per_demand_unit\"]\n",
" )\n",
"\n",
" break_intervals = {}\n",
" for v in range(data['num_vehicles']):\n",
" vehicle_break = data['breaks'][v]\n",
" for v in range(data[\"num_vehicles\"]):\n",
" vehicle_break = data[\"breaks\"][v]\n",
" break_intervals[v] = [\n",
" routing.solver().FixedDurationIntervalVar(15, 100, vehicle_break[0],\n",
" vehicle_break[1],\n",
" f'Break for vehicle {v}')\n",
" routing.solver().FixedDurationIntervalVar(\n",
" 15, 100, vehicle_break[0], vehicle_break[1], f\"Break for vehicle {v}\"\n",
" )\n",
" ]\n",
" time_dimension.SetBreakIntervalsOfVehicle(break_intervals[v], v,\n",
" node_visit_transit.values())\n",
" time_dimension.SetBreakIntervalsOfVehicle(\n",
" break_intervals[v], v, node_visit_transit.values()\n",
" )\n",
"\n",
" # Setting first solution heuristic (cheapest addition).\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" ) # pylint: disable=no-member\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",
@@ -378,7 +406,7 @@
" if assignment:\n",
" print_solution(data, manager, routing, assignment)\n",
" else:\n",
" print('No solution found!')\n",
" print(\"No solution found!\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -89,13 +89,11 @@
"\n",
"def main(board_size):\n",
" # Creates the solver.\n",
" solver = pywrapcp.Solver('n-queens')\n",
" solver = pywrapcp.Solver(\"n-queens\")\n",
"\n",
" # Creates the variables.\n",
" # The array index is the column, and the value is the row.\n",
" queens = [\n",
" solver.IntVar(0, board_size - 1, f'x{i}') for i in range(board_size)\n",
" ]\n",
" queens = [solver.IntVar(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",
@@ -105,8 +103,7 @@
" solver.Add(solver.AllDifferent([queens[i] + i for i in range(board_size)]))\n",
" solver.Add(solver.AllDifferent([queens[i] - i for i in range(board_size)]))\n",
"\n",
" db = solver.Phase(queens, solver.CHOOSE_FIRST_UNBOUND,\n",
" solver.ASSIGN_MIN_VALUE)\n",
" db = solver.Phase(queens, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n",
"\n",
" # Iterates through the solutions, displaying each.\n",
" num_solutions = 0\n",
@@ -117,20 +114,20 @@
" for j in range(board_size):\n",
" if queens[j].Value() == i:\n",
" # There is a queen in column j, row i.\n",
" print('Q', end=' ')\n",
" print(\"Q\", end=\" \")\n",
" else:\n",
" print('_', end=' ')\n",
" print(\"_\", end=\" \")\n",
" print()\n",
" print()\n",
" num_solutions += 1\n",
" solver.EndSearch()\n",
"\n",
" # Statistics.\n",
" print('\\nStatistics')\n",
" print(f' failures: {solver.Failures()}')\n",
" print(f' branches: {solver.Branches()}')\n",
" print(f' wall time: {solver.WallTime()} ms')\n",
" print(f' Solutions found: {num_solutions}')\n",
" print(\"\\nStatistics\")\n",
" print(f\" failures: {solver.Failures()}\")\n",
" print(f\" branches: {solver.Branches()}\")\n",
" print(f\" wall time: {solver.WallTime()} ms\")\n",
" print(f\" Solutions found: {num_solutions}\")\n",
"\n",
"\n",
"# By default, solve the 8x8 problem.\n",

View File

@@ -89,37 +89,38 @@
"def main():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" # Instantiate the solver.\n",
" solver = pywrapcp.Solver('CPSimple')\n",
" solver = pywrapcp.Solver(\"CPSimple\")\n",
"\n",
" # Create the variables.\n",
" num_vals = 3\n",
" x = solver.IntVar(0, num_vals - 1, 'x')\n",
" y = solver.IntVar(0, num_vals - 1, 'y')\n",
" z = solver.IntVar(0, num_vals - 1, 'z')\n",
" x = solver.IntVar(0, num_vals - 1, \"x\")\n",
" y = solver.IntVar(0, num_vals - 1, \"y\")\n",
" z = solver.IntVar(0, num_vals - 1, \"z\")\n",
"\n",
" # Constraint 0: x != y.\n",
" solver.Add(x != y)\n",
" print('Number of constraints: ', solver.Constraints())\n",
" print(\"Number of constraints: \", solver.Constraints())\n",
"\n",
" # Solve the problem.\n",
" decision_builder = solver.Phase([x, y, z], solver.CHOOSE_FIRST_UNBOUND,\n",
" solver.ASSIGN_MIN_VALUE)\n",
" decision_builder = solver.Phase(\n",
" [x, y, z], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE\n",
" )\n",
"\n",
" # Print solution on console.\n",
" count = 0\n",
" solver.NewSearch(decision_builder)\n",
" while solver.NextSolution():\n",
" count += 1\n",
" solution = f'Solution {count}:\\n'\n",
" solution = f\"Solution {count}:\\n\"\n",
" for var in [x, y, z]:\n",
" solution += f' {var.Name()} = {var.Value()}'\n",
" solution += f\" {var.Name()} = {var.Value()}\"\n",
" print(solution)\n",
" solver.EndSearch()\n",
" print(f'Number of solutions found: {count}')\n",
" print(f\"Number of solutions found: {count}\")\n",
"\n",
" print('Advanced usage:')\n",
" print(f'Problem solved in {solver.WallTime()}ms')\n",
" print(f'Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes')\n",
" print(\"Advanced usage:\")\n",
" print(f\"Problem solved in {solver.WallTime()}ms\")\n",
" print(f\"Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -100,7 +100,6 @@
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the absolute difference between the two nodes.\"\"\"\n",
@@ -117,23 +116,24 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" ) # pylint: disable=no-member\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",
"\n",
" # Print solution on console.\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" index = routing.Start(0)\n",
" plan_output = 'Route for vehicle 0:\\n'\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f'{manager.IndexToNode(index)} -> '\n",
" plan_output += f\"{manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
"\n",
"\n",

View File

@@ -95,20 +95,23 @@
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" # Locations in block units\n",
" locations = \\\n",
" [(4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8),]\n",
" locations = [\n",
" # fmt:off\n",
" (4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8)\n",
" # fmt:on\n",
" ]\n",
" # Convert locations in meters using a city block dimension of 114m x 80m.\n",
" data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations]\n",
" data['num_vehicles'] = 1\n",
" data['depot'] = 0\n",
" data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations]\n",
" data[\"num_vehicles\"] = 1\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
@@ -117,15 +120,15 @@
" distances_ = {}\n",
" index_manager_ = manager\n",
" # precompute distance between location to have distance callback in O(1)\n",
" for from_counter, from_node in enumerate(data['locations']):\n",
" for from_counter, from_node in enumerate(data[\"locations\"]):\n",
" distances_[from_counter] = {}\n",
" for to_counter, to_node in enumerate(data['locations']):\n",
" for to_counter, to_node in enumerate(data[\"locations\"]):\n",
" if from_counter == to_counter:\n",
" distances_[from_counter][to_counter] = 0\n",
" else:\n",
" distances_[from_counter][to_counter] = (\n",
" abs(from_node[0] - to_node[0]) +\n",
" abs(from_node[1] - to_node[1]))\n",
" distances_[from_counter][to_counter] = abs(\n",
" from_node[0] - to_node[0]\n",
" ) + abs(from_node[1] - to_node[1])\n",
"\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n",
@@ -139,17 +142,17 @@
"\n",
"def print_solution(manager, routing, assignment):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" index = routing.Start(0)\n",
" plan_output = 'Route for vehicle 0:\\n'\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
"\n",
"\n",
@@ -159,8 +162,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"locations\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -175,7 +179,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -92,57 +92,59 @@
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" # Locations in block units\n",
" data['locations'] = [\n",
" (288, 149), (288, 129), (270, 133), (256, 141), (256, 157), (246, 157),\n",
" (236, 169), (228, 169), (228, 161), (220, 169), (212, 169), (204, 169),\n",
" (196, 169), (188, 169), (196, 161), (188, 145), (172, 145), (164, 145),\n",
" (156, 145), (148, 145), (140, 145), (148, 169), (164, 169), (172, 169),\n",
" (156, 169), (140, 169), (132, 169), (124, 169), (116, 161), (104, 153),\n",
" (104, 161), (104, 169), (90, 165), (80, 157), (64, 157), (64, 165),\n",
" (56, 169), (56, 161), (56, 153), (56, 145), (56, 137), (56, 129),\n",
" (56, 121), (40, 121), (40, 129), (40, 137), (40, 145), (40, 153),\n",
" (40, 161), (40, 169), (32, 169), (32, 161), (32, 153), (32, 145),\n",
" (32, 137), (32, 129), (32, 121), (32, 113), (40, 113), (56, 113),\n",
" (56, 105), (48, 99), (40, 99), (32, 97), (32, 89), (24, 89),\n",
" (16, 97), (16, 109), (8, 109), (8, 97), (8, 89), (8, 81),\n",
" (8, 73), (8, 65), (8, 57), (16, 57), (8, 49), (8, 41),\n",
" (24, 45), (32, 41), (32, 49), (32, 57), (32, 65), (32, 73),\n",
" (32, 81), (40, 83), (40, 73), (40, 63), (40, 51), (44, 43),\n",
" (44, 35), (44, 27), (32, 25), (24, 25), (16, 25), (16, 17),\n",
" (24, 17), (32, 17), (44, 11), (56, 9), (56, 17), (56, 25),\n",
" (56, 33), (56, 41), (64, 41), (72, 41), (72, 49), (56, 49),\n",
" (48, 51), (56, 57), (56, 65), (48, 63), (48, 73), (56, 73),\n",
" (56, 81), (48, 83), (56, 89), (56, 97), (104, 97), (104, 105),\n",
" (104, 113), (104, 121), (104, 129), (104, 137), (104, 145), (116, 145),\n",
" (124, 145), (132, 145), (132, 137), (140, 137), (148, 137), (156, 137),\n",
" (164, 137), (172, 125), (172, 117), (172, 109), (172, 101), (172, 93),\n",
" (172, 85), (180, 85), (180, 77), (180, 69), (180, 61), (180, 53),\n",
" (172, 53), (172, 61), (172, 69), (172, 77), (164, 81), (148, 85),\n",
" (124, 85), (124, 93), (124, 109), (124, 125), (124, 117), (124, 101),\n",
" (104, 89), (104, 81), (104, 73), (104, 65), (104, 49), (104, 41),\n",
" (104, 33), (104, 25), (104, 17), (92, 9), (80, 9), (72, 9),\n",
" (64, 21), (72, 25), (80, 25), (80, 25), (80, 41), (88, 49),\n",
" (104, 57), (124, 69), (124, 77), (132, 81), (140, 65), (132, 61),\n",
" (124, 61), (124, 53), (124, 45), (124, 37), (124, 29), (132, 21),\n",
" (124, 21), (120, 9), (128, 9), (136, 9), (148, 9), (162, 9),\n",
" (156, 25), (172, 21), (180, 21), (180, 29), (172, 29), (172, 37),\n",
" (172, 45), (180, 45), (180, 37), (188, 41), (196, 49), (204, 57),\n",
" (212, 65), (220, 73), (228, 69), (228, 77), (236, 77), (236, 69),\n",
" (236, 61), (228, 61), (228, 53), (236, 53), (236, 45), (228, 45),\n",
" (228, 37), (236, 37), (236, 29), (228, 29), (228, 21), (236, 21),\n",
" (252, 21), (260, 29), (260, 37), (260, 45), (260, 53), (260, 61),\n",
" (260, 69), (260, 77), (276, 77), (276, 69), (276, 61), (276, 53),\n",
" (284, 53), (284, 61), (284, 69), (284, 77), (284, 85), (284, 93),\n",
" (284, 101), (288, 109), (280, 109), (276, 101), (276, 93), (276, 85),\n",
" (268, 97), (260, 109), (252, 101), (260, 93), (260, 85), (236, 85),\n",
" (228, 85), (228, 93), (236, 93), (236, 101), (228, 101), (228, 109),\n",
" (228, 117), (228, 125), (220, 125), (212, 117), (204, 109), (196, 101),\n",
" (188, 93), (180, 93), (180, 101), (180, 109), (180, 117), (180, 125),\n",
" (196, 145), (204, 145), (212, 145), (220, 145), (228, 145), (236, 145),\n",
" (246, 141), (252, 125), (260, 129), (280, 133)\n",
" ] # yapf: disable\n",
" data['num_vehicles'] = 1\n",
" data['depot'] = 0\n",
" data[\"locations\"] = [\n",
" # fmt: off\n",
" (288, 149), (288, 129), (270, 133), (256, 141), (256, 157), (246, 157),\n",
" (236, 169), (228, 169), (228, 161), (220, 169), (212, 169), (204, 169),\n",
" (196, 169), (188, 169), (196, 161), (188, 145), (172, 145), (164, 145),\n",
" (156, 145), (148, 145), (140, 145), (148, 169), (164, 169), (172, 169),\n",
" (156, 169), (140, 169), (132, 169), (124, 169), (116, 161), (104, 153),\n",
" (104, 161), (104, 169), (90, 165), (80, 157), (64, 157), (64, 165),\n",
" (56, 169), (56, 161), (56, 153), (56, 145), (56, 137), (56, 129),\n",
" (56, 121), (40, 121), (40, 129), (40, 137), (40, 145), (40, 153),\n",
" (40, 161), (40, 169), (32, 169), (32, 161), (32, 153), (32, 145),\n",
" (32, 137), (32, 129), (32, 121), (32, 113), (40, 113), (56, 113),\n",
" (56, 105), (48, 99), (40, 99), (32, 97), (32, 89), (24, 89),\n",
" (16, 97), (16, 109), (8, 109), (8, 97), (8, 89), (8, 81),\n",
" (8, 73), (8, 65), (8, 57), (16, 57), (8, 49), (8, 41),\n",
" (24, 45), (32, 41), (32, 49), (32, 57), (32, 65), (32, 73),\n",
" (32, 81), (40, 83), (40, 73), (40, 63), (40, 51), (44, 43),\n",
" (44, 35), (44, 27), (32, 25), (24, 25), (16, 25), (16, 17),\n",
" (24, 17), (32, 17), (44, 11), (56, 9), (56, 17), (56, 25),\n",
" (56, 33), (56, 41), (64, 41), (72, 41), (72, 49), (56, 49),\n",
" (48, 51), (56, 57), (56, 65), (48, 63), (48, 73), (56, 73),\n",
" (56, 81), (48, 83), (56, 89), (56, 97), (104, 97), (104, 105),\n",
" (104, 113), (104, 121), (104, 129), (104, 137), (104, 145), (116, 145),\n",
" (124, 145), (132, 145), (132, 137), (140, 137), (148, 137), (156, 137),\n",
" (164, 137), (172, 125), (172, 117), (172, 109), (172, 101), (172, 93),\n",
" (172, 85), (180, 85), (180, 77), (180, 69), (180, 61), (180, 53),\n",
" (172, 53), (172, 61), (172, 69), (172, 77), (164, 81), (148, 85),\n",
" (124, 85), (124, 93), (124, 109), (124, 125), (124, 117), (124, 101),\n",
" (104, 89), (104, 81), (104, 73), (104, 65), (104, 49), (104, 41),\n",
" (104, 33), (104, 25), (104, 17), (92, 9), (80, 9), (72, 9),\n",
" (64, 21), (72, 25), (80, 25), (80, 25), (80, 41), (88, 49),\n",
" (104, 57), (124, 69), (124, 77), (132, 81), (140, 65), (132, 61),\n",
" (124, 61), (124, 53), (124, 45), (124, 37), (124, 29), (132, 21),\n",
" (124, 21), (120, 9), (128, 9), (136, 9), (148, 9), (162, 9),\n",
" (156, 25), (172, 21), (180, 21), (180, 29), (172, 29), (172, 37),\n",
" (172, 45), (180, 45), (180, 37), (188, 41), (196, 49), (204, 57),\n",
" (212, 65), (220, 73), (228, 69), (228, 77), (236, 77), (236, 69),\n",
" (236, 61), (228, 61), (228, 53), (236, 53), (236, 45), (228, 45),\n",
" (228, 37), (236, 37), (236, 29), (228, 29), (228, 21), (236, 21),\n",
" (252, 21), (260, 29), (260, 37), (260, 45), (260, 53), (260, 61),\n",
" (260, 69), (260, 77), (276, 77), (276, 69), (276, 61), (276, 53),\n",
" (284, 53), (284, 61), (284, 69), (284, 77), (284, 85), (284, 93),\n",
" (284, 101), (288, 109), (280, 109), (276, 101), (276, 93), (276, 85),\n",
" (268, 97), (260, 109), (252, 101), (260, 93), (260, 85), (236, 85),\n",
" (228, 85), (228, 93), (236, 93), (236, 101), (228, 101), (228, 109),\n",
" (228, 117), (228, 125), (220, 125), (212, 117), (204, 109), (196, 101),\n",
" (188, 93), (180, 93), (180, 101), (180, 109), (180, 117), (180, 125),\n",
" (196, 145), (204, 145), (212, 145), (220, 145), (228, 145), (236, 145),\n",
" (246, 141), (252, 125), (260, 129), (280, 133)\n",
" # fmt: on\n",
" ]\n",
" data[\"num_vehicles\"] = 1\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
@@ -156,26 +158,26 @@
" distances[from_counter][to_counter] = 0\n",
" else:\n",
" # Euclidean distance\n",
" distances[from_counter][to_counter] = (int(\n",
" math.hypot((from_node[0] - to_node[0]),\n",
" (from_node[1] - to_node[1]))))\n",
" distances[from_counter][to_counter] = int(\n",
" math.hypot((from_node[0] - to_node[0]), (from_node[1] - to_node[1]))\n",
" )\n",
" return distances\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" index = routing.Start(0)\n",
" plan_output = 'Route:\\n'\n",
" plan_output = \"Route:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" print(plan_output)\n",
" plan_output += f'Objective: {route_distance}m\\n'\n",
" plan_output += f\"Objective: {route_distance}m\\n\"\n",
"\n",
"\n",
"def main():\n",
@@ -184,13 +186,14 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['locations']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"locations\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
" distance_matrix = compute_euclidean_distance_matrix(data['locations'])\n",
" distance_matrix = compute_euclidean_distance_matrix(data[\"locations\"])\n",
"\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
@@ -207,7 +210,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,7 +90,7 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" data[\"distance_matrix\"] = [\n",
" [0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],\n",
" [2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],\n",
" [713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],\n",
@@ -104,26 +104,26 @@
" [1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],\n",
" [2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],\n",
" [1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],\n",
" ] # yapf: disable\n",
" data['num_vehicles'] = 1\n",
" data['depot'] = 0\n",
" ]\n",
" data[\"num_vehicles\"] = 1\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()} miles')\n",
" print(f\"Objective: {solution.ObjectiveValue()} miles\")\n",
" index = routing.Start(0)\n",
" plan_output = 'Route for vehicle 0:\\n'\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" print(plan_output)\n",
" plan_output += f'Route distance: {route_distance}miles\\n'\n",
" plan_output += f\"Route distance: {route_distance}miles\\n\"\n",
"\n",
"\n",
"def main():\n",
@@ -132,8 +132,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -144,7 +145,7 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -154,7 +155,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,94 +90,45 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['num_vehicles'] = 1\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 1\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" index = routing.Start(0)\n",
" plan_output = 'Route for vehicle 0:\\n'\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
"\n",
"\n",
@@ -187,20 +138,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -210,7 +161,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -98,100 +98,52 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"\n",
@@ -201,20 +153,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -224,7 +176,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
@@ -233,7 +186,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -98,9 +98,9 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data['time_matrix'] = [\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" data[\"time_matrix\"] = [\n",
" [0, 27, 38, 34, 29, 13, 25, 9, 15, 9, 26, 25, 19, 17, 23, 38, 33],\n",
" [27, 0, 34, 15, 9, 25, 36, 17, 34, 37, 54, 29, 24, 33, 50, 43, 60],\n",
" [38, 34, 0, 49, 43, 25, 13, 40, 23, 37, 20, 63, 58, 56, 39, 77, 37],\n",
@@ -120,43 +120,45 @@
" [33, 60, 37, 67, 62, 35, 24, 42, 25, 23, 17, 42, 36, 26, 9, 39, 0],\n",
" ]\n",
" # 15 min of service time\n",
" data['service_time'] = [15] * len(data['time_matrix'])\n",
" data['service_time'][data['depot']] = 0\n",
" assert len(data['time_matrix']) == len(data['service_time'])\n",
" data[\"service_time\"] = [15] * len(data[\"time_matrix\"])\n",
" data[\"service_time\"][data[\"depot\"]] = 0\n",
" assert len(data[\"time_matrix\"]) == len(data[\"service_time\"])\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
"\n",
" print('Breaks:')\n",
" print(\"Breaks:\")\n",
" intervals = solution.IntervalVarContainer()\n",
" for i in range(intervals.Size()):\n",
" brk = intervals.Element(i)\n",
" if brk.PerformedValue():\n",
" print(f'{brk.Var().Name()}: ' +\n",
" f'Start({brk.StartValue()}) Duration({brk.DurationValue()})')\n",
" print(\n",
" f\"{brk.Var().Name()}: \"\n",
" + f\"Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n",
" )\n",
" else:\n",
" print(f'{brk.Var().Name()}: Unperformed')\n",
" print(f\"{brk.Var().Name()}: Unperformed\")\n",
"\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" total_time = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" while not routing.IsEnd(index):\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += f'{manager.IndexToNode(index)} '\n",
" plan_output += f'Time({solution.Value(time_var)}) -> '\n",
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
" plan_output += f\"Time({solution.Value(time_var)}) -> \"\n",
" index = solution.Value(routing.NextVar(index))\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += f'{manager.IndexToNode(index)} '\n",
" plan_output += f'Time({solution.Value(time_var)})\\n'\n",
" plan_output += f'Time of the route: {solution.Value(time_var)}min\\n'\n",
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
" plan_output += f\"Time({solution.Value(time_var)})\\n\"\n",
" plan_output += f\"Time of the route: {solution.Value(time_var)}min\\n\"\n",
" print(plan_output)\n",
" total_time += solution.Value(time_var)\n",
" print(f'Total time of all routes: {total_time}min')\n",
" print(f\"Total time of all routes: {total_time}min\")\n",
"\n",
"\n",
"def main():\n",
@@ -165,21 +167,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def time_callback(from_index, to_index):\n",
" \"\"\"Returns the travel time + service time between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to time matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['time_matrix'][from_node][to_node] + data['service_time'][\n",
" from_node]\n",
" return data[\"time_matrix\"][from_node][to_node] + data[\"service_time\"][from_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -187,13 +188,14 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 10, # needed optional waiting time to place break\n",
" 180, # maximum time per vehicle\n",
" True, # Force start cumul to zero.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" time_dimension.SetGlobalSpanCostCoefficient(10)\n",
"\n",
@@ -202,7 +204,7 @@
" node_visit_transit = [0] * routing.Size()\n",
" for index in range(routing.Size()):\n",
" node = manager.IndexToNode(index)\n",
" node_visit_transit[index] = data['service_time'][node]\n",
" node_visit_transit[index] = data[\"service_time\"][node]\n",
"\n",
" break_intervals = {}\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
@@ -212,19 +214,21 @@
" 60, # start max\n",
" 10, # duration: 10 min\n",
" False, # optional: no\n",
" f'Break for vehicle {v}')\n",
" f\"Break for vehicle {v}\",\n",
" )\n",
" ]\n",
" time_dimension.SetBreakIntervalsOfVehicle(\n",
" break_intervals[v], # breaks\n",
" v, # vehicle index\n",
" node_visit_transit)\n",
" break_intervals[v], v, node_visit_transit # breaks # vehicle index\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(2)\n",
"\n",
@@ -235,7 +239,7 @@
" if solution:\n",
" print_solution(manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -90,109 +90,61 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['demands'] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]\n",
" data['vehicle_capacities'] = [15, 15, 15, 15]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"demands\"] = [0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]\n",
" data[\"vehicle_capacities\"] = [15, 15, 15, 15]\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" total_load = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" route_load = 0\n",
" while not routing.IsEnd(index):\n",
" node_index = manager.IndexToNode(index)\n",
" route_load += data['demands'][node_index]\n",
" plan_output += f' {node_index} Load({route_load}) -> '\n",
" route_load += data[\"demands\"][node_index]\n",
" plan_output += f\" {node_index} Load({route_load}) -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f' {manager.IndexToNode(index)} Load({route_load})\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f'Load of the route: {route_load}\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\" {manager.IndexToNode(index)} Load({route_load})\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" plan_output += f\"Load of the route: {route_load}\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" total_load += route_load\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 distance of all routes: {total_distance}m\")\n",
" print(f\"Total load of all routes: {total_load}\")\n",
"\n",
"\n",
"def main():\n",
@@ -201,49 +153,50 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
" # Define cost of each arc.\n",
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
"\n",
" # Add Capacity constraint.\n",
" def demand_callback(from_index):\n",
" \"\"\"Returns the demand of the node.\"\"\"\n",
" # Convert from routing variable Index to demands NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" return data['demands'][from_node]\n",
" return data[\"demands\"][from_node]\n",
"\n",
" demand_callback_index = routing.RegisterUnaryTransitCallback(\n",
" demand_callback)\n",
" demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)\n",
" routing.AddDimensionWithVehicleCapacity(\n",
" demand_callback_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_capacities'], # vehicle maximum capacities\n",
" data[\"vehicle_capacities\"], # vehicle maximum capacities\n",
" True, # start cumul to zero\n",
" 'Capacity')\n",
" \"Capacity\",\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",
" # Solve the problem.\n",

View File

@@ -90,118 +90,70 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['demands'] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]\n",
" data['vehicle_capacities'] = [15, 15, 15, 15]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"demands\"] = [0, 1, 1, 3, 6, 3, 6, 8, 8, 1, 2, 1, 2, 6, 6, 8, 8]\n",
" data[\"vehicle_capacities\"] = [15, 15, 15, 15]\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" # Display dropped nodes.\n",
" dropped_nodes = 'Dropped nodes:'\n",
" dropped_nodes = \"Dropped nodes:\"\n",
" for node in range(routing.Size()):\n",
" if routing.IsStart(node) or routing.IsEnd(node):\n",
" continue\n",
" if assignment.Value(routing.NextVar(node)) == node:\n",
" dropped_nodes += f' {manager.IndexToNode(node)}'\n",
" dropped_nodes += f\" {manager.IndexToNode(node)}\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
" total_distance = 0\n",
" total_load = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" route_load = 0\n",
" while not routing.IsEnd(index):\n",
" node_index = manager.IndexToNode(index)\n",
" route_load += data['demands'][node_index]\n",
" plan_output += f' {node_index} Load({route_load}) -> '\n",
" route_load += data[\"demands\"][node_index]\n",
" plan_output += f\" {node_index} Load({route_load}) -> \"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f' {manager.IndexToNode(index)} Load({route_load})\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f'Load of the route: {route_load}\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\" {manager.IndexToNode(index)} Load({route_load})\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" plan_output += f\"Load of the route: {route_load}\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" total_load += route_load\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 Distance of all routes: {total_distance}m\")\n",
" print(f\"Total Load of all routes: {total_load}\")\n",
"\n",
"\n",
"def main():\n",
@@ -210,53 +162,54 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
" # Define cost of each arc.\n",
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
"\n",
" # Add Capacity constraint.\n",
" def demand_callback(from_index):\n",
" \"\"\"Returns the demand of the node.\"\"\"\n",
" # Convert from routing variable Index to demands NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" return data['demands'][from_node]\n",
" return data[\"demands\"][from_node]\n",
"\n",
" demand_callback_index = routing.RegisterUnaryTransitCallback(\n",
" demand_callback)\n",
" demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)\n",
" routing.AddDimensionWithVehicleCapacity(\n",
" demand_callback_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_capacities'], # vehicle maximum capacities\n",
" data[\"vehicle_capacities\"], # vehicle maximum capacities\n",
" True, # start cumul to zero\n",
" 'Capacity')\n",
" \"Capacity\",\n",
" )\n",
" # Allow to drop nodes.\n",
" penalty = 1000\n",
" for node in range(1, len(data['distance_matrix'])):\n",
" for node in range(1, len(data[\"distance_matrix\"])):\n",
" routing.AddDisjunction([manager.NodeToIndex(node)], penalty)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",
" # Solve the problem.\n",

View File

@@ -98,100 +98,52 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" max_route_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" max_route_distance = max(route_distance, max_route_distance)\n",
" print(f'Maximum of the route distances: {max_route_distance}m')\n",
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
@@ -201,20 +153,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -222,20 +174,22 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
@@ -244,7 +198,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -90,106 +90,58 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['initial_routes'] = [\n",
" data[\"initial_routes\"] = [\n",
" [8, 16, 14, 13, 12, 11],\n",
" [3, 4, 9, 10],\n",
" [15, 1],\n",
" [7, 5, 2, 6],\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" max_route_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" max_route_distance = max(route_distance, max_route_distance)\n",
" print(f'Maximum of the route distances: {max_route_distance}m')\n",
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
@@ -199,20 +151,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -220,13 +172,14 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
@@ -242,18 +195,18 @@
" routing.CloseModelWithParameters(search_parameters)\n",
"\n",
" # Get initial solution from routes after closing the model.\n",
" initial_solution = routing.ReadAssignmentFromRoutes(data['initial_routes'],\n",
" True)\n",
" print('Initial solution:')\n",
" initial_solution = routing.ReadAssignmentFromRoutes(data[\"initial_routes\"], True)\n",
" print(\"Initial solution:\")\n",
" print_solution(data, manager, routing, initial_solution)\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveFromAssignmentWithParameters(\n",
" initial_solution, search_parameters)\n",
" initial_solution, search_parameters\n",
" )\n",
"\n",
" # Print solution on console.\n",
" if solution:\n",
" print('Solution after search:')\n",
" print(\"Solution after search:\")\n",
" print_solution(data, manager, routing, solution)\n",
"\n",
"\n",

View File

@@ -94,77 +94,28 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['value'] = [\n",
" data[\"value\"] = [\n",
" 0, # depot\n",
" 42, # 1\n",
" 42, # 2\n",
@@ -183,23 +134,23 @@
" 42, # 15\n",
" 42, # 16\n",
" ]\n",
" assert len(data['distance_matrix']) == len(data['value'])\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" assert len(data[\"distance_matrix\"]) == len(data[\"value\"])\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" max_route_distance = 0\n",
" dim_one = routing.GetDimensionOrDie('One')\n",
" dim_two = routing.GetDimensionOrDie('Two')\n",
" dim_one = routing.GetDimensionOrDie(\"One\")\n",
" dim_two = routing.GetDimensionOrDie(\"Two\")\n",
"\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" one_var = dim_one.CumulVar(index)\n",
@@ -207,23 +158,27 @@
" two_var = dim_two.CumulVar(index)\n",
" two_slack_var = dim_two.SlackVar(index)\n",
" plan_output += (\n",
" f' N:{manager.IndexToNode(index)}'\n",
" f' one:({solution.Value(one_var)}, {solution.Value(one_slack_var)})'\n",
" f' two:({solution.Value(two_var)}, {solution.Value(two_slack_var)})'\n",
" ' -> ')\n",
" f\" N:{manager.IndexToNode(index)}\"\n",
" f\" one:({solution.Value(one_var)}, {solution.Value(one_slack_var)})\"\n",
" f\" two:({solution.Value(two_var)}, {solution.Value(two_slack_var)})\"\n",
" \" -> \"\n",
" )\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" previous_index, index, vehicle_id\n",
" )\n",
" one_var = dim_one.CumulVar(index)\n",
" two_var = dim_two.CumulVar(index)\n",
" plan_output += (f'N:{manager.IndexToNode(index)}'\n",
" f' one:{solution.Value(one_var)}'\n",
" f' two:{solution.Value(two_var)}\\n')\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += (\n",
" f\"N:{manager.IndexToNode(index)}\"\n",
" f\" one:{solution.Value(one_var)}\"\n",
" f\" two:{solution.Value(two_var)}\\n\"\n",
" )\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" max_route_distance = max(route_distance, max_route_distance)\n",
" print(f'Maximum of the route distances: {max_route_distance}m')\n",
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
@@ -233,8 +188,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -246,7 +202,7 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -254,13 +210,14 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3_000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(10)\n",
"\n",
@@ -272,8 +229,9 @@
" 42 * 16, # capacity: be able to store PEAK*ROUTE_LENGTH in worst case\n",
" 42, # slack_max: to be able to store peak in slack\n",
" True, # Fix StartCumulToZero not really matter here\n",
" 'One')\n",
" dim_one = routing.GetDimensionOrDie('One')\n",
" \"One\",\n",
" )\n",
" dim_one = routing.GetDimensionOrDie(\"One\")\n",
"\n",
" # Dimension Two will be used to store the max node value in the route end node\n",
" # CumulVar so we can use it as an objective cost.\n",
@@ -282,16 +240,17 @@
" 42 * 16, # capacity: be able to have PEAK value in CumulVar(End)\n",
" 42, # slack_max: to be able to store peak in slack\n",
" True, # Fix StartCumulToZero YES here\n",
" 'Two')\n",
" dim_two = routing.GetDimensionOrDie('Two')\n",
" \"Two\",\n",
" )\n",
" dim_two = routing.GetDimensionOrDie(\"Two\")\n",
"\n",
" # force depot Slack to be value since we don't have any predecessor...\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" start = routing.Start(v)\n",
" dim_one.SlackVar(start).SetValue(data['value'][0])\n",
" dim_one.SlackVar(start).SetValue(data[\"value\"][0])\n",
" routing.AddToAssignment(dim_one.SlackVar(start))\n",
"\n",
" dim_two.SlackVar(start).SetValue(data['value'][0])\n",
" dim_two.SlackVar(start).SetValue(data[\"value\"][0])\n",
" routing.AddToAssignment(dim_two.SlackVar(start))\n",
"\n",
" # Step by step relation\n",
@@ -305,14 +264,12 @@
" for v in range(manager.GetNumberOfVehicles()):\n",
" previous_index = routing.Start(v)\n",
" cond = routing.NextVar(previous_index) == index\n",
" value = solver.Max(dim_one.SlackVar(previous_index),\n",
" data['value'][node])\n",
" value = solver.Max(dim_one.SlackVar(previous_index), data[\"value\"][node])\n",
" test.append((cond * value).Var())\n",
" for previous in range(1, 17):\n",
" previous_index = manager.NodeToIndex(previous)\n",
" cond = routing.NextVar(previous_index) == index\n",
" value = solver.Max(dim_one.SlackVar(previous_index),\n",
" data['value'][node])\n",
" value = solver.Max(dim_one.SlackVar(previous_index), data[\"value\"][node])\n",
" test.append((cond * value).Var())\n",
" solver.Add(solver.Sum(test) == dim_one.SlackVar(index))\n",
"\n",
@@ -335,9 +292,11 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(5)\n",
"\n",
@@ -348,7 +307,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -90,77 +90,28 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['pickups_deliveries'] = [\n",
" data[\"pickups_deliveries\"] = [\n",
" [1, 6],\n",
" [2, 10],\n",
" [4, 3],\n",
@@ -170,30 +121,31 @@
" [13, 12],\n",
" [16, 14],\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"def main():\n",
@@ -202,8 +154,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -215,38 +168,41 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Define Transportation Requests.\n",
" for request in data['pickups_deliveries']:\n",
" for request in data[\"pickups_deliveries\"]:\n",
" pickup_index = manager.NodeToIndex(request[0])\n",
" delivery_index = manager.NodeToIndex(request[1])\n",
" routing.AddPickupAndDelivery(pickup_index, delivery_index)\n",
" routing.solver().Add(\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(\n",
" delivery_index))\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n",
" )\n",
" routing.solver().Add(\n",
" distance_dimension.CumulVar(pickup_index) <=\n",
" distance_dimension.CumulVar(delivery_index))\n",
" distance_dimension.CumulVar(pickup_index)\n",
" <= distance_dimension.CumulVar(delivery_index)\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,77 +90,28 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['pickups_deliveries'] = [\n",
" data[\"pickups_deliveries\"] = [\n",
" [1, 6],\n",
" [2, 10],\n",
" [4, 3],\n",
@@ -170,30 +121,31 @@
" [13, 12],\n",
" [16, 14],\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"def main():\n",
@@ -202,8 +154,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -215,40 +168,44 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Define Transportation Requests.\n",
" for request in data['pickups_deliveries']:\n",
" for request in data[\"pickups_deliveries\"]:\n",
" pickup_index = manager.NodeToIndex(request[0])\n",
" delivery_index = manager.NodeToIndex(request[1])\n",
" routing.AddPickupAndDelivery(pickup_index, delivery_index)\n",
" routing.solver().Add(\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(\n",
" delivery_index))\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n",
" )\n",
" routing.solver().Add(\n",
" distance_dimension.CumulVar(pickup_index) <=\n",
" distance_dimension.CumulVar(delivery_index))\n",
" distance_dimension.CumulVar(pickup_index)\n",
" <= distance_dimension.CumulVar(delivery_index)\n",
" )\n",
" routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n",
" pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_FIFO)\n",
" pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_FIFO\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,77 +90,28 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['pickups_deliveries'] = [\n",
" data[\"pickups_deliveries\"] = [\n",
" [1, 6],\n",
" [2, 10],\n",
" [4, 3],\n",
@@ -170,30 +121,31 @@
" [13, 12],\n",
" [16, 14],\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"def main():\n",
@@ -202,8 +154,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -215,40 +168,44 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Define Transportation Requests.\n",
" for request in data['pickups_deliveries']:\n",
" for request in data[\"pickups_deliveries\"]:\n",
" pickup_index = manager.NodeToIndex(request[0])\n",
" delivery_index = manager.NodeToIndex(request[1])\n",
" routing.AddPickupAndDelivery(pickup_index, delivery_index)\n",
" routing.solver().Add(\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(\n",
" delivery_index))\n",
" routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)\n",
" )\n",
" routing.solver().Add(\n",
" distance_dimension.CumulVar(pickup_index) <=\n",
" distance_dimension.CumulVar(delivery_index))\n",
" distance_dimension.CumulVar(pickup_index)\n",
" <= distance_dimension.CumulVar(delivery_index)\n",
" )\n",
" routing.SetPickupAndDeliveryPolicyOfAllVehicles(\n",
" pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_LIFO)\n",
" pywrapcp.RoutingModel.PICKUP_AND_DELIVERY_LIFO\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION)\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,7 +90,7 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['time_matrix'] = [\n",
" data[\"time_matrix\"] = [\n",
" [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n",
" [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n",
" [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n",
@@ -109,7 +109,7 @@
" [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n",
" [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n",
" ]\n",
" data['time_windows'] = [\n",
" data[\"time_windows\"] = [\n",
" (0, 5), # depot\n",
" (7, 12), # 1\n",
" (10, 15), # 2\n",
@@ -128,37 +128,39 @@
" (10, 15), # 15\n",
" (5, 15), # 16\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['vehicle_load_time'] = 5\n",
" data['vehicle_unload_time'] = 5\n",
" data['depot_capacity'] = 2\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"vehicle_load_time\"] = 5\n",
" data[\"vehicle_unload_time\"] = 5\n",
" data[\"depot_capacity\"] = 2\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" total_time = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" while not routing.IsEnd(index):\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f'{manager.IndexToNode(index)}'\n",
" f' Time({solution.Min(time_var)}, {solution.Max(time_var)})'\n",
" ' -> ')\n",
" f\"{manager.IndexToNode(index)}\"\n",
" f\" Time({solution.Min(time_var)}, {solution.Max(time_var)})\"\n",
" \" -> \"\n",
" )\n",
" index = solution.Value(routing.NextVar(index))\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f'{manager.IndexToNode(index)}'\n",
" f' Time({solution.Min(time_var)},{solution.Max(time_var)})\\n')\n",
" plan_output += f'Time of the route: {solution.Min(time_var)}min\\n'\n",
" f\"{manager.IndexToNode(index)}\"\n",
" f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\\n\"\n",
" )\n",
" plan_output += f\"Time of the route: {solution.Min(time_var)}min\\n\"\n",
" print(plan_output)\n",
" total_time += solution.Min(time_var)\n",
" print(f'Total time of all routes: {total_time}min')\n",
" print(f\"Total time of all routes: {total_time}min\")\n",
"\n",
"\n",
"def main():\n",
@@ -167,20 +169,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def time_callback(from_index, to_index):\n",
" \"\"\"Returns the travel time between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to time matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['time_matrix'][from_node][to_node]\n",
" return data[\"time_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -188,57 +190,66 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 60, # allow waiting time\n",
" 60, # maximum time per vehicle\n",
" False, # Don't force start cumul to zero.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot.\n",
" for location_idx, time_window in enumerate(data['time_windows']):\n",
" for location_idx, time_window in enumerate(data[\"time_windows\"]):\n",
" if location_idx == 0:\n",
" continue\n",
" index = manager.NodeToIndex(location_idx)\n",
" time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n",
" # Add time window constraints for each vehicle start node.\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n",
" data['time_windows'][0][1])\n",
" time_dimension.CumulVar(index).SetRange(\n",
" data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n",
" )\n",
"\n",
" # Add resource constraints at the depot.\n",
" solver = routing.solver()\n",
" intervals = []\n",
" for i in range(data['num_vehicles']):\n",
" for i in range(data[\"num_vehicles\"]):\n",
" # Add time windows at start of routes\n",
" intervals.append(\n",
" solver.FixedDurationIntervalVar(\n",
" time_dimension.CumulVar(routing.Start(i)),\n",
" data['vehicle_load_time'], 'depot_interval'))\n",
" data[\"vehicle_load_time\"],\n",
" \"depot_interval\",\n",
" )\n",
" )\n",
" # Add time windows at end of routes.\n",
" intervals.append(\n",
" solver.FixedDurationIntervalVar(\n",
" time_dimension.CumulVar(routing.End(i)),\n",
" data['vehicle_unload_time'], 'depot_interval'))\n",
" data[\"vehicle_unload_time\"],\n",
" \"depot_interval\",\n",
" )\n",
" )\n",
"\n",
" depot_usage = [1 for i in range(len(intervals))]\n",
" solver.Add(\n",
" solver.Cumulative(intervals, depot_usage, data['depot_capacity'],\n",
" 'depot'))\n",
" solver.Cumulative(intervals, depot_usage, data[\"depot_capacity\"], \"depot\")\n",
" )\n",
"\n",
" # Instantiate route start and end times to produce feasible times.\n",
" for i in range(data['num_vehicles']):\n",
" for i in range(data[\"num_vehicles\"]):\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.Start(i)))\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.End(i)))\n",
" time_dimension.CumulVar(routing.Start(i))\n",
" )\n",
" routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
@@ -247,7 +258,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -98,109 +98,66 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(routing_manager: pywrapcp.RoutingIndexManager,\n",
" routing_model: pywrapcp.RoutingModel):\n",
"def print_solution(\n",
" routing_manager: pywrapcp.RoutingIndexManager, routing_model: pywrapcp.RoutingModel\n",
"):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print('################')\n",
" print(f'Solution objective: {routing_model.CostVar().Value()}')\n",
" print(\"################\")\n",
" print(f\"Solution objective: {routing_model.CostVar().Value()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(routing_manager.GetNumberOfVehicles()):\n",
" index = routing_model.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing_model.IsEnd(index):\n",
" plan_output += f' {routing_manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {routing_manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = routing_model.NextVar(index).Value()\n",
" route_distance += routing_model.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f' {routing_manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\" {routing_manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"class SolutionCallback:\n",
" \"\"\"Create a solution callback.\"\"\"\n",
"\n",
" def __init__(self, manager: pywrapcp.RoutingIndexManager,\n",
" model: pywrapcp.RoutingModel, limit: int):\n",
" def __init__(\n",
" self,\n",
" manager: pywrapcp.RoutingIndexManager,\n",
" model: pywrapcp.RoutingModel,\n",
" limit: int,\n",
" ):\n",
" self._routing_manager = manager\n",
" self._routing_model = model\n",
" self._counter = 0\n",
@@ -223,9 +180,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" routing_manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'],\n",
" data['depot'])\n",
" routing_manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing_model = pywrapcp.RoutingModel(routing_manager)\n",
@@ -237,22 +194,22 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = routing_manager.IndexToNode(from_index)\n",
" to_node = routing_manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing_model.RegisterTransitCallback(\n",
" distance_callback)\n",
" transit_callback_index = routing_model.RegisterTransitCallback(distance_callback)\n",
"\n",
" # Define cost of each arc.\n",
" routing_model.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing_model.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing_model.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
@@ -263,9 +220,11 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(5)\n",
"\n",
" # Solve the problem.\n",
@@ -273,9 +232,9 @@
"\n",
" # Print solution on console.\n",
" if solution:\n",
" print(f'Best objective: {solution_callback.objectives[-1]}')\n",
" print(f\"Best objective: {solution_callback.objectives[-1]}\")\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -90,101 +90,53 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['distance_matrix'] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" ],\n",
" data[\"distance_matrix\"] = [\n",
" # fmt: off\n",
" [0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],\n",
" [548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],\n",
" [776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],\n",
" [696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],\n",
" [582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],\n",
" [274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],\n",
" [502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],\n",
" [194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],\n",
" [308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],\n",
" [194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],\n",
" [536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],\n",
" [502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],\n",
" [388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],\n",
" [354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],\n",
" [468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],\n",
" [776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],\n",
" [662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0],\n",
" # fmt: on\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['starts'] = [1, 2, 15, 16]\n",
" data['ends'] = [0, 0, 0, 0]\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"starts\"] = [1, 2, 15, 16]\n",
" data[\"ends\"] = [0, 0, 0, 0]\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" max_route_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" max_route_distance = max(route_distance, max_route_distance)\n",
" print(f'Maximum of the route distances: {max_route_distance}m')\n",
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"def main():\n",
@@ -193,21 +145,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['starts'],\n",
" data['ends'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"starts\"], data[\"ends\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -215,20 +166,22 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 2000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -90,7 +90,7 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['time_matrix'] = [\n",
" data[\"time_matrix\"] = [\n",
" [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n",
" [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n",
" [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n",
@@ -109,7 +109,7 @@
" [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n",
" [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n",
" ]\n",
" data['time_windows'] = [\n",
" data[\"time_windows\"] = [\n",
" (0, 5), # depot\n",
" (7, 12), # 1\n",
" (10, 15), # 2\n",
@@ -128,34 +128,36 @@
" (10, 15), # 15\n",
" (11, 15), # 16\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" total_time = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" while not routing.IsEnd(index):\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f'{manager.IndexToNode(index)}'\n",
" f' Time({solution.Min(time_var)},{solution.Max(time_var)})'\n",
" ' -> ')\n",
" f\"{manager.IndexToNode(index)}\"\n",
" f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\"\n",
" \" -> \"\n",
" )\n",
" index = solution.Value(routing.NextVar(index))\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f'{manager.IndexToNode(index)}'\n",
" f' Time({solution.Min(time_var)},{solution.Max(time_var)})\\n')\n",
" plan_output += f'Time of the route: {solution.Min(time_var)}min\\n'\n",
" f\"{manager.IndexToNode(index)}\"\n",
" f\" Time({solution.Min(time_var)},{solution.Max(time_var)})\\n\"\n",
" )\n",
" plan_output += f\"Time of the route: {solution.Min(time_var)}min\\n\"\n",
" print(plan_output)\n",
" total_time += solution.Min(time_var)\n",
" print(f'Total time of all routes: {total_time}min')\n",
" print(f\"Total time of all routes: {total_time}min\")\n",
"\n",
"\n",
"def main():\n",
@@ -164,20 +166,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def time_callback(from_index, to_index):\n",
" \"\"\"Returns the travel time between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to time matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['time_matrix'][from_node][to_node]\n",
" return data[\"time_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -185,39 +187,41 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 30, # allow waiting time\n",
" 30, # maximum time per vehicle\n",
" False, # Don't force start cumul to zero.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot.\n",
" for location_idx, time_window in enumerate(data['time_windows']):\n",
" if location_idx == data['depot']:\n",
" for location_idx, time_window in enumerate(data[\"time_windows\"]):\n",
" if location_idx == data[\"depot\"]:\n",
" continue\n",
" index = manager.NodeToIndex(location_idx)\n",
" time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n",
" # Add time window constraints for each vehicle start node.\n",
" depot_idx = data['depot']\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" depot_idx = data[\"depot\"]\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(\n",
" data['time_windows'][depot_idx][0],\n",
" data['time_windows'][depot_idx][1])\n",
" data[\"time_windows\"][depot_idx][0], data[\"time_windows\"][depot_idx][1]\n",
" )\n",
"\n",
" # Instantiate route start and end times to produce feasible times.\n",
" for i in range(data['num_vehicles']):\n",
" for i in range(data[\"num_vehicles\"]):\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.Start(i)))\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.End(i)))\n",
" time_dimension.CumulVar(routing.Start(i))\n",
" )\n",
" routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Simple VRP with special locations which need to be visited at end of the route.\n"
]
},
@@ -90,7 +91,7 @@
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" # Special location don't consume token, while regular one consume one\n",
" data['tokens'] = [\n",
" data[\"tokens\"] = [\n",
" 0, # 0 depot\n",
" 0, # 1 special node\n",
" 0, # 2 special node\n",
@@ -112,20 +113,20 @@
" -1, # 18\n",
" ]\n",
" # just need to be big enough, not a limiting factor\n",
" data['vehicle_tokens'] = [20, 20, 20, 20]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"vehicle_tokens\"] = [20, 20, 20, 20]\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" token_dimension = routing.GetDimensionOrDie('Token')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" token_dimension = routing.GetDimensionOrDie(\"Token\")\n",
" total_distance = 0\n",
" total_token = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" index = routing.Start(vehicle_id)\n",
" total_token += solution.Value(token_dimension.CumulVar(index))\n",
" route_distance = 0\n",
@@ -134,20 +135,21 @@
" node_index = manager.IndexToNode(index)\n",
" token_var = token_dimension.CumulVar(index)\n",
" route_token = solution.Value(token_var)\n",
" plan_output += f' {node_index} Token({route_token}) -> '\n",
" plan_output += f\" {node_index} Token({route_token}) -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" previous_index, index, vehicle_id\n",
" )\n",
" node_index = manager.IndexToNode(index)\n",
" token_var = token_dimension.CumulVar(index)\n",
" route_token = solution.Value(token_var)\n",
" plan_output += f' {node_index} Token({route_token})\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f\" {node_index} Token({route_token})\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" total_distance += route_distance\n",
" print(plan_output)\n",
" print(f'Total distance of all routes: {total_distance}m')\n",
" print(f'Total token of all routes: {total_token}')\n",
" print(f\"Total distance of all routes: {total_distance}m\")\n",
" print(f\"Total token of all routes: {total_token}\")\n",
"\n",
"\n",
"def main():\n",
@@ -156,8 +158,9 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['tokens']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"tokens\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -176,8 +179,9 @@
" 0, # null slack\n",
" 3000, # maximum distance per vehicle\n",
" True, # start cumul to zero\n",
" 'distance')\n",
" distance_dimension = routing.GetDimensionOrDie('distance')\n",
" \"distance\",\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(\"distance\")\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Define cost of each arc.\n",
@@ -188,17 +192,18 @@
" \"\"\"Returns the number of token consumed by the node.\"\"\"\n",
" # Convert from routing variable Index to tokens NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" return data['tokens'][from_node]\n",
" return data[\"tokens\"][from_node]\n",
"\n",
" token_callback_index = routing.RegisterUnaryTransitCallback(token_callback)\n",
" routing.AddDimensionWithVehicleCapacity(\n",
" token_callback_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_tokens'], # vehicle maximum tokens\n",
" data[\"vehicle_tokens\"], # vehicle maximum tokens\n",
" False, # start cumul to zero\n",
" 'Token')\n",
" \"Token\",\n",
" )\n",
" # Add constraint: special node can only be visited if token remaining is zero\n",
" token_dimension = routing.GetDimensionOrDie('Token')\n",
" token_dimension = routing.GetDimensionOrDie(\"Token\")\n",
" for node in range(1, 6):\n",
" index = manager.NodeToIndex(node)\n",
" routing.solver().Add(token_dimension.CumulVar(index) == 0)\n",
@@ -206,16 +211,20 @@
" # Instantiate route start and end times to produce feasible times.\n",
" for i in range(manager.GetNumberOfVehicles()):\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" token_dimension.CumulVar(routing.Start(i)))\n",
" token_dimension.CumulVar(routing.Start(i))\n",
" )\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" token_dimension.CumulVar(routing.End(i)))\n",
" token_dimension.CumulVar(routing.End(i))\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",
" # Solve the problem.\n",
@@ -225,7 +234,7 @@
" if solution:\n",
" print_solution(manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -89,23 +89,24 @@
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" max_route_distance = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} -> '\n",
" plan_output += f\" {manager.IndexToNode(index)} -> \"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f'{manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\"{manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" max_route_distance = max(route_distance, max_route_distance)\n",
" print(f'Maximum of the route distances: {max_route_distance}m')\n",
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"def main():\n",
@@ -134,22 +135,25 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 3000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(5)\n",
"\n",

View File

@@ -101,55 +101,50 @@
" data = {}\n",
" # Locations in block unit\n",
" locations_ = [\n",
" (4, 4), # depot\n",
" (2, 0),\n",
" (8, 0), # locations to visit\n",
" (0, 1),\n",
" (1, 1),\n",
" (5, 2),\n",
" (7, 2),\n",
" (3, 3),\n",
" (6, 3),\n",
" (5, 5),\n",
" (8, 5),\n",
" (1, 6),\n",
" (2, 6),\n",
" (3, 7),\n",
" (6, 7),\n",
" (0, 8),\n",
" (7, 8),\n",
" # fmt: off\n",
" (4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8),\n",
" # fmt: on\n",
" ]\n",
" # Compute locations in meters using the block dimension defined as follow\n",
" # Manhattan average block: 750ft x 264ft -> 228m x 80m\n",
" # here we use: 114m x 80m city block\n",
" # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n",
" data['locations'] = [(l[0] * 114, l[1] * 80) for l in locations_]\n",
" data['num_locations'] = len(data['locations'])\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations_]\n",
" data[\"num_locations\"] = len(data[\"locations\"])\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f' {manager.IndexToNode(index)} ->'\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id)\n",
" plan_output += f' {manager.IndexToNode(index)}\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" previous_index, index, vehicle_id\n",
" )\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"\n",
@@ -158,22 +153,22 @@
"#######################\n",
"def manhattan_distance(position_1, position_2):\n",
" \"\"\"Computes the Manhattan distance between two points.\"\"\"\n",
" return (abs(position_1[0] - position_2[0]) +\n",
" abs(position_1[1] - position_2[1]))\n",
" return abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])\n",
"\n",
"\n",
"def create_distance_evaluator(data):\n",
" \"\"\"Creates callback to return distance between points.\"\"\"\n",
" distances_ = {}\n",
" # precompute distance between location to have distance callback in O(1)\n",
" for from_node in range(data['num_locations']):\n",
" for from_node in range(data[\"num_locations\"]):\n",
" distances_[from_node] = {}\n",
" for to_node in range(data['num_locations']):\n",
" for to_node in range(data[\"num_locations\"]):\n",
" if from_node == to_node:\n",
" distances_[from_node][to_node] = 0\n",
" else:\n",
" distances_[from_node][to_node] = (manhattan_distance(\n",
" data['locations'][from_node], data['locations'][to_node]))\n",
" distances_[from_node][to_node] = manhattan_distance(\n",
" data[\"locations\"][from_node], data[\"locations\"][to_node]\n",
" )\n",
"\n",
" def distance_evaluator(manager, from_index, to_index):\n",
" \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n",
@@ -187,13 +182,14 @@
"\n",
"def add_distance_dimension(routing, distance_evaluator_index):\n",
" \"\"\"Add Global Span constraint.\"\"\"\n",
" distance = 'Distance'\n",
" distance = \"Distance\"\n",
" routing.AddDimension(\n",
" distance_evaluator_index,\n",
" 0, # null slack\n",
" 3000, # maximum distance per vehicle\n",
" True, # start cumul to zero\n",
" distance)\n",
" distance,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(distance)\n",
" # Try to minimize the max distance among vehicles.\n",
" # /!\\ It doesn't mean the standard deviation is minimized\n",
@@ -206,15 +202,17 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" data[\"num_locations\"], data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
" # Define weight of each edge\n",
" distance_evaluator_index = routing.RegisterTransitCallback(\n",
" functools.partial(create_distance_evaluator(data), manager))\n",
" functools.partial(create_distance_evaluator(data), manager)\n",
" )\n",
"\n",
" # Define cost of each arc.\n",
" routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n",
@@ -225,7 +223,8 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
@@ -234,7 +233,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -90,7 +90,7 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['time_matrix'] = [\n",
" data[\"time_matrix\"] = [\n",
" [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n",
" [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n",
" [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n",
@@ -109,7 +109,7 @@
" [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n",
" [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n",
" ]\n",
" data['time_windows'] = [\n",
" data[\"time_windows\"] = [\n",
" (0, 5), # depot\n",
" (7, 12), # 1\n",
" (10, 15), # 2\n",
@@ -128,8 +128,8 @@
" (10, 15), # 15\n",
" (11, 15), # 16\n",
" ]\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
@@ -137,21 +137,35 @@
"def print_solution(routes, cumul_data):\n",
" \"\"\"Print the solution.\"\"\"\n",
" total_time = 0\n",
" route_str = ''\n",
" route_str = \"\"\n",
" for i, route in enumerate(routes):\n",
" route_str += 'Route ' + str(i) + ':\\n'\n",
" route_str += \"Route \" + str(i) + \":\\n\"\n",
" start_time = cumul_data[i][0][0]\n",
" end_time = cumul_data[i][0][1]\n",
" route_str += ' ' + str(route[0]) + \\\n",
" ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n",
" route_str += (\n",
" \" \"\n",
" + str(route[0])\n",
" + \" Time(\"\n",
" + str(start_time)\n",
" + \", \"\n",
" + str(end_time)\n",
" + \")\"\n",
" )\n",
" for j in range(1, len(route)):\n",
" start_time = cumul_data[i][j][0]\n",
" end_time = cumul_data[i][j][1]\n",
" route_str += ' -> ' + str(route[j]) + \\\n",
" ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n",
" route_str += f'\\n Route time: {start_time}min\\n\\n'\n",
" route_str += (\n",
" \" -> \"\n",
" + str(route[j])\n",
" + \" Time(\"\n",
" + str(start_time)\n",
" + \", \"\n",
" + str(end_time)\n",
" + \")\"\n",
" )\n",
" route_str += f\"\\n Route time: {start_time}min\\n\\n\"\n",
" total_time += cumul_data[i][len(route) - 1][0]\n",
" route_str += f'Total time: {total_time}min'\n",
" route_str += f\"Total time: {total_time}min\"\n",
" print(route_str)\n",
"\n",
"\n",
@@ -200,20 +214,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def time_callback(from_index, to_index):\n",
" \"\"\"Returns the travel time between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to time matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['time_matrix'][from_node][to_node]\n",
" return data[\"time_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -221,38 +235,41 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 30, # allow waiting time\n",
" 30, # maximum time per vehicle\n",
" False, # Don't force cumulative time to be 0 at start of routes.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot.\n",
" for location_idx, time_window in enumerate(data['time_windows']):\n",
" for location_idx, time_window in enumerate(data[\"time_windows\"]):\n",
" if location_idx == 0:\n",
" continue\n",
" index = manager.NodeToIndex(location_idx)\n",
" time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n",
" # Add time window constraints for each vehicle start node.\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n",
" data['time_windows'][0][1])\n",
" time_dimension.CumulVar(index).SetRange(\n",
" data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n",
" )\n",
"\n",
" # Instantiate route start and end times to produce feasible times.\n",
" for i in range(data['num_vehicles']):\n",
" for i in range(data[\"num_vehicles\"]):\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.Start(i)))\n",
" routing.AddVariableMinimizedByFinalizer(\n",
" time_dimension.CumulVar(routing.End(i)))\n",
" time_dimension.CumulVar(routing.Start(i))\n",
" )\n",
" routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",

View File

@@ -92,12 +92,11 @@
"from ortools.linear_solver import pywraplp\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_LOAD_MIN = flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.')\n",
"_LOAD_MAX = flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.')\n",
"_COMMUTE_TIME = flags.DEFINE_integer('commute_time', 30,\n",
" 'Commute time in minutes.')\n",
"_NUM_WORKERS = flags.DEFINE_integer('num_workers', 98,\n",
" 'Maximum number of workers.')\n",
"\n",
"_LOAD_MIN = flags.DEFINE_integer(\"load_min\", 480, \"Minimum load in minutes.\")\n",
"_LOAD_MAX = flags.DEFINE_integer(\"load_max\", 540, \"Maximum load in minutes.\")\n",
"_COMMUTE_TIME = flags.DEFINE_integer(\"commute_time\", 30, \"Commute time in minutes.\")\n",
"_NUM_WORKERS = flags.DEFINE_integer(\"num_workers\", 98, \"Maximum number of workers.\")\n",
"\n",
"\n",
"class AllSolutionCollector(cp_model.CpSolverSolutionCallback):\n",
@@ -118,24 +117,21 @@
" return self.__collect\n",
"\n",
"\n",
"def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,\n",
" total_size_max):\n",
"def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max):\n",
" \"\"\"Enumerate all possible knapsacks with total size in the given range.\n",
"\n",
" Args:\n",
" item_sizes: a list of integers. item_sizes[i] is the size of item #i.\n",
" total_size_min: an integer, the minimum total size.\n",
" total_size_max: an integer, the maximum total size.\n",
" Args:\n",
" item_sizes: a list of integers. item_sizes[i] is the size of item #i.\n",
" total_size_min: an integer, the minimum total size.\n",
" total_size_max: an integer, the maximum total size.\n",
"\n",
" Returns:\n",
" The list of all the knapsacks whose total size is in the given inclusive\n",
" range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an\n",
" nonnegative integer: the number of times we put item #K in the knapsack.\n",
" \"\"\"\n",
" Returns:\n",
" The list of all the knapsacks whose total size is in the given inclusive\n",
" range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an\n",
" nonnegative integer: the number of times we put item #K in the knapsack.\n",
" \"\"\"\n",
" model = cp_model.CpModel()\n",
" variables = [\n",
" model.NewIntVar(0, total_size_max // size, '') for size in item_sizes\n",
" ]\n",
" variables = [model.NewIntVar(0, total_size_max // size, \"\") for size in item_sizes]\n",
" load = sum(variables[i] * size for i, size in enumerate(item_sizes))\n",
" model.AddLinearConstraint(load, total_size_min, total_size_max)\n",
"\n",
@@ -148,52 +144,52 @@
" return solution_collector.combinations()\n",
"\n",
"\n",
"def AggregateItemCollectionsOptimally(item_collections, max_num_collections,\n",
" ideal_item_ratios):\n",
"def AggregateItemCollectionsOptimally(\n",
" item_collections, max_num_collections, ideal_item_ratios\n",
"):\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",
" may appear multiple times), a given \"ideal breakdown of items\", and a\n",
" maximum number of collections, this method finds the optimal way to\n",
" aggregate the collections in order to:\n",
" - maximize the overall number of items\n",
" - while keeping the ratio of each item, among the overall selection, as close\n",
" as possible to a given input ratio (which depends on the item).\n",
" Each collection may be selected more than one time.\n",
" Given a set of collections of N possible items (in each collection, an item\n",
" may appear multiple times), a given \"ideal breakdown of items\", and a\n",
" maximum number of collections, this method finds the optimal way to\n",
" aggregate the collections in order to:\n",
" - maximize the overall number of items\n",
" - while keeping the ratio of each item, among the overall selection, as close\n",
" as possible to a given input ratio (which depends on the item).\n",
" Each collection may be selected more than one time.\n",
"\n",
" Args:\n",
" item_collections: a list of item collections. Each item collection is a list\n",
" of integers [#item0, ..., #itemN-1], where #itemK is the number of times\n",
" item #K appears in the collection, and N is the number of distinct items.\n",
" max_num_collections: an integer, the maximum number of item collections that\n",
" may be selected (counting repetitions of the same collection).\n",
" ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is\n",
" the ideal ratio of item #K in the whole aggregated selection.\n",
" Args:\n",
" item_collections: a list of item collections. Each item collection is a list\n",
" of integers [#item0, ..., #itemN-1], where #itemK is the number of times\n",
" item #K appears in the collection, and N is the number of distinct items.\n",
" max_num_collections: an integer, the maximum number of item collections that\n",
" may be selected (counting repetitions of the same collection).\n",
" ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is\n",
" the ideal ratio of item #K in the whole aggregated selection.\n",
"\n",
" Returns:\n",
" A pair (objective value, list of pairs (item collection, num_selections)),\n",
" where:\n",
" - \"objective value\" is the value of the internal objective function used\n",
" by the MIP Solver\n",
" - Each \"item collection\" is an element of the input item_collections\n",
" - and its associated \"num_selections\" is the number of times it was\n",
" selected.\n",
" \"\"\"\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" Returns:\n",
" A pair (objective value, list of pairs (item collection, num_selections)),\n",
" where:\n",
" - \"objective value\" is the value of the internal objective function used\n",
" by the MIP Solver\n",
" - Each \"item collection\" is an element of the input item_collections\n",
" - and its associated \"num_selections\" is the number of times it was\n",
" selected.\n",
" \"\"\"\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if not solver:\n",
" return []\n",
" n = len(ideal_item_ratios)\n",
" num_distinct_collections = len(item_collections)\n",
" max_num_items_per_collection = 0\n",
" for template in item_collections:\n",
" max_num_items_per_collection = max(max_num_items_per_collection,\n",
" sum(template))\n",
" max_num_items_per_collection = max(max_num_items_per_collection, sum(template))\n",
" upper_bound = max_num_items_per_collection * max_num_collections\n",
"\n",
" # num_selections_of_collection[i] is an IntVar that represents the number\n",
" # of times that we will use collection #i in our global selection.\n",
" num_selections_of_collection = [\n",
" solver.IntVar(0, max_num_collections, 's[%d]' % i)\n",
" solver.IntVar(0, max_num_collections, \"s[%d]\" % i)\n",
" for i in range(num_distinct_collections)\n",
" ]\n",
"\n",
@@ -201,19 +197,17 @@
" # aggregated over all selected collections. This is enforced with dedicated\n",
" # constraints that bind them with the num_selections_of_collection vars.\n",
" num_overall_item = [\n",
" solver.IntVar(0, upper_bound, 'num_overall_item[%d]' % i)\n",
" for i in range(n)\n",
" solver.IntVar(0, upper_bound, \"num_overall_item[%d]\" % i) for i in range(n)\n",
" ]\n",
" for i in range(n):\n",
" ct = solver.Constraint(0.0, 0.0)\n",
" ct.SetCoefficient(num_overall_item[i], -1)\n",
" for j in range(num_distinct_collections):\n",
" ct.SetCoefficient(num_selections_of_collection[j],\n",
" item_collections[j][i])\n",
" ct.SetCoefficient(num_selections_of_collection[j], item_collections[j][i])\n",
"\n",
" # Maintain the num_all_item variable as the sum of all num_overall_item\n",
" # variables.\n",
" num_all_items = solver.IntVar(0, upper_bound, 'num_all_items')\n",
" num_all_items = solver.IntVar(0, upper_bound, \"num_all_items\")\n",
" solver.Add(solver.Sum(num_overall_item) == num_all_items)\n",
"\n",
" # Sets the total number of workers.\n",
@@ -221,15 +215,16 @@
"\n",
" # Objective variables.\n",
" deviation_vars = [\n",
" solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i)\n",
" for i in range(n)\n",
" solver.NumVar(0, upper_bound, \"deviation_vars[%d]\" % i) for i in range(n)\n",
" ]\n",
" for i in range(n):\n",
" deviation = deviation_vars[i]\n",
" solver.Add(deviation >= num_overall_item[i] -\n",
" ideal_item_ratios[i] * num_all_items)\n",
" solver.Add(deviation >= ideal_item_ratios[i] * num_all_items -\n",
" num_overall_item[i])\n",
" solver.Add(\n",
" deviation >= num_overall_item[i] - ideal_item_ratios[i] * num_all_items\n",
" )\n",
" solver.Add(\n",
" deviation >= ideal_item_ratios[i] * num_all_items - num_overall_item[i]\n",
" )\n",
"\n",
" solver.Maximize(num_all_items - solver.Sum(deviation_vars))\n",
"\n",
@@ -244,43 +239,57 @@
"def GetOptimalSchedule(demand):\n",
" \"\"\"Computes the optimal schedule for the installation input.\n",
"\n",
" Args:\n",
" demand: a list of \"appointment types\". Each \"appointment type\" is a triple\n",
" (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the\n",
" ideal percentage (in [0..100.0]) of that type of appointment among all\n",
" appointments scheduled.\n",
" Args:\n",
" demand: a list of \"appointment types\". Each \"appointment type\" is a triple\n",
" (ideal_ratio_pct, name, duration_minutes), where ideal_ratio_pct is the\n",
" ideal percentage (in [0..100.0]) of that type of appointment among all\n",
" appointments scheduled.\n",
"\n",
" Returns:\n",
" The same output type as EnumerateAllKnapsacksWithRepetition.\n",
" \"\"\"\n",
" 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,\n",
" _LOAD_MAX.value)\n",
" print(('Found %d possible day schedules ' % len(combinations) +\n",
" '(i.e. combination of appointments filling up one worker\\'s day)'))\n",
" [a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value\n",
" )\n",
" print(\n",
" (\n",
" \"Found %d possible day schedules \" % len(combinations)\n",
" + \"(i.e. combination of appointments filling up one worker's day)\"\n",
" )\n",
" )\n",
"\n",
" selection = AggregateItemCollectionsOptimally(\n",
" combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand])\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",
" output.append((selection[i], [(combinations[i][t], demand[t][1])\n",
" for t in range(len(demand))\n",
" if combinations[i][t] != 0]))\n",
" output.append(\n",
" (\n",
" selection[i],\n",
" [\n",
" (combinations[i][t], demand[t][1])\n",
" for t in range(len(demand))\n",
" if combinations[i][t] != 0\n",
" ],\n",
" )\n",
" )\n",
"\n",
" return output\n",
"\n",
"\n",
"def main(_):\n",
" demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)]\n",
" print('*** input problem ***')\n",
" print('Appointments: ')\n",
" demand = [(45.0, \"Type1\", 90), (30.0, \"Type2\", 120), (25.0, \"Type3\", 180)]\n",
" print(\"*** input problem ***\")\n",
" print(\"Appointments: \")\n",
" for a in demand:\n",
" print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2]))\n",
" print('Commute time = %d' % _COMMUTE_TIME.value)\n",
" print('Acceptable duration of a work day = [%d..%d]' %\n",
" (_LOAD_MIN.value, _LOAD_MAX.value))\n",
" print('%d workers' % _NUM_WORKERS.value)\n",
" print(\" %.2f%% of %s : %d min\" % (a[0], a[1], a[2]))\n",
" print(\"Commute time = %d\" % _COMMUTE_TIME.value)\n",
" print(\n",
" \"Acceptable duration of a work day = [%d..%d]\"\n",
" % (_LOAD_MIN.value, _LOAD_MAX.value)\n",
" )\n",
" print(\"%d workers\" % _NUM_WORKERS.value)\n",
" selection = GetOptimalSchedule(demand)\n",
" print()\n",
" installed = 0\n",
@@ -288,27 +297,27 @@
" for a in demand:\n",
" installed_per_type[a[1]] = 0\n",
"\n",
" print('*** output solution ***')\n",
" print(\"*** output solution ***\")\n",
" for template in selection:\n",
" num_instances = template[0]\n",
" print('%d schedules with ' % num_instances)\n",
" print(\"%d schedules with \" % num_instances)\n",
" for t in template[1]:\n",
" mult = t[0]\n",
" print(' %d installation of type %s' % (mult, t[1]))\n",
" print(\" %d installation of type %s\" % (mult, t[1]))\n",
" installed += num_instances * mult\n",
" installed_per_type[t[1]] += num_instances * mult\n",
"\n",
" print()\n",
" print('%d installations planned' % installed)\n",
" print(\"%d installations planned\" % installed)\n",
" for a in demand:\n",
" name = a[1]\n",
" 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 type {name} planned\"\n",
" )\n",
" else:\n",
" print(f' {per_type} installations of type {name} planned')\n",
" print(f\" {per_type} installations of type {name} planned\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solve an assignment problem with combination constraints on workers.\n"
]
},
@@ -89,19 +90,27 @@
"def solve_assignment():\n",
" \"\"\"Solve the assignment problem.\"\"\"\n",
" # Data.\n",
" cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48, 101],\n",
" [125, 95, 90, 105, 59, 120], [45, 110, 95, 115, 104, 83],\n",
" [60, 105, 80, 75, 59, 62], [45, 65, 110, 95, 47, 31],\n",
" [38, 51, 107, 41, 69, 99], [47, 85, 57, 71, 92, 77],\n",
" [39, 63, 97, 49, 118, 56], [47, 101, 71, 60, 88, 109],\n",
" [17, 39, 103, 64, 61, 92], [101, 45, 83, 59, 92, 27]]\n",
" cost = [\n",
" [90, 76, 75, 70, 50, 74],\n",
" [35, 85, 55, 65, 48, 101],\n",
" [125, 95, 90, 105, 59, 120],\n",
" [45, 110, 95, 115, 104, 83],\n",
" [60, 105, 80, 75, 59, 62],\n",
" [45, 65, 110, 95, 47, 31],\n",
" [38, 51, 107, 41, 69, 99],\n",
" [47, 85, 57, 71, 92, 77],\n",
" [39, 63, 97, 49, 118, 56],\n",
" [47, 101, 71, 60, 88, 109],\n",
" [17, 39, 103, 64, 61, 92],\n",
" [101, 45, 83, 59, 92, 27],\n",
" ]\n",
"\n",
" group1 = [\n",
" [0, 0, 1, 1], # Workers 2, 3\n",
" [0, 1, 0, 1], # Workers 1, 3\n",
" [0, 1, 1, 0], # Workers 1, 2\n",
" [1, 1, 0, 0], # Workers 0, 1\n",
" [1, 0, 1, 0]\n",
" [1, 0, 1, 0],\n",
" ] # Workers 0, 2\n",
"\n",
" group2 = [\n",
@@ -109,7 +118,7 @@
" [0, 1, 0, 1], # Workers 5, 7\n",
" [0, 1, 1, 0], # Workers 5, 6\n",
" [1, 1, 0, 0], # Workers 4, 5\n",
" [1, 0, 0, 1]\n",
" [1, 0, 0, 1],\n",
" ] # Workers 4, 7\n",
"\n",
" group3 = [\n",
@@ -117,7 +126,7 @@
" [0, 1, 0, 1], # Workers 9, 11\n",
" [0, 1, 1, 0], # Workers 9, 10\n",
" [1, 0, 1, 0], # Workers 8, 10\n",
" [1, 0, 0, 1]\n",
" [1, 0, 0, 1],\n",
" ] # Workers 8, 11\n",
"\n",
" sizes = [10, 7, 3, 12, 15, 4, 11, 5]\n",
@@ -131,10 +140,10 @@
"\n",
" model = cp_model.CpModel()\n",
" # Variables\n",
" selected = [[model.NewBoolVar('x[%i,%i]' % (i, j))\n",
" for j in all_tasks]\n",
" for i in all_workers]\n",
" works = [model.NewBoolVar('works[%i]' % i) for i in all_workers]\n",
" selected = [\n",
" [model.NewBoolVar(\"x[%i,%i]\" % (i, j)) for j in all_tasks] for i in all_workers\n",
" ]\n",
" works = [model.NewBoolVar(\"works[%i]\" % i) for i in all_workers]\n",
"\n",
" # Constraints\n",
"\n",
@@ -148,47 +157,43 @@
"\n",
" # Total task size for each worker is at most total_size_max\n",
" for i in all_workers:\n",
" model.Add(\n",
" 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]],\n",
" group1)\n",
" model.AddAllowedAssignments([works[4], works[5], works[6], works[7]],\n",
" group2)\n",
" model.AddAllowedAssignments([works[8], works[9], works[10], works[11]],\n",
" group3)\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",
"\n",
" # Objective\n",
" model.Minimize(\n",
" sum(selected[i][j] * cost[i][j]\n",
" for j in all_tasks\n",
" for i in all_workers))\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",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print('Total cost = %i' % solver.ObjectiveValue())\n",
" print(\"Total cost = %i\" % solver.ObjectiveValue())\n",
" print()\n",
" for i in all_workers:\n",
" for j in all_tasks:\n",
" if solver.BooleanValue(selected[i][j]):\n",
" print('Worker ', i, ' assigned to task ', j, ' Cost = ',\n",
" cost[i][j])\n",
" print(\n",
" \"Worker \", i, \" assigned to task \", j, \" Cost = \", cost[i][j]\n",
" )\n",
"\n",
" 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(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" solve_assignment()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"We are trying to group items in equal sized groups.\n",
"\n",
"Each item has a color and a value. We want the sum of values of each group to\n",
@@ -105,10 +106,10 @@
" self.__item_in_group = item_in_group\n",
"\n",
" def on_solution_callback(self):\n",
" print('Solution %i' % self.__solution_count)\n",
" 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.ObjectiveValue())\n",
" groups = {}\n",
" sums = {}\n",
" for g in self.__all_groups:\n",
@@ -121,19 +122,19 @@
"\n",
" for g in self.__all_groups:\n",
" group = groups[g]\n",
" print('group %i: sum = %0.2f [' % (g, sums[g]), end='')\n",
" print(\"group %i: sum = %0.2f [\" % (g, sums[g]), end=\"\")\n",
" for item in group:\n",
" value = self.__values[item]\n",
" color = self.__colors[item]\n",
" print(' (%i, %i, %i)' % (item, value, color), end='')\n",
" print(']')\n",
" print(\" (%i, %i, %i)\" % (item, value, color), end=\"\")\n",
" print(\"]\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" \"\"\"Solves a group balancing problem.\"\"\"\n",
"\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" # Data.\n",
" num_groups = 10\n",
" num_items = 100\n",
@@ -162,9 +163,11 @@
" if colors[i] == c:\n",
" items_per_color[c].append(i)\n",
"\n",
" print('Model has %i items, %i groups, and %i colors' %\n",
" (num_items, num_groups, num_colors))\n",
" print(' average sum per group = %i' % average_sum_per_group)\n",
" print(\n",
" \"Model has %i items, %i groups, and %i colors\"\n",
" % (num_items, num_groups, num_colors)\n",
" )\n",
" print(\" average sum per group = %i\" % average_sum_per_group)\n",
"\n",
" # Model.\n",
"\n",
@@ -173,43 +176,42 @@
" 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' %\n",
" (i, g))\n",
" item_in_group[(i, g)] = model.NewBoolVar(\"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(\n",
" sum(item_in_group[(i, g)]\n",
" 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",
"\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.NewIntVar(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",
" sum(item_in_group[(i, g)] * values[i]\n",
" for i in all_items) <= average_sum_per_group + e)\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",
" sum(item_in_group[(i, g)] * values[i]\n",
" for i in all_items) >= average_sum_per_group - e)\n",
" sum(item_in_group[(i, g)] * values[i] for i in all_items)\n",
" >= average_sum_per_group - e\n",
" )\n",
"\n",
" # color_in_group variables.\n",
" 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 %d is in group %d' % (c, g))\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)],\n",
" color_in_group[(colors[i], g)])\n",
" model.AddImplication(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",
@@ -217,8 +219,9 @@
" for g in all_groups:\n",
" literal = color_in_group[(c, g)]\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).OnlyEnforceIf(literal)\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",
"\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",
@@ -226,8 +229,7 @@
" # Redundant constraint, it helps with solving time.\n",
" if max_color < num_colors:\n",
" for g in all_groups:\n",
" model.Add(\n",
" 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",
@@ -235,18 +237,19 @@
" solver = cp_model.CpSolver()\n",
" # solver.parameters.log_search_progress = True\n",
" solver.parameters.num_workers = 16\n",
" solution_printer = SolutionPrinter(values, colors, all_groups, all_items,\n",
" item_in_group)\n",
" solution_printer = SolutionPrinter(\n",
" values, colors, all_groups, all_items, item_in_group\n",
" )\n",
" status = solver.Solve(model, solution_printer)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print('Optimal epsilon: %i' % solver.ObjectiveValue())\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(\"Optimal epsilon: %i\" % solver.ObjectiveValue())\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
" else:\n",
" print('No solution found')\n",
" print(\"No solution found\")\n",
"\n",
"\n",
"main()\n",

File diff suppressed because it is too large Load Diff

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"We are trying to group items in equal sized groups.\n",
"\n",
"Each item has a color and a value. We want the sum of values of each group to be\n",
@@ -127,9 +128,13 @@
" max_set = [\n",
" int(\n",
" math.ceil(\n",
" min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n",
" min(\n",
" max_quantities[q][1] * 1000 / chemical_set[s][q + 1]\n",
" for q in all_products\n",
" if chemical_set[s][q + 1] != 0)))\n",
" if chemical_set[s][q + 1] != 0\n",
" )\n",
" )\n",
" )\n",
" for s in all_sets\n",
" ]\n",
"\n",
@@ -139,14 +144,13 @@
"\n",
" for p in all_products:\n",
" model.Add(\n",
" sum(\n",
" int(chemical_set[s][p + 1] * 10) * set_vars[s]\n",
" for s in all_sets) <= int(max_quantities[p][1] * 10000))\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",
" sum(\n",
" int(chemical_set[s][p + 1] * 10) * set_vars[s]\n",
" for s in all_sets) >= int(max_quantities[p][1] * 10000) -\n",
" epsilon)\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",
"\n",
@@ -158,15 +162,15 @@
" print(f\"Optimal objective value = {solver.ObjectiveValue() / 10000.0}\")\n",
"\n",
" for s in all_sets:\n",
" print(f\" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}\",\n",
" end=\" \")\n",
" print(f\" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}\", end=\" \")\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",
" for s in all_sets)\n",
" for s in all_sets\n",
" )\n",
" print(f\"{name}: {quantity} out of {max_quantity}\")\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Cluster 40 cities in 4 equal groups to minimize sum of crossed distances.\n"
]
},
@@ -87,6 +88,7 @@
"\n",
"\n",
"distance_matrix = [\n",
" # fmt:off\n",
" [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n",
" [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n",
" [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n",
@@ -127,13 +129,14 @@
" [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n",
" [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n",
" [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n",
"] # yapf: disable\n",
" # fmt:on\n",
"]\n",
"\n",
"\n",
"def clustering_sat():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" num_nodes = len(distance_matrix)\n",
" print('Num nodes =', num_nodes)\n",
" print(\"Num nodes =\", num_nodes)\n",
"\n",
" # Number of groups to split the nodes, must divide num_nodes.\n",
" num_groups = 4\n",
@@ -148,7 +151,7 @@
" 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.NewBoolVar(\"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",
@@ -156,23 +159,24 @@
" # Number of neighborss:\n",
" for n in range(num_nodes):\n",
" model.Add(\n",
" sum(neighbors[m, n] for m in range(n)) +\n",
" sum(neighbors[n, m]\n",
" for m in range(n + 1, num_nodes)) == group_size - 1)\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",
" )\n",
"\n",
" # Enforce transivity on all triplets.\n",
" 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(neighbors[n1, n3] + neighbors[n2, n3] +\n",
" neighbors[n1, n2] != 2)\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",
"\n",
" # Minimize weighted sum of arcs.\n",
" model.Minimize(\n",
" 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",
@@ -192,14 +196,14 @@
" for o in range(n + 1, num_nodes):\n",
" if solver.BooleanValue(neighbors[n, o]):\n",
" visited.add(o)\n",
" output += ' ' + str(o)\n",
" print('Group', g, ':', output)\n",
" output += \" \" + str(o)\n",
" print(\"Group\", g, \":\", output)\n",
" break\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" clustering_sat()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Fill a 60x50 rectangle by a minimum number of non-overlapping squares.\n"
]
},
@@ -102,16 +103,16 @@
"\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.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",
"\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.NewIntervalVar(start_x, size, end_x, \"ix_%i\" % i)\n",
" interval_y = model.NewIntervalVar(start_y, size, end_y, \"iy_%i\" % i)\n",
"\n",
" area = model.NewIntVar(1, size_y * size_y, 'area_%i' % i)\n",
" area = model.NewIntVar(1, size_y * size_y, \"area_%i\" % i)\n",
" model.AddMultiplicationEquality(area, [size, size])\n",
"\n",
" areas.append(area)\n",
@@ -136,7 +137,7 @@
" 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",
" 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",
"\n",
@@ -149,35 +150,39 @@
"\n",
" # Creates a solver and solves.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_workers = 8\n",
" solver.parameters.num_workers = 16\n",
" # solver.parameters.log_search_progress = True\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",
" print(\"%s found in %0.2fs\" % (solver.StatusName(status), solver.WallTime()))\n",
"\n",
" # Prints solution.\n",
" if status == cp_model.OPTIMAL:\n",
" display = [[' ' for _ in range(size_x)] for _ in range(size_y)]\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\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",
" char = format(i, '01x')\n",
" char = format(i, \"01x\")\n",
" for j in range(sol_s):\n",
" for k in range(sol_s):\n",
" if display[sol_y + j][sol_x + k] != ' ':\n",
" print('ERROR between %s and %s' %\n",
" (display[sol_y + j][sol_x + k], char))\n",
" if display[sol_y + j][sol_x + k] != \" \":\n",
" print(\n",
" \"ERROR between %s and %s\"\n",
" % (display[sol_y + j][sol_x + k], char)\n",
" )\n",
" display[sol_y + j][sol_x + k] = char\n",
"\n",
" for line in range(size_y):\n",
" print(' '.join(display[line]))\n",
" print(\" \".join(display[line]))\n",
" return status == cp_model.OPTIMAL\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" for num_squares in range(1, 15):\n",
" print('Trying with size =', num_squares)\n",
" print(\"Trying with size =\", num_squares)\n",
" if cover_rectangle(num_squares):\n",
" break\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.\n",
"\n"
]
@@ -87,28 +88,27 @@
"\n",
"\n",
"def send_more_money():\n",
" \"\"\"Solve the cryptarithmic puzzle SEND+MORE=MONEY.\n",
" \"\"\"\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.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",
" # 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.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",
"\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.NewBoolVar(\"c0\")\n",
" c1 = model.NewBoolVar(\"c1\")\n",
" c2 = model.NewBoolVar(\"c2\")\n",
" c3 = model.NewBoolVar(\"c3\")\n",
"\n",
" # Force all letters to take on different values.\n",
" model.AddAllDifferent(s, e, n, d, m, o, r, y)\n",
@@ -131,15 +131,15 @@
" # Solve model.\n",
" solver = cp_model.CpSolver()\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(\"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",
"\n",
"\n",
"def main(_):\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves a flexible jobshop problems with the CP-SAT solver.\n",
"\n",
"A jobshop is a standard scheduling problem when you must sequence a\n",
@@ -106,8 +107,10 @@
"\n",
" def on_solution_callback(self):\n",
" \"\"\"Called at each new solution.\"\"\"\n",
" print('Solution %i, time = %f s, objective = %i' %\n",
" (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
@@ -149,7 +152,7 @@
" max_task_duration = max(max_task_duration, alternative[0])\n",
" horizon += max_task_duration\n",
"\n",
" print('Horizon = %i' % horizon)\n",
" print(\"Horizon = %i\" % horizon)\n",
"\n",
" # Global storage of variables.\n",
" intervals_per_resources = collections.defaultdict(list)\n",
@@ -177,13 +180,15 @@
" max_duration = max(max_duration, alt_duration)\n",
"\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(min_duration, max_duration,\n",
" 'duration' + suffix_name)\n",
" end = model.NewIntVar(0, horizon, 'end' + suffix_name)\n",
" interval = model.NewIntervalVar(start, duration, end,\n",
" 'interval' + suffix_name)\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",
" min_duration, max_duration, \"duration\" + suffix_name\n",
" )\n",
" end = model.NewIntVar(0, horizon, \"end\" + suffix_name)\n",
" interval = model.NewIntervalVar(\n",
" start, duration, end, \"interval\" + suffix_name\n",
" )\n",
"\n",
" # Store the start for the solution.\n",
" starts[(job_id, task_id)] = start\n",
@@ -197,14 +202,14 @@
" if num_alternatives > 1:\n",
" 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",
" 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_duration = task[alt_id][0]\n",
" l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)\n",
" l_end = model.NewIntVar(0, horizon, \"end\" + alt_suffix)\n",
" l_interval = model.NewOptionalIntervalVar(\n",
" l_start, l_duration, l_end, l_presence,\n",
" 'interval' + alt_suffix)\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",
@@ -233,7 +238,7 @@
" model.AddNoOverlap(intervals)\n",
"\n",
" # Makespan objective\n",
" makespan = model.NewIntVar(0, horizon, 'makespan')\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, job_ends)\n",
" model.Minimize(makespan)\n",
"\n",
@@ -244,7 +249,7 @@
"\n",
" # Print final solution.\n",
" for job_id in all_jobs:\n",
" print('Job %i:' % job_id)\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",
" machine = -1\n",
@@ -256,15 +261,16 @@
" machine = jobs[job_id][task_id][alt_id][1]\n",
" selected = alt_id\n",
" print(\n",
" ' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' %\n",
" (job_id, task_id, start_value, selected, machine, duration))\n",
" \" task_%i_%i starts at %i (alt %i, machine %i, duration %i)\"\n",
" % (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('Statistics')\n",
" print(' - conflicts : %i' % solver.NumConflicts())\n",
" print(' - branches : %i' % solver.NumBranches())\n",
" print(' - wall time : %f s' % solver.WallTime())\n",
" print(\"Solve status: %s\" % solver.StatusName(status))\n",
" print(\"Optimal objective value: %i\" % solver.ObjectiveValue())\n",
" print(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
"\n",
"\n",
"flexible_jobshop()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Gate Scheduling problem.\n",
"\n",
"We have a set of jobs to perform (duration, width).\n",
@@ -114,7 +115,7 @@
" [1, 2],\n",
" [6, 8],\n",
" [4, 5],\n",
" [3, 7]\n",
" [3, 7],\n",
" ]\n",
"\n",
" max_width = 10\n",
@@ -133,31 +134,31 @@
"\n",
" for i in all_jobs:\n",
" # Create main interval.\n",
" start = model.NewIntVar(0, horizon, 'start_%i' % i)\n",
" start = model.NewIntVar(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.NewIntVar(0, horizon, \"end_%i\" % i)\n",
" interval = model.NewIntervalVar(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.NewBoolVar(\"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(start0, duration, end0,\n",
" performed_on_m0,\n",
" 'interval_%i_on_m0' % i)\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, 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(start1, duration, end1,\n",
" performed_on_m0.Not(),\n",
" 'interval_%i_on_m1' % i)\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",
" )\n",
" intervals1.append(interval1)\n",
"\n",
" # We only propagate the constraint if the tasks is performed on the machine.\n",
@@ -172,7 +173,7 @@
" model.AddNoOverlap(intervals1)\n",
"\n",
" # Objective variable.\n",
" makespan = model.NewIntVar(0, horizon, 'makespan')\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, ends)\n",
" model.Minimize(makespan)\n",
"\n",
@@ -185,9 +186,8 @@
"\n",
" # Output solution.\n",
" if visualization.RunFromIPython():\n",
" output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width,\n",
" 40.0)\n",
" output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n",
" output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)\n",
" output.AddTitle(\"Makespan = %i\" % solver.ObjectiveValue())\n",
" color_manager = visualization.ColorManager()\n",
" color_manager.SeedRandomColor(0)\n",
"\n",
@@ -197,24 +197,26 @@
" d_x = jobs[i][0]\n",
" d_y = jobs[i][1]\n",
" s_y = performed_machine * (max_width - d_y)\n",
" output.AddRectangle(start, s_y, d_x, d_y,\n",
" color_manager.RandomColor(), 'black', 'j%i' % i)\n",
" output.AddRectangle(\n",
" start, s_y, d_x, d_y, color_manager.RandomColor(), \"black\", \"j%i\" % i\n",
" )\n",
"\n",
" output.AddXScale()\n",
" output.AddYScale()\n",
" output.Display()\n",
" else:\n",
" print('Solution')\n",
" print(' - makespan = %i' % solver.ObjectiveValue())\n",
" print(\"Solution\")\n",
" print(\" - makespan = %i\" % solver.ObjectiveValue())\n",
" for i in all_jobs:\n",
" performed_machine = 1 - solver.Value(performed[i])\n",
" start = solver.Value(starts[i])\n",
" print(' - Job %i starts at %i on machine %i' %\n",
" (i, start, performed_machine))\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(\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",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This is the Golomb ruler problem.\n",
"\n",
"This model aims at maximizing radar interferences in a minimum space.\n",
@@ -96,13 +97,13 @@
"\n",
"def main(_):\n",
" # Create the solver.\n",
" solver = pywrapcp.Solver('golomb ruler')\n",
" solver = pywrapcp.Solver(\"golomb ruler\")\n",
"\n",
" size = 8\n",
" var_max = size * size\n",
" all_vars = list(range(0, size))\n",
"\n",
" marks = [solver.IntVar(0, var_max, 'marks_%d' % i) for i in all_vars]\n",
" marks = [solver.IntVar(0, var_max, \"marks_%d\" % i) for i in all_vars]\n",
"\n",
" objective = solver.Minimize(marks[size - 1], 1)\n",
"\n",
@@ -124,20 +125,27 @@
" collector = solver.AllSolutionCollector(solution)\n",
"\n",
" solver.Solve(\n",
" solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND,\n",
" solver.ASSIGN_MIN_VALUE), [objective, collector])\n",
" solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),\n",
" [objective, collector],\n",
" )\n",
" for i in range(0, collector.SolutionCount()):\n",
" obj_value = collector.Value(i, marks[size - 1])\n",
" time = collector.WallTime(i)\n",
" branches = collector.Branches(i)\n",
" failures = collector.Failures(i)\n",
" print(('Solution #%i: value = %i, failures = %i, branches = %i,'\n",
" 'time = %i ms') % (i, obj_value, failures, branches, time))\n",
" print(\n",
" (\"Solution #%i: value = %i, failures = %i, branches = %i,\" \"time = %i ms\")\n",
" % (i, obj_value, failures, branches, time)\n",
" )\n",
" time = solver.WallTime()\n",
" branches = solver.Branches()\n",
" failures = solver.Failures()\n",
" print(('Total run : failures = %i, branches = %i, time = %i ms' %\n",
" (failures, branches, time)))\n",
" print(\n",
" (\n",
" \"Total run : failures = %i, branches = %i, time = %i ms\"\n",
" % (failures, branches, time)\n",
" )\n",
" )\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This is the Golomb ruler problem.\n",
"\n",
"This model aims at maximizing radar interferences in a minimum space.\n",
@@ -95,11 +96,12 @@
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_ORDER = flags.DEFINE_integer('order', 8, 'Order of the ruler.')\n",
"_ORDER = flags.DEFINE_integer(\"order\", 8, \"Order of the ruler.\")\n",
"_PARAMS = flags.DEFINE_string(\n",
" 'params',\n",
" 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n",
" 'Sat solver parameters.')\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"\n",
"\n",
"def solve_golomb_ruler(order, params):\n",
@@ -110,7 +112,7 @@
" 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.NewIntVar(0, var_max, f\"marks_{i}\") for i in all_vars]\n",
"\n",
" model.Add(marks[0] == 0)\n",
" for i in range(order - 2):\n",
@@ -119,7 +121,7 @@
" 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",
" diff = model.NewIntVar(0, var_max, f\"diff [{j},{i}]\")\n",
" model.Add(diff == marks[j] - marks[i])\n",
" diffs.append(diff)\n",
" model.AddAllDifferent(diffs)\n",
@@ -136,27 +138,27 @@
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
" print(f'Golomb ruler(order={order})')\n",
" print(f\"Golomb ruler(order={order})\")\n",
" status = solver.Solve(model, solution_printer)\n",
"\n",
" # Print solution.\n",
" print(f'status: {solver.StatusName(status)}')\n",
" print(f\"status: {solver.StatusName(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",
" 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",
" 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(\"Statistics:\")\n",
" print(f\"- conflicts: {solver.NumConflicts()}\")\n",
" print(f\"- branches : {solver.NumBranches()}\")\n",
" print(f\"- wall time: {solver.WallTime()}s\\n\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" solve_golomb_ruler(_ORDER.value, _PARAMS.value)\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves the Hidato problem with the CP-SAT solver.\n"
]
},
@@ -89,25 +90,29 @@
"def build_pairs(rows, cols):\n",
" \"\"\"Build closeness pairs for consecutive numbers.\n",
"\n",
" Build set of allowed pairs such that two consecutive numbers touch\n",
" each other in the grid.\n",
" Build set of allowed pairs such that two consecutive numbers touch\n",
" each other in the grid.\n",
"\n",
" Returns:\n",
" A list of pairs for allowed consecutive position of numbers.\n",
" Returns:\n",
" A list of pairs for allowed consecutive position of numbers.\n",
"\n",
" Args:\n",
" rows: the number of rows in the grid\n",
" cols: the number of columns in the grid\n",
" \"\"\"\n",
" Args:\n",
" rows: the number of rows in the grid\n",
" cols: the number of columns in the grid\n",
" \"\"\"\n",
" result = []\n",
" for x in range(rows):\n",
" for y in range(cols):\n",
" for dx in (-1, 0, 1):\n",
" for dy in (-1, 0, 1):\n",
" if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and\n",
" y + dy < cols and (dx != 0 or dy != 0)):\n",
" result.append(\n",
" (x * cols + y, (x + dx) * cols + (y + dy)))\n",
" if (\n",
" x + dx >= 0\n",
" and x + dx < rows\n",
" and y + dy >= 0\n",
" and y + dy < cols\n",
" and (dx != 0 or dy != 0)\n",
" ):\n",
" result.append((x * cols + y, (x + dx) * cols + (y + dy)))\n",
" return result\n",
"\n",
"\n",
@@ -122,7 +127,7 @@
" position = positions[k]\n",
" board[position // cols][position % cols] = k + 1\n",
" # Print the board.\n",
" print('Solution')\n",
" print(\"Solution\")\n",
" print_matrix(board)\n",
"\n",
"\n",
@@ -131,12 +136,12 @@
" rows = len(game)\n",
" cols = len(game[0])\n",
" for i in range(rows):\n",
" line = ''\n",
" line = \"\"\n",
" for j in range(cols):\n",
" if game[i][j] == 0:\n",
" line += ' .'\n",
" line += \" .\"\n",
" else:\n",
" line += '% 3s' % game[i][j]\n",
" line += \"% 3s\" % game[i][j]\n",
" print(line)\n",
"\n",
"\n",
@@ -152,34 +157,60 @@
" puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]\n",
"\n",
" elif problem == 2:\n",
" puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],\n",
" [0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],\n",
" [49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7, 0],\n",
" [0, 0, 0, 14, 0, 0, 0]]\n",
" puzzle = [\n",
" [0, 44, 41, 0, 0, 0, 0],\n",
" [0, 43, 0, 28, 29, 0, 0],\n",
" [0, 1, 0, 0, 0, 33, 0],\n",
" [0, 2, 25, 4, 34, 0, 36],\n",
" [49, 16, 0, 23, 0, 0, 0],\n",
" [0, 19, 0, 0, 12, 7, 0],\n",
" [0, 0, 0, 14, 0, 0, 0],\n",
" ]\n",
"\n",
" elif problem == 3:\n",
" # Problems from the book:\n",
" # Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n",
" # Problem 1 (Practice)\n",
" puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0],\n",
" [23, 0, 1, 14, 11], [0, 25, 0, 0, 12]]\n",
" puzzle = [\n",
" [0, 0, 20, 0, 0],\n",
" [0, 0, 0, 16, 18],\n",
" [22, 0, 15, 0, 0],\n",
" [23, 0, 1, 14, 11],\n",
" [0, 25, 0, 0, 12],\n",
" ]\n",
"\n",
" elif problem == 4:\n",
" # problem 2 (Practice)\n",
" puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5],\n",
" [0, 0, 7, 0, 0], [9, 8, 25, 1, 0]]\n",
" puzzle = [\n",
" [0, 0, 0, 0, 14],\n",
" [0, 18, 12, 0, 0],\n",
" [0, 0, 17, 4, 5],\n",
" [0, 0, 7, 0, 0],\n",
" [9, 8, 25, 1, 0],\n",
" ]\n",
"\n",
" elif problem == 5:\n",
" # problem 3 (Beginner)\n",
" puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19],\n",
" [31, 23, 0, 0, 14, 0], [0, 33, 8, 0, 15, 1],\n",
" [0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]\n",
" puzzle = [\n",
" [0, 26, 0, 0, 0, 18],\n",
" [0, 0, 27, 0, 0, 19],\n",
" [31, 23, 0, 0, 14, 0],\n",
" [0, 33, 8, 0, 15, 1],\n",
" [0, 0, 0, 5, 0, 0],\n",
" [35, 36, 0, 10, 0, 0],\n",
" ]\n",
" elif problem == 6:\n",
" # Problem 15 (Intermediate)\n",
" puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],\n",
" [0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0, 50],\n",
" [0, 0, 0, 0, 22, 0, 48, 40], [9, 0, 0, 32, 23, 0, 0, 41],\n",
" [27, 0, 0, 0, 36, 0, 46, 0], [28, 30, 0, 35, 0, 0, 0, 0]]\n",
" puzzle = [\n",
" [64, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 63, 0, 59, 15, 57, 53, 0],\n",
" [0, 4, 0, 14, 0, 0, 0, 0],\n",
" [3, 0, 11, 0, 20, 19, 0, 50],\n",
" [0, 0, 0, 0, 22, 0, 48, 40],\n",
" [9, 0, 0, 32, 23, 0, 0, 41],\n",
" [27, 0, 0, 0, 36, 0, 46, 0],\n",
" [28, 30, 0, 35, 0, 0, 0, 0],\n",
" ]\n",
" return puzzle\n",
"\n",
"\n",
@@ -191,18 +222,16 @@
" r = len(puzzle)\n",
" c = len(puzzle[0])\n",
" if not visualization.RunFromIPython():\n",
" print('')\n",
" print('----- Solving problem %i -----' % index)\n",
" print('')\n",
" print(('Initial game (%i x %i)' % (r, c)))\n",
" print(\"\")\n",
" print(\"----- Solving problem %i -----\" % index)\n",
" print(\"\")\n",
" print((\"Initial game (%i x %i)\" % (r, c)))\n",
" print_matrix(puzzle)\n",
"\n",
" #\n",
" # declare variables\n",
" #\n",
" positions = [\n",
" model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c)\n",
" ]\n",
" positions = [model.NewIntVar(0, r * c - 1, \"p[%i]\" % i) for i in range(r * c)]\n",
"\n",
" #\n",
" # constraints\n",
@@ -221,8 +250,7 @@
" # 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]],\n",
" close_tuples)\n",
" model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)\n",
"\n",
" #\n",
" # solution and search\n",
@@ -238,12 +266,10 @@
" 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',\n",
" str(i + 1))\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' %\n",
" (index, solver.WallTime()))\n",
" output.AddTitle(\"Puzzle %i solved in %f s\" % (index, solver.WallTime()))\n",
" output.Display()\n",
" else:\n",
" print_solution(\n",
@@ -251,10 +277,10 @@
" 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(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
"\n",
"\n",
"def main(_):\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Integer programming examples that show how to use the APIs.\n"
]
},
@@ -83,12 +84,12 @@
"outputs": [],
"source": [
"from ortools.linear_solver import pywraplp\n",
"from ortools.init import pywrapinit\n",
"\n",
"\n",
"def Announce(solver, api_type):\n",
" print('---- Integer programming example with ' + solver + ' (' + api_type +\n",
" ') -----')\n",
" print(\n",
" \"---- Integer programming example with \" + solver + \" (\" + api_type + \") -----\"\n",
" )\n",
"\n",
"\n",
"def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type):\n",
@@ -98,12 +99,12 @@
" if not solver:\n",
" return\n",
"\n",
" Announce(optimization_problem_type, 'natural language API')\n",
" Announce(optimization_problem_type, \"natural language API\")\n",
"\n",
" infinity = solver.infinity()\n",
" # x1 and x2 are integer non-negative variables.\n",
" x1 = solver.IntVar(0.0, infinity, 'x1')\n",
" x2 = solver.IntVar(0.0, infinity, 'x2')\n",
" x1 = solver.IntVar(0.0, infinity, \"x1\")\n",
" x2 = solver.IntVar(0.0, infinity, \"x2\")\n",
"\n",
" solver.Minimize(x1 + 2 * x2)\n",
" solver.Add(3 * x1 + 2 * x2 >= 17)\n",
@@ -117,12 +118,12 @@
" if not solver:\n",
" return\n",
"\n",
" Announce(optimization_problem_type, 'C++ style API')\n",
" Announce(optimization_problem_type, \"C++ style API\")\n",
"\n",
" infinity = solver.infinity()\n",
" # x1 and x2 are integer non-negative variables.\n",
" x1 = solver.IntVar(0.0, infinity, 'x1')\n",
" x2 = solver.IntVar(0.0, infinity, 'x2')\n",
" x1 = solver.IntVar(0.0, infinity, \"x1\")\n",
" x2 = solver.IntVar(0.0, infinity, \"x2\")\n",
"\n",
" # Minimize x1 + 2 * x2.\n",
" objective = solver.Objective()\n",
@@ -139,8 +140,8 @@
"\n",
"def SolveAndPrint(solver, variable_list):\n",
" \"\"\"Solve the problem and print the solution.\"\"\"\n",
" print('Number of variables = %d' % solver.NumVariables())\n",
" print('Number of constraints = %d' % solver.NumConstraints())\n",
" print(\"Number of variables = %d\" % solver.NumVariables())\n",
" print(\"Number of constraints = %d\" % solver.NumConstraints())\n",
"\n",
" result_status = solver.Solve()\n",
"\n",
@@ -151,33 +152,33 @@
" # GLOP_LINEAR_PROGRAMMING, verifying the solution is highly recommended!).\n",
" assert solver.VerifySolution(1e-7, True)\n",
"\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
"\n",
" # The objective value of the solution.\n",
" print('Optimal objective value = %f' % solver.Objective().Value())\n",
" print(\"Optimal objective value = %f\" % solver.Objective().Value())\n",
"\n",
" # The value of each variable in the solution.\n",
" for variable in variable_list:\n",
" print('%s = %f' % (variable.name(), variable.solution_value()))\n",
" print(\"%s = %f\" % (variable.name(), variable.solution_value()))\n",
"\n",
" print('Advanced usage:')\n",
" print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n",
" print(\"Advanced usage:\")\n",
" print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n",
"\n",
"\n",
"def RunAllIntegerExampleNaturalLanguageAPI():\n",
" RunIntegerExampleNaturalLanguageAPI('GLPK')\n",
" RunIntegerExampleNaturalLanguageAPI('CBC')\n",
" RunIntegerExampleNaturalLanguageAPI('SCIP')\n",
" RunIntegerExampleNaturalLanguageAPI('SAT')\n",
" RunIntegerExampleNaturalLanguageAPI('Gurobi')\n",
" RunIntegerExampleNaturalLanguageAPI(\"GLPK\")\n",
" # Disabling due to ASAN errors with CBC.\n",
" # RunIntegerExampleNaturalLanguageAPI('CBC')\n",
" RunIntegerExampleNaturalLanguageAPI(\"SCIP\")\n",
" RunIntegerExampleNaturalLanguageAPI(\"SAT\")\n",
"\n",
"\n",
"def RunAllIntegerExampleCppStyleAPI():\n",
" RunIntegerExampleCppStyleAPI('GLPK')\n",
" RunIntegerExampleCppStyleAPI('CBC')\n",
" RunIntegerExampleCppStyleAPI('SCIP')\n",
" RunIntegerExampleCppStyleAPI('SAT')\n",
" RunIntegerExampleCppStyleAPI('Gurobi')\n",
" RunIntegerExampleCppStyleAPI(\"GLPK\")\n",
" # Disabling due to ASAN errors with CBC.\n",
" # RunIntegerExampleCppStyleAPI('CBC')\n",
" RunIntegerExampleCppStyleAPI(\"SCIP\")\n",
" RunIntegerExampleCppStyleAPI(\"SAT\")\n",
"\n",
"\n",
"def main():\n",
@@ -185,11 +186,6 @@
" RunAllIntegerExampleCppStyleAPI()\n",
"\n",
"\n",
"pywrapinit.CppBridge.InitLogging('integer_programming.py')\n",
"cpp_flags = pywrapinit.CppFlags()\n",
"cpp_flags.stderrthreshold = 0\n",
"cpp_flags.log_prefix = False\n",
"pywrapinit.CppBridge.SetFlags(cpp_flags)\n",
"main()\n",
"\n"
]

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This model implements a variation of the ft06 jobshop.\n",
"\n",
"A jobshop is a standard scheduling problem when you must sequence a\n",
@@ -114,29 +115,42 @@
" all_machines = range(0, machines_count)\n",
" all_jobs = range(0, jobs_count)\n",
"\n",
" durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n",
" [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n",
" durations = [\n",
" [1, 3, 6, 7, 3, 6],\n",
" [8, 5, 10, 10, 10, 4],\n",
" [5, 4, 8, 9, 1, 7],\n",
" [5, 5, 5, 3, 8, 9],\n",
" [9, 3, 5, 4, 3, 1],\n",
" [3, 3, 9, 10, 4, 1],\n",
" ]\n",
"\n",
" machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n",
" [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n",
" machines = [\n",
" [2, 0, 1, 3, 5, 4],\n",
" [1, 2, 4, 5, 0, 3],\n",
" [2, 3, 5, 0, 1, 4],\n",
" [1, 0, 2, 3, 4, 5],\n",
" [2, 1, 4, 5, 0, 3],\n",
" [1, 3, 5, 0, 4, 2],\n",
" ]\n",
"\n",
" # Computes horizon statically.\n",
" horizon = 150\n",
"\n",
" task_type = collections.namedtuple('task_type', 'start end interval')\n",
" task_type = collections.namedtuple(\"task_type\", \"start end interval\")\n",
"\n",
" # Creates jobs.\n",
" 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.NewIntVar(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(start_var, duration, end_var,\n",
" 'interval_%i_%i' % (i, j))\n",
" all_tasks[(i, j)] = task_type(start=start_var,\n",
" end=end_var,\n",
" interval=interval_var)\n",
" end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.NewIntervalVar(\n",
" start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n",
" )\n",
" all_tasks[(i, j)] = task_type(\n",
" start=start_var, end=end_var, interval=interval_var\n",
" )\n",
"\n",
" # Create disjuctive constraints.\n",
" for i in all_machines:\n",
@@ -156,23 +170,24 @@
" 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",
" arcs.append([0, j1 + 1, start_lit])\n",
" start_lit = model.NewBoolVar(\"%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.NewBoolVar(\"%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",
" arcs.append([j1 + 1, j2 + 1, lit])\n",
" lit = model.NewBoolVar(\"%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] +\n",
" min_distance).OnlyEnforceIf(lit)\n",
" model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf(\n",
" lit\n",
" )\n",
"\n",
" model.AddCircuit(arcs)\n",
"\n",
@@ -182,9 +197,10 @@
" 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",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n",
" )\n",
" model.Minimize(obj_var)\n",
"\n",
" # Solve model.\n",
@@ -193,7 +209,7 @@
"\n",
" # Output solution.\n",
" if status == cp_model.OPTIMAL:\n",
" print('Optimal makespan: %i' % solver.ObjectiveValue())\n",
" print(\"Optimal makespan: %i\" % solver.ObjectiveValue())\n",
"\n",
"\n",
"jobshop_ft06_distance()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This model implements a simple jobshop named ft06.\n",
"\n",
"A jobshop is a standard scheduling problem when you must sequence a\n",
@@ -107,29 +108,42 @@
" all_machines = range(0, machines_count)\n",
" all_jobs = range(0, jobs_count)\n",
"\n",
" durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],\n",
" [5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]\n",
" durations = [\n",
" [1, 3, 6, 7, 3, 6],\n",
" [8, 5, 10, 10, 10, 4],\n",
" [5, 4, 8, 9, 1, 7],\n",
" [5, 5, 5, 3, 8, 9],\n",
" [9, 3, 5, 4, 3, 1],\n",
" [3, 3, 9, 10, 4, 1],\n",
" ]\n",
"\n",
" machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],\n",
" [1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]\n",
" machines = [\n",
" [2, 0, 1, 3, 5, 4],\n",
" [1, 2, 4, 5, 0, 3],\n",
" [2, 3, 5, 0, 1, 4],\n",
" [1, 0, 2, 3, 4, 5],\n",
" [2, 1, 4, 5, 0, 3],\n",
" [1, 3, 5, 0, 4, 2],\n",
" ]\n",
"\n",
" # Computes horizon dynamically.\n",
" horizon = sum([sum(durations[i]) for i in all_jobs])\n",
"\n",
" task_type = collections.namedtuple('task_type', 'start end interval')\n",
" task_type = collections.namedtuple(\"task_type\", \"start end interval\")\n",
"\n",
" # Creates jobs.\n",
" 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.NewIntVar(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(start_var, duration, end_var,\n",
" 'interval_%i_%i' % (i, j))\n",
" all_tasks[(i, j)] = task_type(start=start_var,\n",
" end=end_var,\n",
" interval=interval_var)\n",
" end_var = model.NewIntVar(0, horizon, \"end_%i_%i\" % (i, j))\n",
" interval_var = model.NewIntervalVar(\n",
" start_var, duration, end_var, \"interval_%i_%i\" % (i, j)\n",
" )\n",
" all_tasks[(i, j)] = task_type(\n",
" start=start_var, end=end_var, interval=interval_var\n",
" )\n",
"\n",
" # Create disjuctive constraints.\n",
" machine_to_jobs = {}\n",
@@ -148,9 +162,10 @@
" 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",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])\n",
" obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]\n",
" )\n",
" model.Minimize(obj_var)\n",
"\n",
" # Solve model.\n",
@@ -161,12 +176,13 @@
" # Output 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",
" ] for i in all_jobs]\n",
" visualization.DisplayJobshop(starts, durations, machines, 'FT06')\n",
" starts = [\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.ObjectiveValue())\n",
"\n",
"\n",
"jobshop_ft06()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Jobshop with maintenance tasks using the CP-SAT solver.\n"
]
},
@@ -96,8 +97,10 @@
"\n",
" def on_solution_callback(self):\n",
" \"\"\"Called at each new solution.\"\"\"\n",
" print('Solution %i, time = %f s, objective = %i' %\n",
" (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
@@ -119,10 +122,11 @@
" horizon = sum(task[1] for job in jobs_data for task in job)\n",
"\n",
" # Named tuple to store information about created variables.\n",
" task_type = collections.namedtuple('Task', 'start end interval')\n",
" task_type = collections.namedtuple(\"Task\", \"start end interval\")\n",
" # Named tuple to manipulate solution information.\n",
" assigned_task_type = collections.namedtuple('assigned_task_type',\n",
" 'start job index duration')\n",
" assigned_task_type = collections.namedtuple(\n",
" \"assigned_task_type\", \"start job index duration\"\n",
" )\n",
"\n",
" # Creates job intervals and add to the corresponding machine lists.\n",
" all_tasks = {}\n",
@@ -132,18 +136,19 @@
" for task_id, task in enumerate(job):\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(start_var, duration, end_var,\n",
" 'interval' + suffix)\n",
" all_tasks[job_id, task_id] = task_type(start=start_var,\n",
" end=end_var,\n",
" interval=interval_var)\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, duration, end_var, \"interval\" + suffix\n",
" )\n",
" all_tasks[job_id, task_id] = task_type(\n",
" start=start_var, end=end_var, interval=interval_var\n",
" )\n",
" 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.NewIntervalVar(4, 4, 8, \"weekend_0\"))\n",
"\n",
" # Create and add disjunctive constraints.\n",
" for machine in all_machines:\n",
@@ -152,15 +157,16 @@
" # 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(all_tasks[job_id, task_id +\n",
" 1].start >= all_tasks[job_id, task_id].end)\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(obj_var, [\n",
" all_tasks[job_id, len(job) - 1].end\n",
" for job_id, job in enumerate(jobs_data)\n",
" ])\n",
" obj_var = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(\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",
"\n",
" # Solve model.\n",
@@ -176,48 +182,50 @@
" for task_id, task in enumerate(job):\n",
" machine = task[0]\n",
" assigned_jobs[machine].append(\n",
" assigned_task_type(start=solver.Value(\n",
" all_tasks[job_id, task_id].start),\n",
" job=job_id,\n",
" index=task_id,\n",
" duration=task[1]))\n",
" assigned_task_type(\n",
" start=solver.Value(all_tasks[job_id, task_id].start),\n",
" job=job_id,\n",
" index=task_id,\n",
" duration=task[1],\n",
" )\n",
" )\n",
"\n",
" # Create per machine output lines.\n",
" output = ''\n",
" output = \"\"\n",
" for machine in all_machines:\n",
" # Sort by starting time.\n",
" assigned_jobs[machine].sort()\n",
" sol_line_tasks = 'Machine ' + str(machine) + ': '\n",
" sol_line = ' '\n",
" sol_line_tasks = \"Machine \" + str(machine) + \": \"\n",
" sol_line = \" \"\n",
"\n",
" for assigned_task in assigned_jobs[machine]:\n",
" name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)\n",
" name = \"job_%i_%i\" % (assigned_task.job, assigned_task.index)\n",
" # Add spaces to output to align columns.\n",
" sol_line_tasks += '%-10s' % name\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",
" sol_tmp = \"[%i,%i]\" % (start, start + duration)\n",
" # Add spaces to output to align columns.\n",
" sol_line += '%-10s' % sol_tmp\n",
" sol_line += \"%-10s\" % sol_tmp\n",
"\n",
" sol_line += '\\n'\n",
" sol_line_tasks += '\\n'\n",
" sol_line += \"\\n\"\n",
" sol_line_tasks += \"\\n\"\n",
" output += sol_line_tasks\n",
" 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.ObjectiveValue())\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(\"Statistics\")\n",
" print(\" - conflicts : %i\" % solver.NumConflicts())\n",
" print(\" - branches : %i\" % solver.NumBranches())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" jobshop_with_maintenance()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solver a 2D rectangle knapsack problem.\n",
"\n",
"This code is adapted from\n",
@@ -96,14 +97,16 @@
"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",
"_PARAMS = flags.DEFINE_string(\n",
" 'params',\n",
" 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n",
" 'Sat solver parameters.',\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"_MODEL = flags.DEFINE_string(\n",
" \"model\", \"rotation\", \"'duplicate' or 'rotation' or 'optional'\"\n",
")\n",
"_MODEL = flags.DEFINE_string('model', 'rotation',\n",
" '\\'duplicate\\' or \\'rotation\\' or \\'optional\\'')\n",
"\n",
"\n",
"def build_data():\n",
@@ -122,25 +125,25 @@
" k10 9 11 5 369.560 cyan\n",
" \"\"\"\n",
"\n",
" data = pd.read_table(io.StringIO(data), sep=r'\\s+')\n",
" print('Input data')\n",
" data = pd.read_table(io.StringIO(data), sep=r\"\\s+\")\n",
" print(\"Input data\")\n",
" print(data)\n",
"\n",
" max_height = 20\n",
" max_width = 30\n",
"\n",
" print(f'Container max_width:{max_width} max_height:{max_height}')\n",
" print(f'#Items: {len(data.index)}')\n",
" print(f\"Container max_width:{max_width} max_height:{max_height}\")\n",
" print(f\"#Items: {len(data.index)}\")\n",
" 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",
" # Derived data (expanded to individual items).\n",
" data_widths = data['width'].to_numpy()\n",
" data_heights = data['height'].to_numpy()\n",
" data_availability = data['available'].to_numpy()\n",
" data_values = data['value'].to_numpy()\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
" data_availability = data[\"available\"].to_numpy()\n",
" data_values = data[\"value\"].to_numpy()\n",
"\n",
" # Non duplicated items data.\n",
" base_item_widths = np.repeat(data_widths, data_availability)\n",
@@ -169,21 +172,29 @@
"\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.NewBoolVar(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.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",
"\n",
" ## Interval variables.\n",
" x_intervals.append(\n",
" model.NewIntervalVar(x_starts[i], item_widths[i] * is_used[i],\n",
" x_ends[i], f'x_interval{i}'))\n",
" model.NewIntervalVar(\n",
" x_starts[i], item_widths[i] * is_used[i], x_ends[i], f\"x_interval{i}\"\n",
" )\n",
" )\n",
" y_intervals.append(\n",
" model.NewIntervalVar(y_starts[i], item_heights[i] * is_used[i],\n",
" y_ends[i], f'y_interval{i}'))\n",
" model.NewIntervalVar(\n",
" y_starts[i], item_heights[i] * is_used[i], y_ends[i], 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",
"\n",
" # Constraints.\n",
"\n",
@@ -199,8 +210,8 @@
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
" print('Writing proto to %s' % _OUTPUT_PROTO.value)\n",
" with open(_OUTPUT_PROTO.value, 'w') as text_file:\n",
" print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n",
" with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve model.\n",
@@ -211,27 +222,29 @@
" status = solver.Solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL:\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",
" data = pd.DataFrame({\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",
" 'item_value': [item_values[i] for i in used]\n",
" })\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\": [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",
" \"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",
" # Derived data (expanded to individual items).\n",
" data_widths = data['width'].to_numpy()\n",
" data_heights = data['height'].to_numpy()\n",
" data_availability = data['available'].to_numpy()\n",
" data_values = data['value'].to_numpy()\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
" data_availability = data[\"available\"].to_numpy()\n",
" data_values = data[\"value\"].to_numpy()\n",
"\n",
" # Non duplicated items data.\n",
" base_item_widths = np.repeat(data_widths, data_availability)\n",
@@ -258,22 +271,30 @@
"\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.NewBoolVar(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.NewIntVar(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]),\n",
" f'y_start{i}'))\n",
" model.NewIntVar(0, max_height - int(item_heights[i]), f\"y_start{i}\")\n",
" )\n",
"\n",
" ## Interval variables.\n",
" x_intervals.append(\n",
" model.NewOptionalFixedSizeIntervalVar(x_starts[i], item_widths[i],\n",
" is_used[i], f'x_interval{i}'))\n",
" model.NewOptionalFixedSizeIntervalVar(\n",
" x_starts[i], item_widths[i], is_used[i], f\"x_interval{i}\"\n",
" )\n",
" )\n",
" y_intervals.append(\n",
" model.NewOptionalFixedSizeIntervalVar(y_starts[i], item_heights[i],\n",
" is_used[i], f'y_interval{i}'))\n",
" model.NewOptionalFixedSizeIntervalVar(\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",
"\n",
" # Constraints.\n",
"\n",
@@ -289,8 +310,8 @@
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
" print('Writing proto to %s' % _OUTPUT_PROTO.value)\n",
" with open(_OUTPUT_PROTO.value, 'w') as text_file:\n",
" print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n",
" with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve model.\n",
@@ -301,29 +322,29 @@
" status = solver.Solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL:\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",
" data = pd.DataFrame({\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': [\n",
" solver.Value(y_starts[i]) + item_heights[i] for i in used\n",
" ],\n",
" 'item_value': [item_values[i] for i in used]\n",
" })\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\": [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",
" \"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",
" # Derived data (expanded to individual items).\n",
" data_widths = data['width'].to_numpy()\n",
" data_heights = data['height'].to_numpy()\n",
" data_availability = data['available'].to_numpy()\n",
" data_values = data['value'].to_numpy()\n",
" data_widths = data[\"width\"].to_numpy()\n",
" data_heights = data[\"height\"].to_numpy()\n",
" data_availability = data[\"available\"].to_numpy()\n",
" data_values = data[\"value\"].to_numpy()\n",
"\n",
" item_widths = np.repeat(data_widths, data_availability)\n",
" item_heights = np.repeat(data_heights, data_availability)\n",
@@ -347,26 +368,26 @@
" 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.NewIntVar(0, max_width, f\"x_start{i}\"))\n",
" x_sizes.append(\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),\n",
" f'x_size{i}'))\n",
" x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}'))\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"x_size{i}\")\n",
" )\n",
" x_ends.append(model.NewIntVar(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.NewIntVar(0, max_height, f\"y_start{i}\"))\n",
" y_sizes.append(\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),\n",
" f'y_size{i}'))\n",
" y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}'))\n",
" model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f\"y_size{i}\")\n",
" )\n",
" y_ends.append(model.NewIntVar(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],\n",
" f'x_interval{i}'))\n",
" model.NewIntervalVar(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],\n",
" f'y_interval{i}'))\n",
" model.NewIntervalVar(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",
" is_used = []\n",
@@ -375,9 +396,9 @@
"\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.NewBoolVar(f\"not_selected_{i}\")\n",
" no_rotation = model.NewBoolVar(f\"no_rotation_{i}\")\n",
" rotated = model.NewBoolVar(f\"rotated_{i}\")\n",
"\n",
" ### Exactly one state must be chosen.\n",
" model.AddExactlyOne(not_selected, no_rotation, rotated)\n",
@@ -385,8 +406,12 @@
" ### 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",
" # 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",
@@ -402,8 +427,8 @@
"\n",
" # Output proto to file.\n",
" if _OUTPUT_PROTO.value:\n",
" print('Writing proto to %s' % _OUTPUT_PROTO.value)\n",
" with open(_OUTPUT_PROTO.value, 'w') as text_file:\n",
" print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n",
" with open(_OUTPUT_PROTO.value, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve model.\n",
@@ -414,26 +439,28 @@
" status = solver.Solve(model)\n",
"\n",
" # Report solution.\n",
" if status == cp_model.OPTIMAL:\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",
" data = pd.DataFrame({\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",
" 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",
" \"item_value\": [item_values[i] for i in used],\n",
" }\n",
" )\n",
" print(data)\n",
"\n",
"\n",
"def main(_):\n",
" \"\"\"Solve the problem with all models.\"\"\"\n",
" data, max_height, max_width = build_data()\n",
" if _MODEL.value == 'duplicate':\n",
" if _MODEL.value == \"duplicate\":\n",
" solve_with_duplicate_items(data, max_height, max_width)\n",
" elif _MODEL.value == 'optional':\n",
" elif _MODEL.value == \"optional\":\n",
" solve_with_duplicate_optional_items(data, max_height, max_width)\n",
" else:\n",
" solve_with_rotations(data, max_height, max_width)\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Reader and solver of the single assembly line balancing problem.\n",
"\n",
"from https://assembly-line-balancing.de/salbp/:\n",
@@ -103,12 +104,14 @@
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.')\n",
"_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.')\n",
"_INPUT = flags.DEFINE_string(\"input\", \"\", \"Input file to parse and solve.\")\n",
"_PARAMS = flags.DEFINE_string(\"params\", \"\", \"Sat solver parameters.\")\n",
"_OUTPUT_PROTO = flags.DEFINE_string(\n",
" 'output_proto', '', 'Output file to write the cp_model proto to.')\n",
"_MODEL = flags.DEFINE_string('model', 'boolean',\n",
" 'Model used: boolean, scheduling, greedy')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_MODEL = flags.DEFINE_string(\n",
" \"model\", \"boolean\", \"Model used: boolean, scheduling, greedy\"\n",
")\n",
"\n",
"\n",
"class SectionInfo(object):\n",
@@ -121,13 +124,13 @@
"\n",
" def __str__(self):\n",
" if self.index_map:\n",
" return f'SectionInfo(index_map={self.index_map})'\n",
" return f\"SectionInfo(index_map={self.index_map})\"\n",
" elif self.set_of_pairs:\n",
" return f'SectionInfo(set_of_pairs={self.set_of_pairs})'\n",
" return f\"SectionInfo(set_of_pairs={self.set_of_pairs})\"\n",
" elif self.value is not None:\n",
" return f'SectionInfo(value={self.value})'\n",
" return f\"SectionInfo(value={self.value})\"\n",
" else:\n",
" return 'SectionInfo()'\n",
" return \"SectionInfo()\"\n",
"\n",
"\n",
"def read_model(filename):\n",
@@ -136,64 +139,64 @@
" current_info = SectionInfo()\n",
"\n",
" model = {}\n",
" with open(filename, 'r') as input_file:\n",
" print(f'Reading model from \\'{filename}\\'')\n",
" section_name = ''\n",
" with open(filename, \"r\") as input_file:\n",
" print(f\"Reading model from '{filename}'\")\n",
" section_name = \"\"\n",
"\n",
" for line in input_file:\n",
" stripped_line = line.strip()\n",
" if not stripped_line:\n",
" continue\n",
"\n",
" match_section_def = re.match(r'<([\\w\\s]+)>', stripped_line)\n",
" match_section_def = re.match(r\"<([\\w\\s]+)>\", stripped_line)\n",
" if match_section_def:\n",
" section_name = match_section_def.group(1)\n",
" if section_name == 'end':\n",
" if section_name == \"end\":\n",
" continue\n",
"\n",
" current_info = SectionInfo()\n",
" model[section_name] = current_info\n",
" continue\n",
"\n",
" match_single_number = re.match(r'^([0-9]+)$', stripped_line)\n",
" match_single_number = re.match(r\"^([0-9]+)$\", stripped_line)\n",
" if match_single_number:\n",
" current_info.value = int(match_single_number.group(1))\n",
" continue\n",
"\n",
" match_key_value = re.match(r'^([0-9]+)\\s+([0-9]+)$', stripped_line)\n",
" match_key_value = re.match(r\"^([0-9]+)\\s+([0-9]+)$\", stripped_line)\n",
" if match_key_value:\n",
" key = int(match_key_value.group(1))\n",
" value = int(match_key_value.group(2))\n",
" current_info.index_map[key] = value\n",
" continue\n",
"\n",
" match_pair = re.match(r'^([0-9]+),([0-9]+)$', stripped_line)\n",
" match_pair = re.match(r\"^([0-9]+),([0-9]+)$\", stripped_line)\n",
" if match_pair:\n",
" left = int(match_pair.group(1))\n",
" right = int(match_pair.group(2))\n",
" current_info.set_of_pairs.add((left, right))\n",
" continue\n",
"\n",
" print(f'Unrecognized line \\'{stripped_line}\\'')\n",
" print(f\"Unrecognized line '{stripped_line}'\")\n",
"\n",
" return model\n",
"\n",
"\n",
"def print_stats(model):\n",
" print('Model Statistics')\n",
" print(\"Model Statistics\")\n",
" for key, value in model.items():\n",
" print(f' - {key}: {value}')\n",
" print(f\" - {key}: {value}\")\n",
"\n",
"\n",
"def solve_model_greedily(model):\n",
" \"\"\"Compute a greedy solution.\"\"\"\n",
" print('Solving using a Greedy heuristics')\n",
" print(\"Solving using a Greedy heuristics\")\n",
"\n",
" num_tasks = model['number of tasks'].value\n",
" num_tasks = model[\"number of tasks\"].value\n",
" all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.\n",
" precedences = model['precedence relations'].set_of_pairs\n",
" durations = model['task times'].index_map\n",
" cycle_time = model['cycle time'].value\n",
" precedences = model[\"precedence relations\"].set_of_pairs\n",
" durations = model[\"task times\"].index_map\n",
" cycle_time = model[\"cycle time\"].value\n",
"\n",
" weights = collections.defaultdict(int)\n",
" successors = collections.defaultdict(list)\n",
@@ -212,7 +215,7 @@
"\n",
" while len(assignment) < num_tasks:\n",
" if not candidates:\n",
" print('error empty')\n",
" print(\"error empty\")\n",
" break\n",
"\n",
" best = -1\n",
@@ -242,7 +245,7 @@
" candidates.add(succ)\n",
" del weights[succ]\n",
"\n",
" print(f' greedy solution uses {current_pod + 1} pods.')\n",
" print(f\" greedy solution uses {current_pod + 1} pods.\")\n",
"\n",
" return assignment\n",
"\n",
@@ -250,13 +253,13 @@
"def solve_boolean_model(model, hint):\n",
" \"\"\"Solve the given model.\"\"\"\n",
"\n",
" print('Solving using the Boolean model')\n",
" print(\"Solving using the Boolean model\")\n",
" # Model data\n",
" num_tasks = model['number of tasks'].value\n",
" num_tasks = model[\"number of tasks\"].value\n",
" all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the model.\n",
" durations = model['task times'].index_map\n",
" precedences = model['precedence relations'].set_of_pairs\n",
" cycle_time = model['cycle time'].value\n",
" durations = model[\"task times\"].index_map\n",
" precedences = model[\"precedence relations\"].set_of_pairs\n",
" cycle_time = model[\"cycle time\"].value\n",
"\n",
" num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks - 1\n",
" all_pods = range(num_pods)\n",
@@ -271,11 +274,11 @@
" # 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.NewBoolVar(f\"assign_{t}_{p}\")\n",
" possible[t, p] = model.NewBoolVar(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.NewBoolVar(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",
@@ -283,8 +286,7 @@
"\n",
" # Total tasks assigned to one pod cannot exceed cycle time.\n",
" for p in all_pods:\n",
" model.Add(\n",
" 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",
@@ -302,8 +304,7 @@
" # Precedences.\n",
" for before, after in precedences:\n",
" for p in range(1, num_pods):\n",
" model.AddImplication(assign[before, p], possible[after,\n",
" p - 1].Not())\n",
" model.AddImplication(assign[before, p], possible[after, p - 1].Not())\n",
"\n",
" # Link active variables with the assign one.\n",
" for p in all_pods:\n",
@@ -327,7 +328,7 @@
" model.AddHint(assign[t, hint[t]], 1)\n",
"\n",
" if _OUTPUT_PROTO.value:\n",
" print(f'Writing proto to {_OUTPUT_PROTO.value}')\n",
" print(f\"Writing proto to {_OUTPUT_PROTO.value}\")\n",
" model.ExportToFile(_OUTPUT_PROTO.value)\n",
"\n",
" # Solve model.\n",
@@ -341,13 +342,13 @@
"def solve_scheduling_model(model, hint):\n",
" \"\"\"Solve the given model using a cumutive model.\"\"\"\n",
"\n",
" print('Solving using the scheduling model')\n",
" print(\"Solving using the scheduling model\")\n",
" # Model data\n",
" num_tasks = model['number of tasks'].value\n",
" num_tasks = model[\"number of tasks\"].value\n",
" all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.\n",
" durations = model['task times'].index_map\n",
" precedences = model['precedence relations'].set_of_pairs\n",
" cycle_time = model['cycle time'].value\n",
" durations = model[\"task times\"].index_map\n",
" precedences = model[\"precedence relations\"].set_of_pairs\n",
" cycle_time = model[\"cycle time\"].value\n",
"\n",
" num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks\n",
"\n",
@@ -356,21 +357,20 @@
" # 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.NewIntVar(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.NewFixedSizeIntervalVar(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,\n",
" 'obj_interval')\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",
" intervals.append(obj_interval)\n",
" demands.append(cycle_time)\n",
"\n",
@@ -389,7 +389,7 @@
" model.AddHint(pods[t], hint[t])\n",
"\n",
" if _OUTPUT_PROTO.value:\n",
" print(f'Writing proto to{_OUTPUT_PROTO.value}')\n",
" print(f\"Writing proto to{_OUTPUT_PROTO.value}\")\n",
" model.ExportToFile(_OUTPUT_PROTO.value)\n",
"\n",
" # Solve model.\n",
@@ -402,15 +402,15 @@
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" model = read_model(_INPUT.value)\n",
" print_stats(model)\n",
" greedy_solution = solve_model_greedily(model)\n",
"\n",
" if _MODEL.value == 'boolean':\n",
" if _MODEL.value == \"boolean\":\n",
" solve_boolean_model(model, greedy_solution)\n",
" elif _MODEL.value == 'scheduling':\n",
" elif _MODEL.value == \"scheduling\":\n",
" solve_scheduling_model(model, greedy_solution)\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Test linear sum assignment on a 4x4 matrix.\n",
"\n",
" Example taken from:\n",
@@ -95,8 +96,7 @@
" \"\"\"Test linear sum assignment on a 4x4 matrix.\"\"\"\n",
" num_sources = 4\n",
" num_targets = 4\n",
" cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n",
" [45, 110, 95, 115]]\n",
" cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n",
" expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]\n",
"\n",
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
@@ -106,21 +106,22 @@
"\n",
" solve_status = assignment.solve()\n",
" if solve_status == assignment.OPTIMAL:\n",
" print('Successful solve.')\n",
" print('Total cost', assignment.optimal_cost(), '/', expected_cost)\n",
" print(\"Successful solve.\")\n",
" print(\"Total cost\", assignment.optimal_cost(), \"/\", expected_cost)\n",
" for i in range(0, assignment.num_nodes()):\n",
" print('Left node %d assigned to right node %d with cost %d.' %\n",
" (i, assignment.right_mate(i), assignment.assignment_cost(i)))\n",
" print(\n",
" \"Left node %d assigned to right node %d with cost %d.\"\n",
" % (i, assignment.right_mate(i), assignment.assignment_cost(i))\n",
" )\n",
" elif solve_status == assignment.INFEASIBLE:\n",
" print('No perfect matching exists.')\n",
" print(\"No perfect matching exists.\")\n",
" elif solve_status == assignment.POSSIBLE_OVERFLOW:\n",
" print(\n",
" 'Some input costs are too large and may cause an integer overflow.')\n",
" print(\"Some input costs are too large and may cause an integer overflow.\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" run_assignment_on_4x4_matrix()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Linear programming examples that show how to use the APIs.\n"
]
},
@@ -86,8 +87,9 @@
"\n",
"\n",
"def Announce(solver, api_type):\n",
" print('---- Linear programming example with ' + solver + ' (' + api_type +\n",
" ') -----')\n",
" print(\n",
" \"---- Linear programming example with \" + solver + \" (\" + api_type + \") -----\"\n",
" )\n",
"\n",
"\n",
"def RunLinearExampleNaturalLanguageAPI(optimization_problem_type):\n",
@@ -97,23 +99,25 @@
" if not solver:\n",
" return\n",
"\n",
" Announce(optimization_problem_type, 'natural language API')\n",
" Announce(optimization_problem_type, \"natural language API\")\n",
"\n",
" infinity = solver.infinity()\n",
" # x1, x2 and x3 are continuous non-negative variables.\n",
" x1 = solver.NumVar(0.0, infinity, 'x1')\n",
" x2 = solver.NumVar(0.0, infinity, 'x2')\n",
" x3 = solver.NumVar(0.0, infinity, 'x3')\n",
" x1 = solver.NumVar(0.0, infinity, \"x1\")\n",
" x2 = solver.NumVar(0.0, infinity, \"x2\")\n",
" x3 = solver.NumVar(0.0, infinity, \"x3\")\n",
"\n",
" solver.Maximize(10 * x1 + 6 * x2 + 4 * x3)\n",
" c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, 'ConstraintName0')\n",
" c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, \"ConstraintName0\")\n",
" c1 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300)\n",
" sum_of_vars = sum([x1, x2, x3])\n",
" c2 = solver.Add(sum_of_vars <= 100.0, 'OtherConstraintName')\n",
" c2 = solver.Add(sum_of_vars <= 100.0, \"OtherConstraintName\")\n",
"\n",
" SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != 'PDLP')\n",
" SolveAndPrint(\n",
" solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != \"PDLP\"\n",
" )\n",
" # Print a linear expression's solution value.\n",
" print('Sum of vars: %s = %s' % (sum_of_vars, sum_of_vars.solution_value()))\n",
" print(\"Sum of vars: %s = %s\" % (sum_of_vars, sum_of_vars.solution_value()))\n",
"\n",
"\n",
"def RunLinearExampleCppStyleAPI(optimization_problem_type):\n",
@@ -122,13 +126,13 @@
" if not solver:\n",
" return\n",
"\n",
" Announce(optimization_problem_type, 'C++ style API')\n",
" Announce(optimization_problem_type, \"C++ style API\")\n",
"\n",
" infinity = solver.infinity()\n",
" # x1, x2 and x3 are continuous non-negative variables.\n",
" x1 = solver.NumVar(0.0, infinity, 'x1')\n",
" x2 = solver.NumVar(0.0, infinity, 'x2')\n",
" x3 = solver.NumVar(0.0, infinity, 'x3')\n",
" x1 = solver.NumVar(0.0, infinity, \"x1\")\n",
" x2 = solver.NumVar(0.0, infinity, \"x2\")\n",
" x3 = solver.NumVar(0.0, infinity, \"x3\")\n",
"\n",
" # Maximize 10 * x1 + 6 * x2 + 4 * x3.\n",
" objective = solver.Objective()\n",
@@ -138,31 +142,32 @@
" objective.SetMaximization()\n",
"\n",
" # x1 + x2 + x3 <= 100.\n",
" c0 = solver.Constraint(-infinity, 100.0, 'c0')\n",
" c0 = solver.Constraint(-infinity, 100.0, \"c0\")\n",
" c0.SetCoefficient(x1, 1)\n",
" c0.SetCoefficient(x2, 1)\n",
" c0.SetCoefficient(x3, 1)\n",
"\n",
" # 10 * x1 + 4 * x2 + 5 * x3 <= 600.\n",
" c1 = solver.Constraint(-infinity, 600.0, 'c1')\n",
" c1 = solver.Constraint(-infinity, 600.0, \"c1\")\n",
" c1.SetCoefficient(x1, 10)\n",
" c1.SetCoefficient(x2, 4)\n",
" c1.SetCoefficient(x3, 5)\n",
"\n",
" # 2 * x1 + 2 * x2 + 6 * x3 <= 300.\n",
" c2 = solver.Constraint(-infinity, 300.0, 'c2')\n",
" c2 = solver.Constraint(-infinity, 300.0, \"c2\")\n",
" c2.SetCoefficient(x1, 2)\n",
" c2.SetCoefficient(x2, 2)\n",
" c2.SetCoefficient(x3, 6)\n",
"\n",
" SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2],\n",
" optimization_problem_type != 'PDLP')\n",
" SolveAndPrint(\n",
" solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != \"PDLP\"\n",
" )\n",
"\n",
"\n",
"def SolveAndPrint(solver, variable_list, constraint_list, is_precise):\n",
" \"\"\"Solve the problem and print the solution.\"\"\"\n",
" print('Number of variables = %d' % solver.NumVariables())\n",
" print('Number of constraints = %d' % solver.NumConstraints())\n",
" print(\"Number of variables = %d\" % solver.NumVariables())\n",
" print(\"Number of constraints = %d\" % solver.NumConstraints())\n",
"\n",
" result_status = solver.Solve()\n",
"\n",
@@ -174,37 +179,39 @@
" if is_precise:\n",
" assert solver.VerifySolution(1e-7, True)\n",
"\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
"\n",
" # The objective value of the solution.\n",
" print('Optimal objective value = %f' % solver.Objective().Value())\n",
" print(\"Optimal objective value = %f\" % solver.Objective().Value())\n",
"\n",
" # The value of each variable in the solution.\n",
" for variable in variable_list:\n",
" print('%s = %f' % (variable.name(), variable.solution_value()))\n",
" print(\"%s = %f\" % (variable.name(), variable.solution_value()))\n",
"\n",
" print('Advanced usage:')\n",
" print('Problem solved in %d iterations' % solver.iterations())\n",
" print(\"Advanced usage:\")\n",
" print(\"Problem solved in %d iterations\" % solver.iterations())\n",
" for variable in variable_list:\n",
" print('%s: reduced cost = %f' %\n",
" (variable.name(), variable.reduced_cost()))\n",
" print(\"%s: reduced cost = %f\" % (variable.name(), variable.reduced_cost()))\n",
" activities = solver.ComputeConstraintActivities()\n",
" for i, constraint in enumerate(constraint_list):\n",
" print(('constraint %d: dual value = %f\\n'\n",
" ' activity = %f' %\n",
" (i, constraint.dual_value(), activities[constraint.index()])))\n",
" print(\n",
" (\n",
" \"constraint %d: dual value = %f\\n activity = %f\"\n",
" % (i, constraint.dual_value(), activities[constraint.index()])\n",
" )\n",
" )\n",
"\n",
"\n",
"def main():\n",
" RunLinearExampleNaturalLanguageAPI('GLOP')\n",
" RunLinearExampleNaturalLanguageAPI('GLPK_LP')\n",
" RunLinearExampleNaturalLanguageAPI('CLP')\n",
" RunLinearExampleNaturalLanguageAPI('PDLP')\n",
" RunLinearExampleNaturalLanguageAPI(\"GLOP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"GLPK_LP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"CLP\")\n",
" RunLinearExampleNaturalLanguageAPI(\"PDLP\")\n",
"\n",
" RunLinearExampleCppStyleAPI('GLOP')\n",
" RunLinearExampleCppStyleAPI('GLPK_LP')\n",
" RunLinearExampleCppStyleAPI('CLP')\n",
" RunLinearExampleCppStyleAPI('PDLP')\n",
" RunLinearExampleCppStyleAPI(\"GLOP\")\n",
" RunLinearExampleCppStyleAPI(\"GLPK_LP\")\n",
" RunLinearExampleCppStyleAPI(\"CLP\")\n",
" RunLinearExampleCppStyleAPI(\"PDLP\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Magic sequence problem.\n",
"\n",
"This models aims at building a sequence of numbers such that the number of\n",
@@ -96,12 +97,12 @@
"\n",
"def main(argv):\n",
" # Create the solver.\n",
" solver = pywrapcp.Solver('magic sequence')\n",
" solver = pywrapcp.Solver(\"magic sequence\")\n",
"\n",
" # Create an array of IntVars to hold the answers.\n",
" size = int(argv[1]) if len(argv) > 1 else 100\n",
" all_values = list(range(0, size))\n",
" all_vars = [solver.IntVar(0, size, 'vars_%d' % i) for i in all_values]\n",
" all_vars = [solver.IntVar(0, size, \"vars_%d\" % i) for i in all_values]\n",
"\n",
" # The number of variables equal to j shall be the value of all_vars[j].\n",
" solver.Add(solver.Distribute(all_vars, all_values, all_vars))\n",
@@ -111,8 +112,8 @@
" solver.Add(solver.Sum(all_vars) == size)\n",
"\n",
" solver.NewSearch(\n",
" solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND,\n",
" solver.ASSIGN_MIN_VALUE))\n",
" solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)\n",
" )\n",
" solver.NextSolution()\n",
" print(all_vars)\n",
" solver.EndSearch()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Excape the maze while collecting treasures in order.\n",
"\n",
"The path must begin at the 'start' position, finish at the 'end' position,\n",
@@ -94,23 +95,29 @@
"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",
"_PARAMS = flags.DEFINE_string('params',\n",
" 'num_search_workers:8,log_search_progress:true',\n",
" 'Sat solver parameters.')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\", \"num_search_workers:8,log_search_progress:true\", \"Sat solver parameters.\"\n",
")\n",
"\n",
"\n",
"def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank,\n",
" arcs):\n",
"def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, arcs):\n",
" \"\"\"Checks if the neighbor is valid, and adds it to the model.\"\"\"\n",
" if (x + dx < 0 or x + dx >= size or y + dy < 0 or y + dy >= size or\n",
" z + dz < 0 or z + dz >= size):\n",
" if (\n",
" x + dx < 0\n",
" or x + dx >= size\n",
" or y + dy < 0\n",
" or y + dy >= size\n",
" or z + dz < 0\n",
" or z + dz >= size\n",
" ):\n",
" return\n",
" before_index = index_map[(x, y, z)]\n",
" 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",
" move_literal = model.NewBoolVar(\"\")\n",
" model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal)\n",
" arcs.append((before_index, after_index, move_literal))\n",
"\n",
@@ -139,8 +146,7 @@
" position_to_rank = {}\n",
"\n",
" for coord in reverse_map:\n",
" position_to_rank[coord] = model.NewIntVar(0, counter - 1,\n",
" f'rank_{coord}')\n",
" position_to_rank[coord] = model.NewIntVar(0, counter - 1, f\"rank_{coord}\")\n",
"\n",
" # Path constraints.\n",
" model.Add(position_to_rank[start] == 0)\n",
@@ -154,18 +160,24 @@
" for x in range(size):\n",
" for y in range(size):\n",
" for z in range(size):\n",
" add_neighbor(size, x, y, z, -1, 0, 0, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(size, x, y, z, 1, 0, 0, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(size, x, y, z, 0, -1, 0, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(size, x, y, z, 0, 1, 0, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(size, x, y, z, 0, 0, -1, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(size, x, y, z, 0, 0, 1, model, index_map,\n",
" position_to_rank, arcs)\n",
" add_neighbor(\n",
" size, x, y, z, -1, 0, 0, model, index_map, position_to_rank, arcs\n",
" )\n",
" add_neighbor(\n",
" size, x, y, z, 1, 0, 0, model, index_map, position_to_rank, arcs\n",
" )\n",
" add_neighbor(\n",
" size, x, y, z, 0, -1, 0, model, index_map, position_to_rank, arcs\n",
" )\n",
" add_neighbor(\n",
" size, x, y, z, 0, 1, 0, model, index_map, position_to_rank, arcs\n",
" )\n",
" add_neighbor(\n",
" size, x, y, z, 0, 0, -1, model, index_map, position_to_rank, arcs\n",
" )\n",
" add_neighbor(\n",
" size, x, y, z, 0, 0, 1, model, index_map, position_to_rank, arcs\n",
" )\n",
"\n",
" # Closes the loop as the constraint expects a circuit, not a path.\n",
" arcs.append((index_map[end], index_map[start], True))\n",
@@ -186,28 +198,28 @@
"\n",
" # Prints solution.\n",
" if result == cp_model.OPTIMAL:\n",
" path = [''] * counter\n",
" path = [\"\"] * counter\n",
" for x in range(size):\n",
" 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",
" msg = f'({x}, {y}, {z})'\n",
" msg = f\"({x}, {y}, {z})\"\n",
" if position == start:\n",
" msg += ' [start]'\n",
" msg += \" [start]\"\n",
" elif position == end:\n",
" msg += ' [end]'\n",
" msg += \" [end]\"\n",
" else:\n",
" for b in range(len(boxes)):\n",
" if position == boxes[b]:\n",
" msg += f' [boxes {b}]'\n",
" msg += f\" [boxes {b}]\"\n",
" path[rank] = msg\n",
" print(path)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value)\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Scheduling cooking tasks in a bakery using no-wait jobshop scheduling.\n",
"\n",
"We are scheduling a full day of baking:\n",
@@ -93,34 +94,35 @@
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_PARAMS = flags.DEFINE_string('params',\n",
" 'num_search_workers:16, max_time_in_seconds:30',\n",
" 'Sat solver parameters.')\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\", \"num_search_workers:16, max_time_in_seconds:30\", \"Sat solver parameters.\"\n",
")\n",
"_PROTO_FILE = flags.DEFINE_string(\n",
" 'proto_file', '', 'If not empty, output the proto to this file.')\n",
" \"proto_file\", \"\", \"If not empty, output the proto to this file.\"\n",
")\n",
"\n",
"# Recipes\n",
"CROISSANT = 'croissant'\n",
"APPLE_PIE = 'apple pie'\n",
"BRIOCHE = 'brioche'\n",
"CHOCOLATE_CAKE = 'chocolate cake'\n",
"CROISSANT = \"croissant\"\n",
"APPLE_PIE = \"apple pie\"\n",
"BRIOCHE = \"brioche\"\n",
"CHOCOLATE_CAKE = \"chocolate cake\"\n",
"\n",
"# Skills\n",
"BAKING = 'baking'\n",
"PROOFING = 'proofing'\n",
"COOKING = 'cooking'\n",
"COOLING = 'cooling'\n",
"DECORATING = 'decorating'\n",
"DISPLAY = 'display'\n",
"BAKING = \"baking\"\n",
"PROOFING = \"proofing\"\n",
"COOKING = \"cooking\"\n",
"COOLING = \"cooling\"\n",
"DECORATING = \"decorating\"\n",
"DISPLAY = \"display\"\n",
"\n",
"\n",
"class Task(object):\n",
" \"\"\"A unit baking task.\n",
"\n",
" - Simple baking tasks have a fixed duration. They are performed by workers.\n",
" - Waiting/cooling/proofing tasks have a min and a max duration.\n",
" They are performed by machine or they use space resources.\n",
" \"\"\"\n",
" - Simple baking tasks have a fixed duration. They are performed by workers.\n",
" - Waiting/cooling/proofing tasks have a min and a max duration.\n",
" They are performed by machine or they use space resources.\n",
" \"\"\"\n",
"\n",
" def __init__(self, name, min_duration, max_duration):\n",
" self.name = name\n",
@@ -152,13 +154,13 @@
"class Resource(object):\n",
" \"\"\"A resource is a worker, a machine, or just some space for cakes to rest.\n",
"\n",
" - Workers have a capacity of 1 and can have variable efficiency.\n",
" - Machines and spaces have a capacity greater or equal to one, but the\n",
" efficiency is fixed to 100.\n",
" - Workers have a capacity of 1 and can have variable efficiency.\n",
" - Machines and spaces have a capacity greater or equal to one, but the\n",
" efficiency is fixed to 100.\n",
"\n",
" For a worker with efficiency k and a task of duration t, the resulting\n",
" work will have a duration `ceil(t * k)`.\n",
" \"\"\"\n",
" For a worker with efficiency k and a task of duration t, the resulting\n",
" work will have a duration `ceil(t * k)`.\n",
" \"\"\"\n",
"\n",
" def __init__(self, name, capacity):\n",
" self.name = name\n",
@@ -176,12 +178,12 @@
" def __init__(self, unique_id, recipe_name, due_date, quantity):\n",
" \"\"\"Builds an order.\n",
"\n",
" Args:\n",
" unique_id: A unique identifier for the order. Used to display the result.\n",
" recipe_name: The name of the recipe. It must match one of the recipes.\n",
" due_date: The due date in minutes since midnight.\n",
" quantity: How many cakes to prepare.\n",
" \"\"\"\n",
" Args:\n",
" unique_id: A unique identifier for the order. Used to display the result.\n",
" recipe_name: The name of the recipe. It must match one of the recipes.\n",
" due_date: The due date in minutes since midnight.\n",
" quantity: How many cakes to prepare.\n",
" \"\"\"\n",
" self.unique_id = unique_id\n",
" self.recipe_name = recipe_name\n",
" self.due_date = due_date\n",
@@ -217,32 +219,41 @@
" chocolate_cake_recipe.add_task(DECORATING, 15, 15)\n",
" chocolate_cake_recipe.add_task(DISPLAY, 5, 5 * 60)\n",
" recipes = [\n",
" croissant_recipe, apple_pie_recipe, brioche_recipe,\n",
" chocolate_cake_recipe\n",
" croissant_recipe,\n",
" apple_pie_recipe,\n",
" brioche_recipe,\n",
" chocolate_cake_recipe,\n",
" ]\n",
"\n",
" # Resources.\n",
" baker1 = Resource('baker1', 1).add_skill(BAKING, 1.0)\n",
" baker2 = Resource('baker2', 1).add_skill(BAKING, 1.0)\n",
" decorator1 = Resource('decorator1', 1).add_skill(DECORATING, 1.0)\n",
" waiting_space = Resource('waiting_space', 4).add_skill(PROOFING, 1.0)\n",
" oven = Resource('oven', 4).add_skill(COOKING, 1.0)\n",
" display_space = Resource('display_space', 12).add_skill(DISPLAY, 1.0)\n",
" baker1 = Resource(\"baker1\", 1).add_skill(BAKING, 1.0)\n",
" baker2 = Resource(\"baker2\", 1).add_skill(BAKING, 1.0)\n",
" decorator1 = Resource(\"decorator1\", 1).add_skill(DECORATING, 1.0)\n",
" waiting_space = Resource(\"waiting_space\", 4).add_skill(PROOFING, 1.0)\n",
" oven = Resource(\"oven\", 4).add_skill(COOKING, 1.0)\n",
" display_space = Resource(\"display_space\", 12).add_skill(DISPLAY, 1.0)\n",
" resources = [baker1, baker2, decorator1, waiting_space, oven, display_space]\n",
"\n",
" # Orders\n",
" croissant_7am = Order('croissant_7am', CROISSANT, 7 * 60, 3)\n",
" croissant_8am = Order('croissant_8am', CROISSANT, 8 * 60, 3)\n",
" croissant_9am = Order('croissant_9am', CROISSANT, 9 * 60, 2)\n",
" croissant_10am = Order('croissant_10am', CROISSANT, 10 * 60, 1)\n",
" croissant_11am = Order('croissant_11am', CROISSANT, 11 * 60, 1)\n",
" brioche_10am = Order('brioche_10am', BRIOCHE, 10 * 60, 8)\n",
" brioche_12pm = Order('brioche_12pm', BRIOCHE, 12 * 60, 8)\n",
" apple_pie_1pm = Order('apple_pie_1pm', APPLE_PIE, 13 * 60, 10)\n",
" chocolate_4pm = Order('chocolate_4pm', CHOCOLATE_CAKE, 16 * 60, 10)\n",
" croissant_7am = Order(\"croissant_7am\", CROISSANT, 7 * 60, 3)\n",
" croissant_8am = Order(\"croissant_8am\", CROISSANT, 8 * 60, 3)\n",
" croissant_9am = Order(\"croissant_9am\", CROISSANT, 9 * 60, 2)\n",
" croissant_10am = Order(\"croissant_10am\", CROISSANT, 10 * 60, 1)\n",
" croissant_11am = Order(\"croissant_11am\", CROISSANT, 11 * 60, 1)\n",
" brioche_10am = Order(\"brioche_10am\", BRIOCHE, 10 * 60, 8)\n",
" brioche_12pm = Order(\"brioche_12pm\", BRIOCHE, 12 * 60, 8)\n",
" apple_pie_1pm = Order(\"apple_pie_1pm\", APPLE_PIE, 13 * 60, 10)\n",
" chocolate_4pm = Order(\"chocolate_4pm\", CHOCOLATE_CAKE, 16 * 60, 10)\n",
" orders = [\n",
" croissant_7am, croissant_8am, croissant_9am, croissant_10am,\n",
" croissant_11am, brioche_10am, brioche_12pm, apple_pie_1pm, chocolate_4pm\n",
" croissant_7am,\n",
" croissant_8am,\n",
" croissant_9am,\n",
" croissant_10am,\n",
" croissant_11am,\n",
" brioche_10am,\n",
" brioche_12pm,\n",
" apple_pie_1pm,\n",
" chocolate_4pm,\n",
" ]\n",
"\n",
" return recipes, resources, orders\n",
@@ -276,50 +287,48 @@
" tardiness_vars = []\n",
" for order in orders:\n",
" for batch in range(order.quantity):\n",
" order_id = f'{order.unique_id}_{batch}'\n",
" order_id = f\"{order.unique_id}_{batch}\"\n",
" sorted_orders.append(order_id)\n",
" previous_end = None\n",
" due_date = order.due_date\n",
" recipe = recipe_by_name[order.recipe_name]\n",
" for task in recipe.tasks:\n",
" skill_name = task.name\n",
" suffix = f'_{order.unique_id}_batch{batch}_{skill_name}'\n",
" suffix = f\"_{order.unique_id}_batch{batch}_{skill_name}\"\n",
"\n",
" start = None\n",
" if previous_end is None:\n",
" start = model.NewIntVar(start_work, horizon,\n",
" f'start{suffix}')\n",
" start = model.NewIntVar(start_work, horizon, f\"start{suffix}\")\n",
" orders_sequence_of_events[order_id].append(\n",
" (start, f'start{suffix}'))\n",
" (start, f\"start{suffix}\")\n",
" )\n",
" else:\n",
" start = previous_end\n",
"\n",
" size = model.NewIntVar(task.min_duration, task.max_duration,\n",
" f'size{suffix}')\n",
" size = model.NewIntVar(\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,\n",
" f'end{suffix}')\n",
" tardiness = model.NewIntVar(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",
" orders_sequence_of_events[order_id].append(\n",
" (end, f'end{suffix}'))\n",
" end = model.NewIntVar(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(\n",
" f'presence{suffix}_{resource.name}')\n",
" presence = model.NewBoolVar(f\"presence{suffix}_{resource.name}\")\n",
" copy = model.NewOptionalIntervalVar(\n",
" start, size, end, presence,\n",
" f'interval{suffix}_{resource.name}')\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",
@@ -332,8 +341,7 @@
" if resource.capacity == 1:\n",
" model.AddNoOverlap(intervals)\n",
" else:\n",
" model.AddCumulative(intervals, [1] * len(intervals),\n",
" resource.capacity)\n",
" model.AddCumulative(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",
@@ -349,15 +357,15 @@
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" for order_id in sorted_orders:\n",
" print(f'{order_id}:')\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",
" print(f' {event_id} at {time // 60}:{time % 60:02}')\n",
" print(f\" {event_id} at {time // 60}:{time % 60:02}\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" recipes, resources, orders = set_up_data()\n",
" solve_with_cp_sat(recipes, resources, orders)\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"CP/SAT model for the N-queens problem.\n"
]
},
@@ -86,7 +87,7 @@
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_SIZE = flags.DEFINE_integer('size', 8, 'Number of queens.')\n",
"_SIZE = flags.DEFINE_integer(\"size\", 8, \"Number of queens.\")\n",
"\n",
"\n",
"class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
@@ -103,8 +104,10 @@
"\n",
" def on_solution_callback(self):\n",
" current_time = time.time()\n",
" print('Solution %i, time = %f s' %\n",
" (self.__solution_count, current_time - self.__start_time))\n",
" print(\n",
" \"Solution %i, time = %f s\"\n",
" % (self.__solution_count, current_time - self.__start_time)\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
" all_queens = range(len(self.__queens))\n",
@@ -112,9 +115,9 @@
" for j in all_queens:\n",
" if self.Value(self.__queens[j]) == i:\n",
" # There is a queen in column j, row i.\n",
" print('Q', end=' ')\n",
" print(\"Q\", end=\" \")\n",
" else:\n",
" print('_', end=' ')\n",
" print(\"_\", end=\" \")\n",
" print()\n",
" print()\n",
"\n",
@@ -127,9 +130,7 @@
"\n",
" ### Creates the variables.\n",
" # The array index is the column, and the value is the row.\n",
" queens = [\n",
" model.NewIntVar(0, board_size - 1, 'x%i' % i) for i in range(board_size)\n",
" ]\n",
" queens = [model.NewIntVar(0, board_size - 1, \"x%i\" % i) for i in range(board_size)]\n",
"\n",
" ### Creates the constraints.\n",
"\n",
@@ -141,8 +142,8 @@
" 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.NewIntVar(0, 2 * board_size, \"diag1_%i\" % i)\n",
" q2 = model.NewIntVar(-board_size, board_size, \"diag2_%i\" % i)\n",
" diag1.append(q1)\n",
" diag2.append(q2)\n",
" model.Add(q1 == queens[i] + i)\n",
@@ -159,11 +160,11 @@
" 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(\"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",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Simple prize collecting TSP problem with a max distance.\n"
]
},
@@ -87,6 +88,7 @@
"\n",
"\n",
"DISTANCE_MATRIX = [\n",
" # fmt:off\n",
" [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n",
" [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n",
" [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n",
@@ -127,7 +129,8 @@
" [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n",
" [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n",
" [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n",
"] # yapf: disable\n",
" # fmt:on\n",
"]\n",
"\n",
"MAX_DISTANCE = 80_000\n",
"\n",
@@ -139,22 +142,22 @@
"def print_solution(solver, visited_nodes, used_arcs, num_nodes):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" # Display dropped nodes.\n",
" dropped_nodes = '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",
" dropped_nodes += f' {i}({VISIT_VALUES[i]})'\n",
" dropped_nodes += f\" {i}({VISIT_VALUES[i]})\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
" current_node = 0\n",
" plan_output = 'Route for vehicle 0:\\n'\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" value_collected = 0\n",
" route_is_finished = False\n",
" while not route_is_finished:\n",
" value_collected += VISIT_VALUES[current_node]\n",
" plan_output += f' {current_node} ->'\n",
" plan_output += f\" {current_node} ->\"\n",
" # find next node\n",
" for node in range(num_nodes):\n",
" if node == current_node:\n",
@@ -165,9 +168,9 @@
" if current_node == 0:\n",
" route_is_finished = True\n",
" 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\" {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",
" print(plan_output)\n",
"\n",
"\n",
@@ -175,7 +178,7 @@
" \"\"\"Entry point of the program.\"\"\"\n",
" num_nodes = len(DISTANCE_MATRIX)\n",
" all_nodes = range(num_nodes)\n",
" print(f'Num nodes = {num_nodes}')\n",
" print(f\"Num nodes = {num_nodes}\")\n",
"\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
@@ -188,8 +191,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.NewBoolVar(f\"{i} is visited\")\n",
" arcs.append((i, i, is_visited.Not()))\n",
"\n",
" obj_vars.append(is_visited)\n",
" obj_coeffs.append(VISIT_VALUES[i])\n",
@@ -199,8 +202,8 @@
" if i == j:\n",
" used_arcs[i, j] = is_visited.Not()\n",
" continue\n",
" arc_is_used = model.NewBoolVar(f'{j} follows {i}')\n",
" arcs.append([i, j, arc_is_used])\n",
" arc_is_used = model.NewBoolVar(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",
@@ -213,13 +216,16 @@
"\n",
" # limit the route distance\n",
" model.Add(\n",
" sum(used_arcs[i, j] * DISTANCE_MATRIX[i][j]\n",
" sum(\n",
" used_arcs[i, j] * DISTANCE_MATRIX[i][j]\n",
" for i in all_nodes\n",
" for j in all_nodes) <= MAX_DISTANCE)\n",
" for j in all_nodes\n",
" )\n",
" <= MAX_DISTANCE\n",
" )\n",
"\n",
" # Maximize visited node values minus the travelled distance.\n",
" model.Maximize(\n",
" 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",
@@ -235,7 +241,7 @@
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" prize_collecting_tsp()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Simple prize collecting VRP problem with a max distance.\n"
]
},
@@ -87,6 +88,7 @@
"\n",
"\n",
"DISTANCE_MATRIX = [\n",
" # fmt:off\n",
" [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n",
" [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n",
" [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n",
@@ -127,7 +129,8 @@
" [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n",
" [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n",
" [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n",
"] # yapf: disable\n",
" # fmt:on\n",
"]\n",
"\n",
"MAX_DISTANCE = 80_000\n",
"\n",
@@ -139,29 +142,28 @@
"def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" # Display dropped nodes.\n",
" dropped_nodes = 'Dropped nodes:'\n",
" dropped_nodes = \"Dropped nodes:\"\n",
" for node in range(num_nodes):\n",
" if node == 0:\n",
" continue\n",
" is_visited = sum([\n",
" solver.BooleanValue(visited_nodes[v][node])\n",
" for v in range(num_vehicles)\n",
" ])\n",
" is_visited = sum(\n",
" [solver.BooleanValue(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",
" dropped_nodes += f\" {node}({VISIT_VALUES[node]})\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
" total_distance = 0\n",
" total_value_collected = 0\n",
" for v in range(num_vehicles):\n",
" current_node = 0\n",
" plan_output = f'Route for vehicle {v}:\\n'\n",
" plan_output = f\"Route for vehicle {v}:\\n\"\n",
" route_distance = 0\n",
" value_collected = 0\n",
" route_is_finished = False\n",
" while not route_is_finished:\n",
" value_collected += VISIT_VALUES[current_node]\n",
" plan_output += f' {current_node} ->'\n",
" plan_output += f\" {current_node} ->\"\n",
" # find next node\n",
" for node in range(num_nodes):\n",
" if node == current_node:\n",
@@ -172,21 +174,21 @@
" if current_node == 0:\n",
" route_is_finished = True\n",
" 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\" {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",
" 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 Distance: {total_distance}m\")\n",
" print(f\"Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}\")\n",
"\n",
"\n",
"def prize_collecting_vrp():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" num_nodes = len(DISTANCE_MATRIX)\n",
" num_vehicles = 4\n",
" print(f'Num nodes = {num_nodes}')\n",
" print(f\"Num nodes = {num_nodes}\")\n",
"\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
@@ -203,8 +205,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.NewBoolVar(f\"{i} is visited\")\n",
" arcs.append((i, i, is_visited.Not()))\n",
"\n",
" obj_vars.append(is_visited)\n",
" obj_coeffs.append(VISIT_VALUES[i])\n",
@@ -214,8 +216,8 @@
" if i == j:\n",
" used_arcs[v][i, j] = is_visited.Not()\n",
" continue\n",
" arc_is_used = model.NewBoolVar(f'{j} follows {i}')\n",
" arcs.append([i, j, arc_is_used])\n",
" arc_is_used = model.NewBoolVar(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",
@@ -228,18 +230,20 @@
"\n",
" # limit the route distance\n",
" model.Add(\n",
" sum(used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]\n",
" sum(\n",
" used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]\n",
" for i in all_nodes\n",
" for j in all_nodes) <= MAX_DISTANCE)\n",
" for j in all_nodes\n",
" )\n",
" <= MAX_DISTANCE\n",
" )\n",
"\n",
" # Each node is visited at most once\n",
" for node in range(1, num_nodes):\n",
" model.AddAtMostOne(\n",
" [visited_nodes[v][node] for v in range(num_vehicles)])\n",
" model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)])\n",
"\n",
" # Maximize visited node values minus the travelled distance.\n",
" model.Maximize(\n",
" 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",
@@ -249,13 +253,12 @@
"\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",
" num_vehicles)\n",
" print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" prize_collecting_vrp()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"MaxFlow and MinCostFlow examples.\n"
]
},
@@ -89,7 +90,7 @@
"\n",
"def max_flow_api():\n",
" \"\"\"MaxFlow simple interface example.\"\"\"\n",
" print('MaxFlow on a simple network.')\n",
" print(\"MaxFlow on a simple network.\")\n",
" tails = [0, 0, 0, 0, 1, 2, 3, 3, 4]\n",
" heads = [1, 2, 3, 4, 3, 4, 4, 5, 5]\n",
" capacities = [5, 8, 5, 3, 4, 5, 6, 6, 4]\n",
@@ -98,52 +99,54 @@
" for i in range(0, len(tails)):\n",
" smf.add_arc_with_capacity(tails[i], heads[i], capacities[i])\n",
" if smf.solve(0, 5) == smf.OPTIMAL:\n",
" print('Total flow', smf.optimal_flow(), '/', expected_total_flow)\n",
" print(\"Total flow\", smf.optimal_flow(), \"/\", expected_total_flow)\n",
" for i in range(smf.num_arcs()):\n",
" print('From source %d to target %d: %d / %d' %\n",
" (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i)))\n",
" print('Source side min-cut:', smf.get_source_side_min_cut())\n",
" print('Sink side min-cut:', smf.get_sink_side_min_cut())\n",
" print(\n",
" \"From source %d to target %d: %d / %d\"\n",
" % (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i))\n",
" )\n",
" print(\"Source side min-cut:\", smf.get_source_side_min_cut())\n",
" print(\"Sink side min-cut:\", smf.get_sink_side_min_cut())\n",
" else:\n",
" print('There was an issue with the max flow input.')\n",
" print(\"There was an issue with the max flow input.\")\n",
"\n",
"\n",
"def min_cost_flow_api():\n",
" \"\"\"MinCostFlow simple interface example.\n",
"\n",
" Note that this example is actually a linear sum assignment example and will\n",
" be more efficiently solved with the pywrapgraph.LinearSumAssignment class.\n",
" \"\"\"\n",
" print('MinCostFlow on 4x4 matrix.')\n",
" Note that this example is actually a linear sum assignment example and will\n",
" be more efficiently solved with the pywrapgraph.LinearSumAssignment class.\n",
" \"\"\"\n",
" print(\"MinCostFlow on 4x4 matrix.\")\n",
" num_sources = 4\n",
" num_targets = 4\n",
" costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],\n",
" [45, 110, 95, 115]]\n",
" costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n",
" expected_cost = 275\n",
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
" for source in range(0, num_sources):\n",
" for target in range(0, num_targets):\n",
" smcf.add_arc_with_capacity_and_unit_cost(source,\n",
" num_sources + target, 1,\n",
" costs[source][target])\n",
" smcf.add_arc_with_capacity_and_unit_cost(\n",
" source, num_sources + target, 1, costs[source][target]\n",
" )\n",
" for node in range(0, num_sources):\n",
" smcf.set_node_supply(node, 1)\n",
" smcf.set_node_supply(num_sources + node, -1)\n",
" status = smcf.solve()\n",
" if status == smcf.OPTIMAL:\n",
" print('Total flow', smcf.optimal_cost(), '/', expected_cost)\n",
" print(\"Total flow\", smcf.optimal_cost(), \"/\", expected_cost)\n",
" for i in range(0, smcf.num_arcs()):\n",
" if smcf.flow(i) > 0:\n",
" print('From source %d to target %d: cost %d' %\n",
" (smcf.tail(i), smcf.head(i) - num_sources,\n",
" smcf.unit_cost(i)))\n",
" print(\n",
" \"From source %d to target %d: cost %d\"\n",
" % (smcf.tail(i), smcf.head(i) - num_sources, smcf.unit_cost(i))\n",
" )\n",
" else:\n",
" print('There was an issue with the min cost flow input.')\n",
" print(\"There was an issue with the min cost flow input.\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" max_flow_api()\n",
" min_cost_flow_api()\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves a Qubo program using the CP-SAT solver.\n"
]
},
@@ -86,6 +87,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"RAW_DATA = [\n",
" # fmt:off\n",
" [\n",
" 0, 0, 49.774821, -59.5968886, -46.0773896, 0, -65.166109, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 47.0957778, 15.259961, -98.7983264, 0, 0, 0, -20.7757184,\n",
@@ -714,6 +716,7 @@
" 0, 84.8495464, 0, 0, 0, 0, 0, 0, 33.0825986, 46.995148, 0, 0, 0,\n",
" -4.243203, 0, 0, -24.0124188\n",
" ]\n",
" # fmt:on\n",
"]\n",
"\n",
"\n",
@@ -725,7 +728,7 @@
"\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.NewBoolVar(\"x_%i\" % i) for i in all_vars]\n",
"\n",
" obj_vars = []\n",
" obj_coeffs = []\n",
@@ -737,7 +740,7 @@
" if coeff == 0.0:\n",
" continue\n",
" x_j = variables[j]\n",
" var = model.NewBoolVar('')\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",
@@ -750,8 +753,7 @@
" obj_vars.append(variables[i])\n",
" obj_coeffs.append(self_coeff)\n",
"\n",
" model.Minimize(\n",
" 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",
@@ -763,7 +765,7 @@
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" solve_qubo()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Sat based solver for the RCPSP problems (see rcpsp.proto).\n",
"\n",
"Introduction to the problem:\n",
@@ -94,39 +95,47 @@
"\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"from ortools.scheduling import pywraprcpsp\n",
"from ortools.scheduling import rcpsp_pb2\n",
"from ortools.scheduling.python import rcpsp\n",
"\n",
"class FLAGS: pass\n",
"\n",
"_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.')\n",
"_INPUT = flags.DEFINE_string(\"input\", \"\", \"Input file to parse and solve.\")\n",
"_OUTPUT_PROTO = flags.DEFINE_string(\n",
" 'output_proto', '', 'Output file to write the cp_model proto to.')\n",
"_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\"params\", \"\", \"Sat solver parameters.\")\n",
"_USE_INTERVAL_MAKESPAN = flags.DEFINE_bool(\n",
" 'use_interval_makespan', True,\n",
" 'Whether we encode the makespan using an interval or not.')\n",
"_HORIZON = flags.DEFINE_integer('horizon', -1, 'Force horizon.')\n",
" \"use_interval_makespan\",\n",
" False,\n",
" \"Whether we encode the makespan using an interval or not.\",\n",
")\n",
"_HORIZON = flags.DEFINE_integer(\"horizon\", -1, \"Force horizon.\")\n",
"_ADD_REDUNDANT_ENERGETIC_CONSTRAINTS = flags.DEFINE_bool(\n",
" 'add_redundant_energetic_constraints', False,\n",
" 'Add redundant energetic constraints on the pairs of tasks extracted from' +\n",
" ' precedence graph.')\n",
" \"add_redundant_energetic_constraints\",\n",
" False,\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', 0.0,\n",
" 'Time limit when computing min delay between tasks.' +\n",
" ' A non-positive time limit disable min delays computation.')\n",
" \"delay_time_limit\",\n",
" 0.0,\n",
" \"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",
" 'preemptive_lb_time_limit', 0.0,\n",
" 'Time limit when computing a preemptive schedule lower bound.' +\n",
" ' A non-positive time limit disable this computation.')\n",
" \"preemptive_lb_time_limit\",\n",
" 0.0,\n",
" \"Time limit when computing a preemptive schedule lower bound.\"\n",
" + \" A non-positive time limit disable this computation.\",\n",
")\n",
"\n",
"\n",
"def PrintProblemStatistics(problem):\n",
" \"\"\"Display various statistics on the problem.\"\"\"\n",
"\n",
" # Determine problem type.\n",
" problem_type = ('Resource Investment Problem'\n",
" if problem.is_resource_investment else 'RCPSP')\n",
" problem_type = (\n",
" \"Resource Investment Problem\" if problem.is_resource_investment else \"RCPSP\"\n",
" )\n",
"\n",
" num_resources = len(problem.resources)\n",
" num_tasks = len(problem.tasks) - 2 # 2 sentinels.\n",
@@ -146,43 +155,39 @@
" tasks_with_delay += 1\n",
"\n",
" if problem.is_rcpsp_max:\n",
" problem_type += '/Max delay'\n",
" problem_type += \"/Max delay\"\n",
" # We print 2 less tasks as these are sentinel tasks that are not counted in\n",
" # the description of the rcpsp models.\n",
" if problem.is_consumer_producer:\n",
" print(f'Solving {problem_type} with:')\n",
" print(f' - {num_resources} reservoir resources')\n",
" print(f' - {num_tasks} tasks')\n",
" print(f\"Solving {problem_type} with:\")\n",
" print(f\" - {num_resources} reservoir resources\")\n",
" print(f\" - {num_tasks} tasks\")\n",
" else:\n",
" print(f'Solving {problem_type} with:')\n",
" print(f' - {num_resources} renewable resources')\n",
" print(f' - {num_tasks} tasks')\n",
" print(f\"Solving {problem_type} with:\")\n",
" print(f\" - {num_resources} renewable resources\")\n",
" print(f\" - {num_tasks} tasks\")\n",
" if tasks_with_alternatives:\n",
" print(\n",
" f' - {tasks_with_alternatives} tasks with alternative resources'\n",
" )\n",
" print(f\" - {tasks_with_alternatives} tasks with alternative resources\")\n",
" if variable_duration_tasks:\n",
" print(\n",
" f' - {variable_duration_tasks} tasks with variable durations'\n",
" )\n",
" print(f\" - {variable_duration_tasks} tasks with variable durations\")\n",
" if tasks_with_delay:\n",
" print(f' - {tasks_with_delay} tasks with successor delays')\n",
" print(f\" - {tasks_with_delay} tasks with successor delays\")\n",
"\n",
"\n",
"def AnalyseDependencyGraph(problem):\n",
" \"\"\"Analyses the dependency graph to improve the model.\n",
"\n",
" Args:\n",
" problem: the protobuf of the problem to solve.\n",
" Args:\n",
" problem: the protobuf of the problem to solve.\n",
"\n",
" Returns:\n",
" a list of (task1, task2, in_between_tasks) with task2 and indirect successor\n",
" of task1, and in_between_tasks being the list of all tasks after task1 and\n",
" before task2.\n",
" \"\"\"\n",
" Returns:\n",
" a list of (task1, task2, in_between_tasks) with task2 and indirect successor\n",
" of task1, and in_between_tasks being the list of all tasks after task1 and\n",
" before task2.\n",
" \"\"\"\n",
"\n",
" num_nodes = len(problem.tasks)\n",
" print(f'Analysing the dependency graph over {num_nodes} nodes')\n",
" print(f\"Analysing the dependency graph over {num_nodes} nodes\")\n",
"\n",
" ins = collections.defaultdict(list)\n",
" outs = collections.defaultdict(list)\n",
@@ -239,49 +244,50 @@
"\n",
" # Sort entries lexicographically by (len(common), source, sink)\n",
" def Price(entry):\n",
" return (num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] +\n",
" entry[1])\n",
" return num_nodes * num_nodes * len(entry[2]) + num_nodes * entry[0] + entry[1]\n",
"\n",
" result.sort(key=Price)\n",
" print(f' - created {len(result)} pairs of nodes to examine', flush=True)\n",
" print(f\" - created {len(result)} pairs of nodes to examine\", flush=True)\n",
" return result, after\n",
"\n",
"\n",
"def SolveRcpsp(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",
"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",
" \"\"\"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",
" ignore all others.\n",
" The model will only look at the tasks {source} + {sink} + active_tasks, and\n",
" ignore all others.\n",
"\n",
" Args:\n",
" problem: the description of the model to solve in protobuf format\n",
" proto_file: the name of the file to export the CpModel proto to.\n",
" params: the string representation of the parameters to pass to the sat\n",
" solver.\n",
" active_tasks: the set of active tasks to consider.\n",
" source: the source task in the graph. Its end will be forced to 0.\n",
" sink: the sink task of the graph. Its start is the makespan of the problem.\n",
" intervals_of_tasks: a heuristic lists of (task1, task2, tasks) used to add\n",
" redundant energetic equations to the model.\n",
" delays: a list of (task1, task2, min_delays) used to add extended precedence\n",
" constraints (start(task2) >= end(task1) + min_delay).\n",
" in_main_solve: indicates if this is the main solve procedure.\n",
" initial_solution: A valid assignment used to hint the search.\n",
" lower_bound: A valid lower bound of the makespan objective.\n",
" Args:\n",
" problem: the description of the model to solve in protobuf format\n",
" proto_file: the name of the file to export the CpModel proto to.\n",
" params: the string representation of the parameters to pass to the sat\n",
" solver.\n",
" active_tasks: the set of active tasks to consider.\n",
" source: the source task in the graph. Its end will be forced to 0.\n",
" sink: the sink task of the graph. Its start is the makespan of the problem.\n",
" intervals_of_tasks: a heuristic lists of (task1, task2, tasks) used to add\n",
" redundant energetic equations to the model.\n",
" delays: a list of (task1, task2, min_delays) used to add extended precedence\n",
" constraints (start(task2) >= end(task1) + min_delay).\n",
" in_main_solve: indicates if this is the main solve procedure.\n",
" initial_solution: A valid assignment used to hint the search.\n",
" lower_bound: A valid lower bound of the makespan objective.\n",
"\n",
" Returns:\n",
" (lower_bound of the objective, best solution found, asssignment)\n",
" \"\"\"\n",
" Returns:\n",
" (lower_bound of the objective, best solution found, assignment)\n",
" \"\"\"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
" model.SetName(problem.name)\n",
@@ -306,7 +312,7 @@
" for d in rd.min_delays:\n",
" horizon += abs(d)\n",
" if in_main_solve:\n",
" print(f'Horizon = {horizon}', flush=True)\n",
" print(f\"Horizon = {horizon}\", flush=True)\n",
"\n",
" # Containers.\n",
" task_starts = {}\n",
@@ -329,15 +335,13 @@
" 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.NewIntVar(0, horizon, f\"start_of_task_{t}\")\n",
" end_var = model.NewIntVar(0, horizon, f\"end_of_task_{t}\")\n",
"\n",
" literals = []\n",
" if num_recipes > 1:\n",
" # Create one literal per recipe.\n",
" literals = [\n",
" model.NewBoolVar(f'is_present_{t}_{r}') for r in all_recipes\n",
" ]\n",
" literals = [model.NewBoolVar(f\"is_present_{t}_{r}\") for r in all_recipes]\n",
"\n",
" # Exactly one recipe must be performed.\n",
" model.AddExactlyOne(literals)\n",
@@ -357,17 +361,19 @@
" # Create the duration variable from the accumulated durations.\n",
" duration_var = model.NewIntVarFromDomain(\n",
" cp_model.Domain.FromValues(task_to_recipe_durations[t]),\n",
" f'duration_of_task_{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(\n",
" duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf(\n",
" literals[r])\n",
" model.Add(duration_var == task_to_recipe_durations[t][r]).OnlyEnforceIf(\n",
" literals[r]\n",
" )\n",
"\n",
" # Create the interval of the task.\n",
" task_interval = model.NewIntervalVar(start_var, duration_var, end_var,\n",
" f'task_interval_{t}')\n",
" task_interval = model.NewIntervalVar(\n",
" start_var, duration_var, end_var, f\"task_interval_{t}\"\n",
" )\n",
"\n",
" # Store task variables.\n",
" task_starts[t] = start_var\n",
@@ -381,33 +387,38 @@
" 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",
" cp_model.Domain.FromValues(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",
" literals[r])\n",
" literals[r]\n",
" )\n",
"\n",
" resource_to_sum_of_demand_max[res] += max(demands)\n",
"\n",
" # Create the energy expression for (task, resource):\n",
" for res in all_resources:\n",
" task_resource_to_energy[(t, res)] = sum(\n",
" literals[r] * task_to_recipe_durations[t][r] *\n",
" task_resource_to_fixed_demands[(t, res)][r]\n",
" for r in all_recipes)\n",
" literals[r]\n",
" * task_to_recipe_durations[t][r]\n",
" * task_resource_to_fixed_demands[(t, res)][r]\n",
" for r in all_recipes\n",
" )\n",
" task_resource_to_max_energy[(t, res)] = max(\n",
" task_to_recipe_durations[t][r] *\n",
" task_resource_to_fixed_demands[(t, res)][r]\n",
" for r in all_recipes)\n",
" task_to_recipe_durations[t][r]\n",
" * task_resource_to_fixed_demands[(t, res)][r]\n",
" for r in all_recipes\n",
" )\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(makespan, makespan_size,\n",
" model.NewConstant(horizon + 1),\n",
" 'interval_makespan')\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",
" )\n",
"\n",
" # Add precedences.\n",
" if problem.is_rcpsp_max:\n",
@@ -417,8 +428,7 @@
" task = problem.tasks[task_id]\n",
" num_modes = len(task.recipes)\n",
"\n",
" for successor_index in range(len(task.successors)):\n",
" next_id = task.successors[successor_index]\n",
" for successor_index, next_id in enumerate(task.successors):\n",
" delay_matrix = task.successor_delays[successor_index]\n",
" num_next_modes = len(problem.tasks[next_id].recipes)\n",
" for m1 in range(num_modes):\n",
@@ -429,8 +439,7 @@
" model.Add(s1 + delay <= makespan).OnlyEnforceIf(p1)\n",
" else:\n",
" for m2 in range(num_next_modes):\n",
" delay = delay_matrix.recipe_delays[m1].min_delays[\n",
" m2]\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",
@@ -452,18 +461,16 @@
" resource = problem.resources[res]\n",
" c = resource.max_capacity\n",
" if c == -1:\n",
" print(f'No capacity: {resource}')\n",
" print(f\"No capacity: {resource}\")\n",
" c = resource_to_sum_of_demand_max[res]\n",
"\n",
" # RIP problems have only renewable resources, and no makespan.\n",
" if problem.is_resource_investment or resource.renewable:\n",
" intervals = [task_intervals[t] for t in all_active_tasks]\n",
" demands = [\n",
" task_to_resource_demands[t][res] for t in all_active_tasks\n",
" ]\n",
" 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",
" capacity = model.NewIntVar(0, c, f\"capacity_of_{res}\")\n",
" model.AddCumulative(intervals, demands, capacity)\n",
" capacities.append(capacity)\n",
" max_cost += c * resource.unit_cost\n",
@@ -481,24 +488,32 @@
" if task_resource_to_fixed_demands[(t, res)][0]:\n",
" reservoir_starts.append(task_starts[t])\n",
" reservoir_demands.append(\n",
" task_resource_to_fixed_demands[(t, res)][0])\n",
" model.AddReservoirConstraint(reservoir_starts,\n",
" reservoir_demands,\n",
" resource.min_capacity,\n",
" resource.max_capacity)\n",
" task_resource_to_fixed_demands[(t, res)][0]\n",
" )\n",
" model.AddReservoirConstraint(\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",
" task_to_resource_demands[t][res]\n",
" for t in all_active_tasks\n",
" ]) <= c)\n",
" cp_model.LinearExpr.Sum(\n",
" [task_to_resource_demands[t][res] for t in all_active_tasks]\n",
" )\n",
" <= c\n",
" )\n",
"\n",
" # Objective.\n",
" if problem.is_resource_investment:\n",
" objective = model.NewIntVar(0, max_cost, 'capacity_costs')\n",
" model.Add(objective == sum(problem.resources[i].unit_cost *\n",
" capacities[i]\n",
" for i in range(len(capacities))))\n",
" objective = model.NewIntVar(0, max_cost, \"capacity_costs\")\n",
" model.Add(\n",
" objective\n",
" == sum(\n",
" problem.resources[i].unit_cost * capacities[i]\n",
" for i in range(len(capacities))\n",
" )\n",
" )\n",
" else:\n",
" objective = makespan\n",
"\n",
@@ -512,8 +527,7 @@
" elif local_start in active_tasks and local_end == sink:\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] +\n",
" 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",
@@ -533,10 +547,13 @@
" # graph, it add the energetic relaxation:\n",
" # (start_var('end') - end_var('start')) * capacity_max >=\n",
" # sum of linearized energies of all tasks from 'in_between_tasks'\n",
" if (not problem.is_resource_investment and\n",
" not problem.is_consumer_producer and\n",
" _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS.value and in_main_solve and\n",
" not problem_is_single_mode):\n",
" if (\n",
" not problem.is_resource_investment\n",
" and not problem.is_consumer_producer\n",
" and _ADD_REDUNDANT_ENERGETIC_CONSTRAINTS.value\n",
" and in_main_solve\n",
" and not problem_is_single_mode\n",
" ):\n",
" added_constraints = 0\n",
" ignored_constraits = 0\n",
" for local_start, local_end, common in intervals_of_tasks:\n",
@@ -548,19 +565,21 @@
" if delays and (local_start, local_end) in delays:\n",
" min_delay, _ = delays[local_start, local_end]\n",
" sum_of_max_energies = sum(\n",
" task_resource_to_max_energy[(t, res)] for t in common)\n",
" task_resource_to_max_energy[(t, res)] for t in common\n",
" )\n",
" if sum_of_max_energies <= c * min_delay:\n",
" ignored_constraits += 1\n",
" continue\n",
" model.Add(\n",
" c *\n",
" (task_starts[local_end] - task_ends[local_start]) >= sum(\n",
" task_resource_to_energy[(t, res)] for t in common))\n",
" c * (task_starts[local_end] - task_ends[local_start])\n",
" >= sum(task_resource_to_energy[(t, res)] for t in common)\n",
" )\n",
" added_constraints += 1\n",
" print(\n",
" f'Added {added_constraints} redundant energetic constraints, and ' +\n",
" f'ignored {ignored_constraits} constraints.',\n",
" flush=True)\n",
" f\"Added {added_constraints} redundant energetic constraints, and \"\n",
" + f\"ignored {ignored_constraits} constraints.\",\n",
" flush=True,\n",
" )\n",
"\n",
" # Add solution hint.\n",
" if initial_solution:\n",
@@ -572,19 +591,25 @@
"\n",
" # Write model to file.\n",
" if proto_file:\n",
" print(f'Writing proto to{proto_file}')\n",
" print(f\"Writing proto to{proto_file}\")\n",
" model.ExportToFile(proto_file)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" if not _USE_INTERVAL_MAKESPAN.value:\n",
" solver.parameters.exploit_all_precedences = True\n",
" solver.parameters.use_hard_precedences_in_cumulative = True\n",
" if params:\n",
" text_format.Parse(params, solver.parameters)\n",
" if solver.parameters.num_workers >= 16 and solver.parameters.num_workers < 24:\n",
" solver.parameters.ignore_subsolvers.append(\"objective_lb_search\")\n",
" solver.parameters.extra_subsolvers.append(\"objective_shaving_search\")\n",
" if in_main_solve:\n",
" solver.parameters.log_search_progress = True\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 range(len(problem.tasks)):\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",
" for r in range(len(task_to_presence_literals[t])):\n",
@@ -594,25 +619,32 @@
" else: # t is not an active task.\n",
" assignment.start_of_task.append(0)\n",
" assignment.selected_recipe_of_task.append(0)\n",
" return (int(solver.BestObjectiveBound()), int(solver.ObjectiveValue()),\n",
" assignment)\n",
" return (\n",
" int(solver.BestObjectiveBound()),\n",
" int(solver.ObjectiveValue()),\n",
" assignment,\n",
" )\n",
" return -1, -1, None\n",
"\n",
"\n",
"def ComputeDelaysBetweenNodes(problem, task_intervals):\n",
" \"\"\"Computes the min delays between all pairs of tasks in 'task_intervals'.\n",
"\n",
" Args:\n",
" problem: The protobuf of the model.\n",
" task_intervals: The output of the AnalysePrecedenceGraph().\n",
" Args:\n",
" problem: The protobuf of the model.\n",
" task_intervals: The output of the AnalysePrecedenceGraph().\n",
"\n",
" Returns:\n",
" a list of (task1, task2, min_delay_between_task1_and_task2)\n",
" \"\"\"\n",
" print('Computing the minimum delay between pairs of intervals')\n",
" Returns:\n",
" a list of (task1, task2, min_delay_between_task1_and_task2)\n",
" \"\"\"\n",
" print(\"Computing the minimum delay between pairs of intervals\")\n",
" delays = {}\n",
" if (problem.is_resource_investment or problem.is_consumer_producer or\n",
" problem.is_rcpsp_max or _DELAY_TIME_LIMIT.value <= 0.0):\n",
" if (\n",
" problem.is_resource_investment\n",
" or problem.is_consumer_producer\n",
" or problem.is_rcpsp_max\n",
" or _DELAY_TIME_LIMIT.value <= 0.0\n",
" ):\n",
" return delays, None, False\n",
"\n",
" complete_problem_assignment = None\n",
@@ -621,9 +653,15 @@
" optimal_found = True\n",
" for start_task, end_task, active_tasks in task_intervals:\n",
" min_delay, feasible_delay, assignment = SolveRcpsp(\n",
" problem, '',\n",
" f'num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}',\n",
" active_tasks, start_task, end_task, [], delays)\n",
" problem,\n",
" \"\",\n",
" f\"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}\",\n",
" active_tasks,\n",
" start_task,\n",
" end_task,\n",
" [],\n",
" delays,\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",
@@ -636,9 +674,9 @@
" num_delays_not_found += 1\n",
" optimal_found = False\n",
"\n",
" print(f' - #optimal delays = {num_optimal_delays}', flush=True)\n",
" print(f\" - #optimal delays = {num_optimal_delays}\", flush=True)\n",
" if num_delays_not_found:\n",
" print(f' - #not computed delays = {num_delays_not_found}', flush=True)\n",
" print(f\" - #not computed delays = {num_delays_not_found}\", flush=True)\n",
"\n",
" return delays, complete_problem_assignment, optimal_found\n",
"\n",
@@ -654,8 +692,10 @@
" resource = problem.resources[res]\n",
" if not resource.renewable:\n",
" continue\n",
" if (sum(demand_map[(t, res)] for t in current) +\n",
" demand_map[(candidate, res)] > resource.max_capacity):\n",
" if (\n",
" sum(demand_map[(t, res)] for t in current) + demand_map[(candidate, res)]\n",
" > resource.max_capacity\n",
" ):\n",
" return False\n",
"\n",
" return True\n",
@@ -664,23 +704,26 @@
"def ComputePreemptiveLowerBound(problem, after, lower_bound):\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",
" Then it will try to assign all of them in a minimum number of configurations.\n",
" This is a standard complete set covering using column generation approach\n",
" where each column is a possible combination of itervals of size one.\n",
" For this, it breaks all intervals into a set of intervals of size one.\n",
" Then it will try to assign all of them in a minimum number of configurations.\n",
" This is a standard complete set covering using column generation approach\n",
" where each column is a possible combination of itervals of size one.\n",
"\n",
" Args:\n",
" problem: The probuf of the model.\n",
" after: a task to list of task dict that contains all tasks after a given\n",
" task.\n",
" lower_bound: A valid lower bound of the problem. It can be 0.\n",
" Args:\n",
" problem: The probuf of the model.\n",
" after: a task to list of task dict that contains all tasks after a given\n",
" task.\n",
" lower_bound: A valid lower bound of the problem. It can be 0.\n",
"\n",
" Returns:\n",
" a valid lower bound of the problem.\n",
" \"\"\"\n",
" Returns:\n",
" a valid lower bound of the problem.\n",
" \"\"\"\n",
" # Check this is a single mode problem.\n",
" if (problem.is_rcpsp_max or problem.is_resource_investment or\n",
" problem.is_consumer_producer):\n",
" if (\n",
" problem.is_rcpsp_max\n",
" or problem.is_resource_investment\n",
" or problem.is_consumer_producer\n",
" ):\n",
" return lower_bound\n",
"\n",
" demand_map = collections.defaultdict(int)\n",
@@ -700,9 +743,11 @@
" max_duration = max(max_duration, recipe.duration)\n",
" sum_of_demands += demand\n",
"\n",
" print(f'Compute a bin-packing lower bound with {len(all_active_tasks)}' +\n",
" ' active tasks',\n",
" flush=True)\n",
" print(\n",
" f\"Compute a bin-packing lower bound with {len(all_active_tasks)}\"\n",
" + \" active tasks\",\n",
" flush=True,\n",
" )\n",
" all_combinations = []\n",
"\n",
" for t in all_active_tasks:\n",
@@ -714,7 +759,7 @@
"\n",
" all_combinations.extend(new_combinations)\n",
"\n",
" print(f' - created {len(all_combinations)} combinations')\n",
" print(f\" - created {len(all_combinations)} combinations\")\n",
" if len(all_combinations) > 5000000:\n",
" return lower_bound # Abort if too large.\n",
"\n",
@@ -725,7 +770,7 @@
" # 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.SetName(f\"lower_bound_{problem.name}\")\n",
"\n",
" vars_per_task = collections.defaultdict(list)\n",
" all_vars = []\n",
@@ -733,7 +778,7 @@
" 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_{t}')\n",
" count = model.NewIntVar(0, min_duration, f\"count_{c}\")\n",
" all_vars.append(count)\n",
" for t in c:\n",
" vars_per_task[t].append(count)\n",
@@ -743,8 +788,7 @@
" model.Add(sum(vars_per_task[t]) >= duration_map[t])\n",
"\n",
" # Objective\n",
" objective_var = model.NewIntVar(lower_bound, sum_of_demands,\n",
" 'objective_var')\n",
" objective_var = model.NewIntVar(lower_bound, sum_of_demands, \"objective_var\")\n",
" model.Add(objective_var == sum(all_vars))\n",
"\n",
" model.Minimize(objective_var)\n",
@@ -755,24 +799,24 @@
" solver.parameters.max_time_in_seconds = _PREEMPTIVE_LB_TIME_LIMIT.value\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",
" status_str = \"optimal\" if status == cp_model.OPTIMAL else \"\"\n",
" lower_bound = max(lower_bound, int(solver.BestObjectiveBound()))\n",
" print(f' - {status_str} static lower bound = {lower_bound}',\n",
" flush=True)\n",
" print(f\" - {status_str} static lower bound = {lower_bound}\", flush=True)\n",
"\n",
" return lower_bound\n",
"\n",
"\n",
"def main(_):\n",
" rcpsp_parser = pywraprcpsp.RcpspParser()\n",
" rcpsp_parser.ParseFile(_INPUT.value)\n",
" rcpsp_parser = rcpsp.RcpspParser()\n",
" rcpsp_parser.parse_file(_INPUT.value)\n",
"\n",
" problem = rcpsp_parser.Problem()\n",
" problem = rcpsp_parser.problem()\n",
" PrintProblemStatistics(problem)\n",
"\n",
" intervals_of_tasks, after = AnalyseDependencyGraph(problem)\n",
" delays, initial_solution, optimal_found = ComputeDelaysBetweenNodes(\n",
" problem, intervals_of_tasks)\n",
" problem, intervals_of_tasks\n",
" )\n",
"\n",
" last_task = len(problem.tasks) - 1\n",
" key = (0, last_task)\n",
@@ -780,17 +824,19 @@
" if not optimal_found and _PREEMPTIVE_LB_TIME_LIMIT.value > 0.0:\n",
" lower_bound = ComputePreemptiveLowerBound(problem, after, lower_bound)\n",
"\n",
" SolveRcpsp(problem=problem,\n",
" proto_file=_OUTPUT_PROTO.value,\n",
" params=_PARAMS.value,\n",
" active_tasks=set(range(1, last_task)),\n",
" source=0,\n",
" sink=last_task,\n",
" intervals_of_tasks=intervals_of_tasks,\n",
" delays=delays,\n",
" in_main_solve=True,\n",
" initial_solution=initial_solution,\n",
" lower_bound=lower_bound)\n",
" SolveRcpsp(\n",
" problem=problem,\n",
" proto_file=_OUTPUT_PROTO.value,\n",
" params=_PARAMS.value,\n",
" active_tasks=set(range(1, last_task)),\n",
" source=0,\n",
" sink=last_task,\n",
" intervals_of_tasks=intervals_of_tasks,\n",
" delays=delays,\n",
" in_main_solve=True,\n",
" initial_solution=initial_solution,\n",
" lower_bound=lower_bound,\n",
" )\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Creates a shift scheduling problem and solves it.\n"
]
},
@@ -86,28 +87,30 @@
"from google.protobuf import text_format\n",
"\n",
"_OUTPUT_PROTO = flags.DEFINE_string(\n",
" 'output_proto', '', 'Output file to write the cp_model proto to.')\n",
"_PARAMS = flags.DEFINE_string('params', 'max_time_in_seconds:10.0',\n",
" 'Sat solver parameters.')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\", \"max_time_in_seconds:10.0\", \"Sat solver parameters.\"\n",
")\n",
"\n",
"\n",
"def negated_bounded_span(works, start, length):\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",
" and if there is variables to the left/right of this span, surround the span by\n",
" them in non negated form.\n",
" Extract the span of Boolean variables [start, start + length), negate them,\n",
" and if there is variables to the left/right of this span, surround the span by\n",
" them in non negated form.\n",
"\n",
" Args:\n",
" works: a list of variables to extract the span from.\n",
" start: the start to the span.\n",
" length: the length of the span.\n",
" Args:\n",
" works: a list of variables to extract the span from.\n",
" start: the start to the span.\n",
" length: the length of the span.\n",
"\n",
" Returns:\n",
" a list of variables which conjunction will be false if the sub-list is\n",
" assigned to True, and correctly bounded by variables assigned to False,\n",
" or by the start or end of works.\n",
" \"\"\"\n",
" Returns:\n",
" a list of variables which conjunction will be false if the sub-list is\n",
" assigned to True, and correctly bounded by variables assigned to False,\n",
" or by the start or end of works.\n",
" \"\"\"\n",
" sequence = []\n",
" # Left border (start of works, or works[start - 1])\n",
" if start > 0:\n",
@@ -120,35 +123,36 @@
" return sequence\n",
"\n",
"\n",
"def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,\n",
" soft_max, hard_max, max_cost, prefix):\n",
"def add_soft_sequence_constraint(\n",
" model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix\n",
"):\n",
" \"\"\"Sequence constraint on true variables with soft and hard bounds.\n",
"\n",
" This constraint look at every maximal contiguous sequence of variables\n",
" assigned to true. If forbids sequence of length < hard_min or > hard_max.\n",
" Then it creates penalty terms if the length is < soft_min or > soft_max.\n",
" This constraint look at every maximal contiguous sequence of variables\n",
" assigned to true. If forbids sequence of length < hard_min or > hard_max.\n",
" Then it creates penalty terms if the length is < soft_min or > soft_max.\n",
"\n",
" Args:\n",
" model: the sequence constraint is built on this model.\n",
" works: a list of Boolean variables.\n",
" hard_min: any sequence of true variables must have a length of at least\n",
" hard_min.\n",
" soft_min: any sequence should have a length of at least soft_min, or a\n",
" linear penalty on the delta will be added to the objective.\n",
" min_cost: the coefficient of the linear penalty if the length is less than\n",
" soft_min.\n",
" soft_max: any sequence should have a length of at most soft_max, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" hard_max: any sequence of true variables must have a length of at most\n",
" hard_max.\n",
" max_cost: the coefficient of the linear penalty if the length is more than\n",
" soft_max.\n",
" prefix: a base name for penalty literals.\n",
" Args:\n",
" model: the sequence constraint is built on this model.\n",
" works: a list of Boolean variables.\n",
" hard_min: any sequence of true variables must have a length of at least\n",
" hard_min.\n",
" soft_min: any sequence should have a length of at least soft_min, or a\n",
" linear penalty on the delta will be added to the objective.\n",
" min_cost: the coefficient of the linear penalty if the length is less than\n",
" soft_min.\n",
" soft_max: any sequence should have a length of at most soft_max, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" hard_max: any sequence of true variables must have a length of at most\n",
" hard_max.\n",
" max_cost: the coefficient of the linear penalty if the length is more than\n",
" soft_max.\n",
" prefix: a base name for penalty literals.\n",
"\n",
" Returns:\n",
" a tuple (variables_list, coefficient_list) containing the different\n",
" penalties created by the sequence constraint.\n",
" \"\"\"\n",
" Returns:\n",
" a tuple (variables_list, coefficient_list) containing the different\n",
" penalties created by the sequence constraint.\n",
" \"\"\"\n",
" cost_literals = []\n",
" cost_coefficients = []\n",
"\n",
@@ -162,7 +166,7 @@
" for length in range(hard_min, soft_min):\n",
" 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",
" name = \": under_span(start=%i, length=%i)\" % (start, length)\n",
" lit = model.NewBoolVar(prefix + name)\n",
" span.append(lit)\n",
" model.AddBoolOr(span)\n",
@@ -176,7 +180,7 @@
" for length in range(soft_max + 1, hard_max + 1):\n",
" 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",
" name = \": over_span(start=%i, length=%i)\" % (start, length)\n",
" lit = model.NewBoolVar(prefix + name)\n",
" span.append(lit)\n",
" model.AddBoolOr(span)\n",
@@ -186,61 +190,61 @@
"\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(\n",
" [works[i].Not() for i in range(start, start + hard_max + 1)])\n",
" model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)])\n",
" return cost_literals, cost_coefficients\n",
"\n",
"\n",
"def add_soft_sum_constraint(model, works, hard_min, soft_min, min_cost,\n",
" soft_max, hard_max, max_cost, prefix):\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",
"\n",
" This constraint counts the variables assigned to true from works.\n",
" If forbids sum < hard_min or > hard_max.\n",
" Then it creates penalty terms if the sum is < soft_min or > soft_max.\n",
" This constraint counts the variables assigned to true from works.\n",
" If forbids sum < hard_min or > hard_max.\n",
" Then it creates penalty terms if the sum is < soft_min or > soft_max.\n",
"\n",
" Args:\n",
" model: the sequence constraint is built on this model.\n",
" works: a list of Boolean variables.\n",
" hard_min: any sequence of true variables must have a sum of at least\n",
" hard_min.\n",
" soft_min: any sequence should have a sum of at least soft_min, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" min_cost: the coefficient of the linear penalty if the sum is less than\n",
" soft_min.\n",
" soft_max: any sequence should have a sum of at most soft_max, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" hard_max: any sequence of true variables must have a sum of at most\n",
" hard_max.\n",
" max_cost: the coefficient of the linear penalty if the sum is more than\n",
" soft_max.\n",
" prefix: a base name for penalty variables.\n",
" Args:\n",
" model: the sequence constraint is built on this model.\n",
" works: a list of Boolean variables.\n",
" hard_min: any sequence of true variables must have a sum of at least\n",
" hard_min.\n",
" soft_min: any sequence should have a sum of at least soft_min, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" min_cost: the coefficient of the linear penalty if the sum is less than\n",
" soft_min.\n",
" soft_max: any sequence should have a sum of at most soft_max, or a linear\n",
" penalty on the delta will be added to the objective.\n",
" hard_max: any sequence of true variables must have a sum of at most\n",
" hard_max.\n",
" max_cost: the coefficient of the linear penalty if the sum is more than\n",
" soft_max.\n",
" prefix: a base name for penalty variables.\n",
"\n",
" Returns:\n",
" a tuple (variables_list, coefficient_list) containing the different\n",
" penalties created by the sequence constraint.\n",
" \"\"\"\n",
" Returns:\n",
" a tuple (variables_list, coefficient_list) containing the different\n",
" penalties created by the sequence constraint.\n",
" \"\"\"\n",
" cost_variables = []\n",
" cost_coefficients = []\n",
" sum_var = model.NewIntVar(hard_min, hard_max, '')\n",
" sum_var = model.NewIntVar(hard_min, hard_max, \"\")\n",
" # This adds the hard constraints on the sum.\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",
" delta = model.NewIntVar(-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",
" excess = model.NewIntVar(0, 7, prefix + \": under_sum\")\n",
" model.AddMaxEquality(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",
" delta = model.NewIntVar(-7, 7, \"\")\n",
" model.Add(delta == sum_var - soft_max)\n",
" excess = model.NewIntVar(0, 7, prefix + ': over_sum')\n",
" excess = model.NewIntVar(0, 7, prefix + \": over_sum\")\n",
" model.AddMaxEquality(excess, [delta, 0])\n",
" cost_variables.append(excess)\n",
" cost_coefficients.append(max_cost)\n",
@@ -253,7 +257,7 @@
" # Data\n",
" num_employees = 8\n",
" num_weeks = 3\n",
" shifts = ['O', 'M', 'A', 'N']\n",
" shifts = [\"O\", \"M\", \"A\", \"N\"]\n",
"\n",
" # Fixed assignment: (employee, shift, day).\n",
" # This fixes the first 2 days of the schedule.\n",
@@ -286,7 +290,7 @@
" (5, 3, 10, -2),\n",
" # Employee 2 does not want a night shift on the first Friday (positive\n",
" # weight).\n",
" (2, 3, 4, 4)\n",
" (2, 3, 4, 4),\n",
" ]\n",
"\n",
" # Shift constraints on continuous sequence :\n",
@@ -343,7 +347,7 @@
" 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.NewBoolVar(\"work%i_%i_%i\" % (e, s, d))\n",
"\n",
" # Linear terms of the objective in a minimization context.\n",
" obj_int_vars = []\n",
@@ -371,9 +375,16 @@
" for e in range(num_employees):\n",
" works = [work[e, shift, d] for d in range(num_days)]\n",
" variables, coeffs = add_soft_sequence_constraint(\n",
" model, works, hard_min, soft_min, min_cost, soft_max, hard_max,\n",
" model,\n",
" works,\n",
" hard_min,\n",
" soft_min,\n",
" min_cost,\n",
" soft_max,\n",
" hard_max,\n",
" max_cost,\n",
" 'shift_constraint(employee %i, shift %i)' % (e, shift))\n",
" \"shift_constraint(employee %i, shift %i)\" % (e, shift),\n",
" )\n",
" obj_bool_vars.extend(variables)\n",
" obj_bool_coeffs.extend(coeffs)\n",
"\n",
@@ -384,10 +395,17 @@
" for w in range(num_weeks):\n",
" works = [work[e, shift, d + w * 7] for d in range(7)]\n",
" variables, coeffs = add_soft_sum_constraint(\n",
" model, works, hard_min, soft_min, min_cost, soft_max,\n",
" hard_max, max_cost,\n",
" 'weekly_sum_constraint(employee %i, shift %i, week %i)' %\n",
" (e, shift, w))\n",
" model,\n",
" works,\n",
" hard_min,\n",
" soft_min,\n",
" min_cost,\n",
" soft_max,\n",
" hard_max,\n",
" max_cost,\n",
" \"weekly_sum_constraint(employee %i, shift %i, week %i)\"\n",
" % (e, shift, w),\n",
" )\n",
" obj_int_vars.extend(variables)\n",
" obj_int_coeffs.extend(coeffs)\n",
"\n",
@@ -396,14 +414,15 @@
" for e in range(num_employees):\n",
" for d in range(num_days - 1):\n",
" transition = [\n",
" work[e, previous_shift, d].Not(), work[e, next_shift,\n",
" d + 1].Not()\n",
" work[e, previous_shift, d].Not(),\n",
" work[e, next_shift, d + 1].Not(),\n",
" ]\n",
" if cost == 0:\n",
" model.AddBoolOr(transition)\n",
" else:\n",
" trans_var = model.NewBoolVar(\n",
" 'transition (employee=%i, day=%i)' % (e, d))\n",
" \"transition (employee=%i, day=%i)\" % (e, d)\n",
" )\n",
" transition.append(trans_var)\n",
" model.AddBoolOr(transition)\n",
" obj_bool_vars.append(trans_var)\n",
@@ -416,28 +435,25 @@
" 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",
" worked = model.NewIntVar(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,\n",
" d)\n",
" excess = model.NewIntVar(0, num_employees - min_demand,\n",
" name)\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",
" obj_int_vars.append(excess)\n",
" obj_int_coeffs.append(over_penalty)\n",
"\n",
" # Objective\n",
" model.Minimize(\n",
" sum(obj_bool_vars[i] * obj_bool_coeffs[i]\n",
" for i in range(len(obj_bool_vars))) +\n",
" sum(obj_int_vars[i] * obj_int_coeffs[i]\n",
" for i in range(len(obj_int_vars))))\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",
"\n",
" if output_proto:\n",
" print('Writing proto to %s' % output_proto)\n",
" with open(output_proto, 'w') as text_file:\n",
" print(\"Writing proto to %s\" % output_proto)\n",
" with open(output_proto, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" # Solve the model.\n",
@@ -450,38 +466,40 @@
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print()\n",
" header = ' '\n",
" header = \" \"\n",
" for w in range(num_weeks):\n",
" header += 'M T W T F S S '\n",
" header += \"M T W T F S S \"\n",
" print(header)\n",
" for e in range(num_employees):\n",
" schedule = ''\n",
" schedule = \"\"\n",
" for d in range(num_days):\n",
" for s in range(num_shifts):\n",
" if solver.BooleanValue(work[e, s, d]):\n",
" schedule += shifts[s] + ' '\n",
" print('worker %i: %s' % (e, schedule))\n",
" schedule += shifts[s] + \" \"\n",
" print(\"worker %i: %s\" % (e, schedule))\n",
" print()\n",
" print('Penalties:')\n",
" print(\"Penalties:\")\n",
" for i, var in enumerate(obj_bool_vars):\n",
" if solver.BooleanValue(var):\n",
" penalty = obj_bool_coeffs[i]\n",
" if penalty > 0:\n",
" print(' %s violated, penalty=%i' % (var.Name(), penalty))\n",
" print(\" %s violated, penalty=%i\" % (var.Name(), penalty))\n",
" else:\n",
" print(' %s fulfilled, gain=%i' % (var.Name(), -penalty))\n",
" print(\" %s fulfilled, gain=%i\" % (var.Name(), -penalty))\n",
"\n",
" for i, var in enumerate(obj_int_vars):\n",
" if solver.Value(var) > 0:\n",
" print(' %s violated by %i, linear penalty=%i' %\n",
" (var.Name(), solver.Value(var), obj_int_coeffs[i]))\n",
" print(\n",
" \" %s violated by %i, linear penalty=%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(\"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",
"\n",
"\n",
"def main(_):\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Single machine jobshop with setup times, release dates and due dates.\n"
]
},
@@ -86,19 +87,22 @@
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"#----------------------------------------------------------------------------\n",
"# ----------------------------------------------------------------------------\n",
"# Command line arguments.\n",
"_OUTPUT_PROTO = flags.DEFINE_string(\n",
" 'output_proto', '', 'Output file to write the cp_model proto to.')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" 'params',\n",
" 'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',\n",
" 'Sat solver parameters.')\n",
"_PREPROCESS = flags.DEFINE_bool('--preprocess_times', True,\n",
" 'Preprocess setup times and durations')\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"_PREPROCESS = flags.DEFINE_bool(\n",
" \"--preprocess_times\", True, \"Preprocess setup times and durations\"\n",
")\n",
"\n",
"\n",
"#----------------------------------------------------------------------------\n",
"# ----------------------------------------------------------------------------\n",
"# Intermediate solution printer\n",
"class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
@@ -109,8 +113,10 @@
"\n",
" def on_solution_callback(self):\n",
" \"\"\"Called after each new solution found.\"\"\"\n",
" print('Solution %i, time = %f s, objective = %i' %\n",
" (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
@@ -120,110 +126,343 @@
" parameters = _PARAMS.value\n",
" output_proto_file = _OUTPUT_PROTO.value\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Data.\n",
"\n",
" job_durations = [\n",
" 2546, 8589, 5953, 3710, 3630, 3016, 4148, 8706, 1604, 5502, 9983, 6209,\n",
" 9920, 7860, 2176\n",
" 2546,\n",
" 8589,\n",
" 5953,\n",
" 3710,\n",
" 3630,\n",
" 3016,\n",
" 4148,\n",
" 8706,\n",
" 1604,\n",
" 5502,\n",
" 9983,\n",
" 6209,\n",
" 9920,\n",
" 7860,\n",
" 2176,\n",
" ]\n",
"\n",
" setup_times = [\n",
" [\n",
" 3559, 1638, 2000, 3676, 2741, 2439, 2406, 1526, 1600, 3356, 4324,\n",
" 1923, 3663, 4103, 2215\n",
" 3559,\n",
" 1638,\n",
" 2000,\n",
" 3676,\n",
" 2741,\n",
" 2439,\n",
" 2406,\n",
" 1526,\n",
" 1600,\n",
" 3356,\n",
" 4324,\n",
" 1923,\n",
" 3663,\n",
" 4103,\n",
" 2215,\n",
" ],\n",
" [\n",
" 1442, 3010, 1641, 4490, 2060, 2143, 3376, 3891, 3513, 2855, 2653,\n",
" 1471, 2257, 1186, 2354\n",
" 1442,\n",
" 3010,\n",
" 1641,\n",
" 4490,\n",
" 2060,\n",
" 2143,\n",
" 3376,\n",
" 3891,\n",
" 3513,\n",
" 2855,\n",
" 2653,\n",
" 1471,\n",
" 2257,\n",
" 1186,\n",
" 2354,\n",
" ],\n",
" [\n",
" 1728, 3583, 3243, 4080, 2191, 3644, 4023, 3510, 2135, 1346, 1410,\n",
" 3565, 3181, 1126, 4169\n",
" 1728,\n",
" 3583,\n",
" 3243,\n",
" 4080,\n",
" 2191,\n",
" 3644,\n",
" 4023,\n",
" 3510,\n",
" 2135,\n",
" 1346,\n",
" 1410,\n",
" 3565,\n",
" 3181,\n",
" 1126,\n",
" 4169,\n",
" ],\n",
" [\n",
" 1291, 1703, 3103, 4001, 1712, 1137, 3341, 3485, 2557, 2435, 1972,\n",
" 1986, 1522, 4734, 2520\n",
" 1291,\n",
" 1703,\n",
" 3103,\n",
" 4001,\n",
" 1712,\n",
" 1137,\n",
" 3341,\n",
" 3485,\n",
" 2557,\n",
" 2435,\n",
" 1972,\n",
" 1986,\n",
" 1522,\n",
" 4734,\n",
" 2520,\n",
" ],\n",
" [\n",
" 4134, 2200, 1502, 3995, 1277, 1808, 1020, 2078, 2999, 1605, 1697,\n",
" 2323, 2268, 2288, 4856\n",
" 4134,\n",
" 2200,\n",
" 1502,\n",
" 3995,\n",
" 1277,\n",
" 1808,\n",
" 1020,\n",
" 2078,\n",
" 2999,\n",
" 1605,\n",
" 1697,\n",
" 2323,\n",
" 2268,\n",
" 2288,\n",
" 4856,\n",
" ],\n",
" [\n",
" 4974, 2480, 2492, 4088, 2587, 4652, 1478, 3942, 1222, 3305, 1206,\n",
" 1024, 2605, 3080, 3516\n",
" 4974,\n",
" 2480,\n",
" 2492,\n",
" 4088,\n",
" 2587,\n",
" 4652,\n",
" 1478,\n",
" 3942,\n",
" 1222,\n",
" 3305,\n",
" 1206,\n",
" 1024,\n",
" 2605,\n",
" 3080,\n",
" 3516,\n",
" ],\n",
" [\n",
" 1903, 2584, 2104, 1609, 4745, 2691, 1539, 2544, 2499, 2074, 4793,\n",
" 1756, 2190, 1298, 2605\n",
" 1903,\n",
" 2584,\n",
" 2104,\n",
" 1609,\n",
" 4745,\n",
" 2691,\n",
" 1539,\n",
" 2544,\n",
" 2499,\n",
" 2074,\n",
" 4793,\n",
" 1756,\n",
" 2190,\n",
" 1298,\n",
" 2605,\n",
" ],\n",
" [\n",
" 1407, 2536, 2296, 1769, 1449, 3386, 3046, 1180, 4132, 4783, 3386,\n",
" 3429, 2450, 3376, 3719\n",
" 1407,\n",
" 2536,\n",
" 2296,\n",
" 1769,\n",
" 1449,\n",
" 3386,\n",
" 3046,\n",
" 1180,\n",
" 4132,\n",
" 4783,\n",
" 3386,\n",
" 3429,\n",
" 2450,\n",
" 3376,\n",
" 3719,\n",
" ],\n",
" [\n",
" 3026, 1637, 3628, 3096, 1498, 4947, 1912, 3703, 4107, 4730, 1805,\n",
" 2189, 1789, 1985, 3586\n",
" 3026,\n",
" 1637,\n",
" 3628,\n",
" 3096,\n",
" 1498,\n",
" 4947,\n",
" 1912,\n",
" 3703,\n",
" 4107,\n",
" 4730,\n",
" 1805,\n",
" 2189,\n",
" 1789,\n",
" 1985,\n",
" 3586,\n",
" ],\n",
" [\n",
" 3940, 1342, 1601, 2737, 1748, 3771, 4052, 1619, 2558, 3782, 4383,\n",
" 3451, 4904, 1108, 1750\n",
" 3940,\n",
" 1342,\n",
" 1601,\n",
" 2737,\n",
" 1748,\n",
" 3771,\n",
" 4052,\n",
" 1619,\n",
" 2558,\n",
" 3782,\n",
" 4383,\n",
" 3451,\n",
" 4904,\n",
" 1108,\n",
" 1750,\n",
" ],\n",
" [\n",
" 1348, 3162, 1507, 3936, 1453, 2953, 4182, 2968, 3134, 1042, 3175,\n",
" 2805, 4901, 1735, 1654\n",
" 1348,\n",
" 3162,\n",
" 1507,\n",
" 3936,\n",
" 1453,\n",
" 2953,\n",
" 4182,\n",
" 2968,\n",
" 3134,\n",
" 1042,\n",
" 3175,\n",
" 2805,\n",
" 4901,\n",
" 1735,\n",
" 1654,\n",
" ],\n",
" [\n",
" 1099, 1711, 1245, 1067, 4343, 3407, 1108, 1784, 4803, 2342, 3377,\n",
" 2037, 3563, 1621, 2840\n",
" 1099,\n",
" 1711,\n",
" 1245,\n",
" 1067,\n",
" 4343,\n",
" 3407,\n",
" 1108,\n",
" 1784,\n",
" 4803,\n",
" 2342,\n",
" 3377,\n",
" 2037,\n",
" 3563,\n",
" 1621,\n",
" 2840,\n",
" ],\n",
" [\n",
" 2573, 4222, 3164, 2563, 3231, 4731, 2395, 1033, 4795, 3288, 2335,\n",
" 4935, 4066, 1440, 4979\n",
" 2573,\n",
" 4222,\n",
" 3164,\n",
" 2563,\n",
" 3231,\n",
" 4731,\n",
" 2395,\n",
" 1033,\n",
" 4795,\n",
" 3288,\n",
" 2335,\n",
" 4935,\n",
" 4066,\n",
" 1440,\n",
" 4979,\n",
" ],\n",
" [\n",
" 3321, 1666, 3573, 2377, 4649, 4600, 1065, 2475, 3658, 3374, 1138,\n",
" 4367, 4728, 3032, 2198\n",
" 3321,\n",
" 1666,\n",
" 3573,\n",
" 2377,\n",
" 4649,\n",
" 4600,\n",
" 1065,\n",
" 2475,\n",
" 3658,\n",
" 3374,\n",
" 1138,\n",
" 4367,\n",
" 4728,\n",
" 3032,\n",
" 2198,\n",
" ],\n",
" [\n",
" 2986, 1180, 4095, 3132, 3987, 3880, 3526, 1460, 4885, 3827, 4945,\n",
" 4419, 3486, 3805, 3804\n",
" 2986,\n",
" 1180,\n",
" 4095,\n",
" 3132,\n",
" 3987,\n",
" 3880,\n",
" 3526,\n",
" 1460,\n",
" 4885,\n",
" 3827,\n",
" 4945,\n",
" 4419,\n",
" 3486,\n",
" 3805,\n",
" 3804,\n",
" ],\n",
" [\n",
" 4163, 3441, 1217, 2941, 1210, 3794, 1779, 1904, 4255, 4967, 4003,\n",
" 3873, 1002, 2055, 4295\n",
" 4163,\n",
" 3441,\n",
" 1217,\n",
" 2941,\n",
" 1210,\n",
" 3794,\n",
" 1779,\n",
" 1904,\n",
" 4255,\n",
" 4967,\n",
" 4003,\n",
" 3873,\n",
" 1002,\n",
" 2055,\n",
" 4295,\n",
" ],\n",
" ]\n",
"\n",
" due_dates = [\n",
" -1, -1, 28569, -1, 98104, 27644, 55274, 57364, -1, -1, 60875, 96637,\n",
" 77888, -1, -1\n",
" ]\n",
" release_dates = [\n",
" 0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0\n",
" -1,\n",
" -1,\n",
" 28569,\n",
" -1,\n",
" 98104,\n",
" 27644,\n",
" 55274,\n",
" 57364,\n",
" -1,\n",
" -1,\n",
" 60875,\n",
" 96637,\n",
" 77888,\n",
" -1,\n",
" -1,\n",
" ]\n",
" release_dates = [0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0]\n",
"\n",
" precedences = [(0, 2), (1, 2)]\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Helper data.\n",
" num_jobs = len(job_durations)\n",
" all_jobs = range(num_jobs)\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Preprocess.\n",
" if _PREPROCESS.value:\n",
" for job_id in all_jobs:\n",
" min_incoming_setup = min(\n",
" setup_times[j][job_id] for j in range(num_jobs + 1))\n",
" setup_times[j][job_id] for j in range(num_jobs + 1)\n",
" )\n",
" if release_dates[job_id] != 0:\n",
" min_incoming_setup = min(min_incoming_setup,\n",
" release_dates[job_id])\n",
" min_incoming_setup = min(min_incoming_setup, release_dates[job_id])\n",
" if min_incoming_setup == 0:\n",
" continue\n",
"\n",
" print('job %i has a min incoming setup of %i' %\n",
" (job_id, min_incoming_setup))\n",
" print(\n",
" \"job %i has a min incoming setup of %i\" % (job_id, min_incoming_setup)\n",
" )\n",
" # We can transfer some setup times to the duration of the job.\n",
" job_durations[job_id] += min_incoming_setup\n",
" # Decrease corresponding incoming setup times.\n",
@@ -233,36 +472,37 @@
" if release_dates[job_id] != 0:\n",
" release_dates[job_id] -= min_incoming_setup\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Compute a maximum makespan greedily.\n",
" horizon = sum(job_durations) + sum(\n",
" max(setup_times[i][j]\n",
" for i in range(num_jobs + 1))\n",
" for j in range(num_jobs))\n",
" print('Greedy horizon =', horizon)\n",
" max(setup_times[i][j] for i in range(num_jobs + 1)) for j in range(num_jobs)\n",
" )\n",
" print(\"Greedy horizon =\", horizon)\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Global storage of variables.\n",
" intervals = []\n",
" starts = []\n",
" ends = []\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Scan the jobs and create the relevant variables and intervals.\n",
" for job_id in all_jobs:\n",
" duration = job_durations[job_id]\n",
" release_date = release_dates[job_id]\n",
" due_date = due_dates[job_id] if due_dates[job_id] != -1 else horizon\n",
" print('job %2i: start = %5i, duration = %4i, end = %6i' %\n",
" (job_id, release_date, duration, due_date))\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",
" print(\n",
" \"job %2i: start = %5i, duration = %4i, end = %6i\"\n",
" % (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",
" starts.append(start)\n",
" ends.append(end)\n",
" intervals.append(interval)\n",
@@ -270,59 +510,61 @@
" # No overlap constraint.\n",
" model.AddNoOverlap(intervals)\n",
"\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",
" arcs.append([0, i + 1, start_lit])\n",
" start_lit = model.NewBoolVar(\"\")\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",
" # Final arc from an arc to the dummy node.\n",
" arcs.append([i + 1, 0, model.NewBoolVar('')])\n",
" arcs.append((i + 1, 0, model.NewBoolVar(\"\")))\n",
"\n",
" for j in all_jobs:\n",
" if i == j:\n",
" continue\n",
"\n",
" lit = model.NewBoolVar('%i follows %i' % (j, i))\n",
" arcs.append([i + 1, j + 1, lit])\n",
" lit = model.NewBoolVar(\"%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",
" # two tasks.\n",
" # 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] +\n",
" setup_times[i + 1][j]).OnlyEnforceIf(lit)\n",
" model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n",
" lit\n",
" )\n",
" else:\n",
" model.Add(starts[j] >= ends[i] +\n",
" setup_times[i + 1][j]).OnlyEnforceIf(lit)\n",
" model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(\n",
" lit\n",
" )\n",
"\n",
" model.AddCircuit(arcs)\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Precedences.\n",
" for before, after in precedences:\n",
" print('job %i is after job %i' % (after, before))\n",
" print(\"job %i is after job %i\" % (after, before))\n",
" model.Add(ends[before] <= starts[after])\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Objective.\n",
" makespan = model.NewIntVar(0, horizon, 'makespan')\n",
" makespan = model.NewIntVar(0, horizon, \"makespan\")\n",
" model.AddMaxEquality(makespan, ends)\n",
" model.Minimize(makespan)\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Write problem to file.\n",
" if output_proto_file:\n",
" print('Writing proto to %s' % output_proto_file)\n",
" with open(output_proto_file, 'w') as text_file:\n",
" print(\"Writing proto to %s\" % output_proto_file)\n",
" with open(output_proto_file, \"w\") as text_file:\n",
" text_file.write(str(model))\n",
"\n",
" #----------------------------------------------------------------------------\n",
" # ----------------------------------------------------------------------------\n",
" # Solve.\n",
" solver = cp_model.CpSolver()\n",
" if parameters:\n",
@@ -331,13 +573,14 @@
" 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 %i starts at %i end ends at %i\"\n",
" % (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id]))\n",
" )\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" single_machine_scheduling()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Maximize the minimum of pairwise distances between n robots in a square space.\n"
]
},
@@ -87,14 +88,14 @@
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_NUM_ROBOTS = flags.DEFINE_integer('num_robots', 8,\n",
" 'Number of robots to place.')\n",
"_ROOM_SIZE = flags.DEFINE_integer('room_size', 20,\n",
" 'Size of the square room where robots are.')\n",
"_NUM_ROBOTS = flags.DEFINE_integer(\"num_robots\", 8, \"Number of robots to place.\")\n",
"_ROOM_SIZE = flags.DEFINE_integer(\n",
" \"room_size\", 20, \"Size of the square room where robots are.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" 'params',\n",
" 'num_search_workers:16, max_time_in_seconds:20',\n",
" 'Sat solver parameters.',\n",
" \"params\",\n",
" \"num_search_workers:16, max_time_in_seconds:20\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"\n",
"\n",
@@ -103,8 +104,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.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",
"\n",
" # The specification of the problem is to maximize the minimum euclidian\n",
" # distance between any two robots. Unfortunately, the euclidian distance\n",
@@ -123,30 +124,30 @@
" # 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(0, 2 * scaling * room_size**2,\n",
" 'scaled_min_square_distance')\n",
" scaled_min_square_distance = model.NewIntVar(\n",
" 0, 2 * scaling * room_size**2, \"scaled_min_square_distance\"\n",
" )\n",
"\n",
" # Build intermediate variables and get the list of squared distances on\n",
" # each dimension.\n",
" 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",
" 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",
"\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",
" 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",
"\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 *\n",
" (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",
@@ -165,17 +166,19 @@
"\n",
" # Prints the solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f'Spread {num_robots} with a min pairwise distance of'\n",
" f' {math.sqrt(solver.ObjectiveValue() / scaling)}')\n",
" print(\n",
" f\"Spread {num_robots} with a min pairwise distance of\"\n",
" f\" {math.sqrt(solver.ObjectiveValue() / 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",
" print(\"No solution found.\")\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" spread_robots(_NUM_ROBOTS.value, _ROOM_SIZE.value, _PARAMS.value)\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves the Stell Mill Slab problem with 4 different techniques.\n",
"\n"
]
@@ -86,230 +87,94 @@
"import collections\n",
"import time\n",
"\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_PROBLEM = flags.DEFINE_integer('problem', 2, 'Problem id to solve.')\n",
"\n",
"_PROBLEM = flags.DEFINE_integer(\"problem\", 2, \"Problem id to solve.\")\n",
"_BREAK_SYMMETRIES = flags.DEFINE_boolean(\n",
" 'break_symmetries', True, 'Break symmetries between equivalent orders.')\n",
" \"break_symmetries\", True, \"Break symmetries between equivalent orders.\"\n",
")\n",
"_SOLVER = flags.DEFINE_string(\n",
" 'solver', 'sat_column', 'Method used to solve: sat, sat_table, sat_column.')\n",
" \"solver\", \"sat_column\", \"Method used to solve: sat, sat_table, sat_column.\"\n",
")\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\",\n",
" \"max_time_in_seconds:20,num_workers:8,log_search_progress:true\",\n",
" \"CP-SAT parameters.\",\n",
")\n",
"\n",
"\n",
"def build_problem(problem_id):\n",
" \"\"\"Build problem data.\"\"\"\n",
" capacities = None\n",
" num_colors = None\n",
" num_slabs = None\n",
" orders = None\n",
"\n",
" if problem_id == 0:\n",
" capacities = [\n",
" 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35,\n",
" 39, 42, 43, 44\n",
" # fmt:off\n",
" 0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, 39, 42, 43, 44,\n",
" # fmt:on\n",
" ]\n",
" num_colors = 88\n",
" num_slabs = 111\n",
" orders = [\n",
" (4, 1), # (size, color)\n",
" (22, 2),\n",
" (9, 3),\n",
" (5, 4),\n",
" (8, 5),\n",
" (3, 6),\n",
" (3, 4),\n",
" (4, 7),\n",
" (7, 4),\n",
" (7, 8),\n",
" (3, 6),\n",
" (2, 6),\n",
" (2, 4),\n",
" (8, 9),\n",
" (5, 10),\n",
" (7, 11),\n",
" (4, 7),\n",
" (7, 11),\n",
" (5, 10),\n",
" (7, 11),\n",
" (8, 9),\n",
" (3, 1),\n",
" (25, 12),\n",
" (14, 13),\n",
" (3, 6),\n",
" (22, 14),\n",
" (19, 15),\n",
" (19, 15),\n",
" (22, 16),\n",
" (22, 17),\n",
" (22, 18),\n",
" (20, 19),\n",
" (22, 20),\n",
" (5, 21),\n",
" (4, 22),\n",
" (10, 23),\n",
" (26, 24),\n",
" (17, 25),\n",
" (20, 26),\n",
" (16, 27),\n",
" (10, 28),\n",
" (19, 29),\n",
" (10, 30),\n",
" (10, 31),\n",
" (23, 32),\n",
" (22, 33),\n",
" (26, 34),\n",
" (27, 35),\n",
" (22, 36),\n",
" (27, 37),\n",
" (22, 38),\n",
" (22, 39),\n",
" (13, 40),\n",
" (14, 41),\n",
" (16, 27),\n",
" (26, 34),\n",
" (26, 42),\n",
" (27, 35),\n",
" (22, 36),\n",
" (20, 43),\n",
" (26, 24),\n",
" (22, 44),\n",
" (13, 45),\n",
" (19, 46),\n",
" (20, 47),\n",
" (16, 48),\n",
" (15, 49),\n",
" (17, 50),\n",
" (10, 28),\n",
" (20, 51),\n",
" (5, 52),\n",
" (26, 24),\n",
" (19, 53),\n",
" (15, 54),\n",
" (10, 55),\n",
" (10, 56),\n",
" (13, 57),\n",
" (13, 58),\n",
" (13, 59),\n",
" (12, 60),\n",
" (12, 61),\n",
" (18, 62),\n",
" (10, 63),\n",
" (18, 64),\n",
" (16, 65),\n",
" (20, 66),\n",
" (12, 67),\n",
" (6, 68),\n",
" (6, 68),\n",
" (15, 69),\n",
" (15, 70),\n",
" (15, 70),\n",
" (21, 71),\n",
" (30, 72),\n",
" (30, 73),\n",
" (30, 74),\n",
" (30, 75),\n",
" (23, 76),\n",
" (15, 77),\n",
" (15, 78),\n",
" (27, 79),\n",
" (27, 80),\n",
" (27, 81),\n",
" (27, 82),\n",
" (27, 83),\n",
" (27, 84),\n",
" (27, 79),\n",
" (27, 85),\n",
" (27, 86),\n",
" (10, 87),\n",
" (3, 88)\n",
" orders = [ # (size, color)\n",
" # fmt:off\n",
" (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7),\n",
" (7, 4), (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11),\n",
" (4, 7), (7, 11), (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13),\n",
" (3, 6), (22, 14), (19, 15), (19, 15), (22, 16), (22, 17), (22, 18),\n",
" (20, 19), (22, 20), (5, 21), (4, 22), (10, 23), (26, 24), (17, 25),\n",
" (20, 26), (16, 27), (10, 28), (19, 29), (10, 30), (10, 31), (23, 32),\n",
" (22, 33), (26, 34), (27, 35), (22, 36), (27, 37), (22, 38), (22, 39),\n",
" (13, 40), (14, 41), (16, 27), (26, 34), (26, 42), (27, 35), (22, 36),\n",
" (20, 43), (26, 24), (22, 44), (13, 45), (19, 46), (20, 47), (16, 48),\n",
" (15, 49), (17, 50), (10, 28), (20, 51), (5, 52), (26, 24), (19, 53),\n",
" (15, 54), (10, 55), (10, 56), (13, 57), (13, 58), (13, 59), (12, 60),\n",
" (12, 61), (18, 62), (10, 63), (18, 64), (16, 65), (20, 66), (12, 67),\n",
" (6, 68), (6, 68), (15, 69), (15, 70), (15, 70), (21, 71), (30, 72),\n",
" (30, 73), (30, 74), (30, 75), (23, 76), (15, 77), (15, 78), (27, 79),\n",
" (27, 80), (27, 81), (27, 82), (27, 83), (27, 84), (27, 79), (27, 85),\n",
" (27, 86), (10, 87), (3, 88),\n",
" # fmt:on\n",
" ]\n",
" elif problem_id == 1:\n",
" capacities = [0, 17, 44]\n",
" num_colors = 23\n",
" num_slabs = 30\n",
" orders = [\n",
" (4, 1), # (size, color)\n",
" (22, 2),\n",
" (9, 3),\n",
" (5, 4),\n",
" (8, 5),\n",
" (3, 6),\n",
" (3, 4),\n",
" (4, 7),\n",
" (7, 4),\n",
" (7, 8),\n",
" (3, 6),\n",
" (2, 6),\n",
" (2, 4),\n",
" (8, 9),\n",
" (5, 10),\n",
" (7, 11),\n",
" (4, 7),\n",
" (7, 11),\n",
" (5, 10),\n",
" (7, 11),\n",
" (8, 9),\n",
" (3, 1),\n",
" (25, 12),\n",
" (14, 13),\n",
" (3, 6),\n",
" (22, 14),\n",
" (19, 15),\n",
" (19, 15),\n",
" (22, 16),\n",
" (22, 17),\n",
" (22, 18),\n",
" (20, 19),\n",
" (22, 20),\n",
" (5, 21),\n",
" (4, 22),\n",
" (10, 23)\n",
" orders = [ # (size, color)\n",
" # fmt:off\n",
" (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7), (7, 4),\n",
" (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11), (4, 7), (7, 11),\n",
" (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13), (3, 6), (22, 14),\n",
" (19, 15), (19, 15), (22, 16), (22, 17), (22, 18), (20, 19), (22, 20),\n",
" (5, 21), (4, 22), (10, 23),\n",
" # fmt:on\n",
" ]\n",
" elif problem_id == 2:\n",
" capacities = [0, 17, 44]\n",
" num_colors = 15\n",
" num_slabs = 20\n",
" orders = [\n",
" (4, 1), # (size, color)\n",
" (22, 2),\n",
" (9, 3),\n",
" (5, 4),\n",
" (8, 5),\n",
" (3, 6),\n",
" (3, 4),\n",
" (4, 7),\n",
" (7, 4),\n",
" (7, 8),\n",
" (3, 6),\n",
" (2, 6),\n",
" (2, 4),\n",
" (8, 9),\n",
" (5, 10),\n",
" (7, 11),\n",
" (4, 7),\n",
" (7, 11),\n",
" (5, 10),\n",
" (7, 11),\n",
" (8, 9),\n",
" (3, 1),\n",
" (25, 12),\n",
" (14, 13),\n",
" (3, 6),\n",
" (22, 14),\n",
" (19, 15),\n",
" (19, 15)\n",
" orders = [ # (size, color)\n",
" # fmt:off\n",
" (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7), (7, 4),\n",
" (7, 8), (3, 6), (2, 6), (2, 4), (8, 9), (5, 10), (7, 11), (4, 7), (7, 11),\n",
" (5, 10), (7, 11), (8, 9), (3, 1), (25, 12), (14, 13), (3, 6), (22, 14),\n",
" (19, 15), (19, 15),\n",
" # fmt:on\n",
" ]\n",
"\n",
" elif problem_id == 3:\n",
" capacities = [0, 17, 44]\n",
" num_colors = 8\n",
" num_slabs = 10\n",
" orders = [\n",
" (4, 1), # (size, color)\n",
" (22, 2),\n",
" (9, 3),\n",
" (5, 4),\n",
" (8, 5),\n",
" (3, 6),\n",
" (3, 4),\n",
" (4, 7),\n",
" (7, 4),\n",
" (7, 8),\n",
" (3, 6)\n",
" orders = [ # (size, color)\n",
" # fmt:off\n",
" (4, 1), (22, 2), (9, 3), (5, 4), (8, 5), (3, 6), (3, 4), (4, 7),\n",
" (7, 4), (7, 8), (3, 6),\n",
" # fmt:on\n",
" ]\n",
"\n",
" return (num_slabs, capacities, num_colors, orders)\n",
@@ -333,21 +198,29 @@
" \"\"\"Called on each new solution.\"\"\"\n",
" current_time = time.time()\n",
" objective = sum(self.Value(l) for l in self.__loss)\n",
" print('Solution %i, time = %f s, objective = %i' %\n",
" (self.__solution_count, current_time - self.__start_time,\n",
" objective))\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",
" ] for s in self.__all_slabs]\n",
" orders_in_slab = [\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, self.Value(self.__load[s]), self.Value(self.__loss[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",
" )\n",
" for o in orders_in_slab[s]:\n",
" line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0],\n",
" self.__orders[o][1])\n",
" line += ']'\n",
" line += \"#%i(w%i, c%i) \" % (\n",
" o,\n",
" self.__orders[o][0],\n",
" self.__orders[o][1],\n",
" )\n",
" line += \"]\"\n",
" print(line)\n",
"\n",
"\n",
@@ -361,16 +234,17 @@
" all_slabs = range(num_slabs)\n",
" all_colors = range(num_colors)\n",
" all_orders = range(len(orders))\n",
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
" print(\n",
" \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n",
" % (num_orders, num_slabs, num_capacities - 1)\n",
" )\n",
"\n",
" # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
" 1)\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n",
" ]\n",
" max_loss = max(loss_array)\n",
" orders_per_color = [\n",
@@ -384,16 +258,15 @@
"\n",
" # 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",
" ] for o in all_orders]\n",
" loads = [\n",
" model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s)\n",
" assign = [\n",
" [model.NewBoolVar(\"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",
" color_is_in_slab = [\n",
" [model.NewBoolVar(\"color_%i_in_slab_%i\" % (c + 1, s)) for c in all_colors]\n",
" 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",
" ] for s in all_slabs]\n",
"\n",
" # Compute load of all slabs.\n",
" for s in all_slabs:\n",
@@ -411,8 +284,7 @@
" 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(),\n",
" assign[o][s].Not())\n",
" model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not())\n",
"\n",
" # At most two colors per slab.\n",
" for s in all_slabs:\n",
@@ -447,35 +319,39 @@
" if w not in local_width_to_order:\n",
" local_width_to_order[w] = []\n",
" local_width_to_order[w].append(o)\n",
" for w, os in local_width_to_order.items():\n",
" for _, os in local_width_to_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
" ordered_equivalent_orders.append((os[p], os[p + 1]))\n",
" for w, os in width_to_unique_color_order.items():\n",
" for _, os in width_to_unique_color_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
" ordered_equivalent_orders.append((os[p], os[p + 1]))\n",
"\n",
" # Create position variables if there are symmetries to be broken.\n",
" if break_symmetries and ordered_equivalent_orders:\n",
" print(' - creating %i symmetry breaking constraints' %\n",
" len(ordered_equivalent_orders))\n",
" print(\n",
" \" - creating %i symmetry breaking constraints\"\n",
" % len(ordered_equivalent_orders)\n",
" )\n",
" positions = {}\n",
" for p in ordered_equivalent_orders:\n",
" if p[0] not in positions:\n",
" positions[p[0]] = model.NewIntVar(0, num_slabs - 1,\n",
" 'position_of_slab_%i' % p[0])\n",
" positions[p[0]] = model.NewIntVar(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n",
" )\n",
" model.AddMapDomain(positions[p[0]], assign[p[0]])\n",
" if p[1] not in positions:\n",
" positions[p[1]] = model.NewIntVar(0, num_slabs - 1,\n",
" 'position_of_slab_%i' % p[1])\n",
" positions[p[1]] = model.NewIntVar(\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n",
" )\n",
" model.AddMapDomain(positions[p[1]], assign[p[1]])\n",
" # Finally add the symmetry breaking constraint.\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.NewIntVar(0, num_slabs * max_loss, \"obj\")\n",
" losses = [model.NewIntVar(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",
@@ -483,17 +359,19 @@
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_search_workers = 8\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",
"\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",
" \"Loss = %i, time = %f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" )\n",
" else:\n",
" print('No solution')\n",
" print(\"No solution\")\n",
"\n",
"\n",
"def collect_valid_slabs_dp(capacities, colors, widths, loss_array):\n",
@@ -502,13 +380,11 @@
"\n",
" max_capacity = max(capacities)\n",
"\n",
" valid_assignment = collections.namedtuple('valid_assignment',\n",
" 'orders load colors')\n",
" valid_assignment = collections.namedtuple(\"valid_assignment\", \"orders load colors\")\n",
" all_valid_assignments = [valid_assignment(orders=[], load=0, colors=[])]\n",
"\n",
" for order_id in range(len(colors)):\n",
" for order_id, new_color in enumerate(colors):\n",
" new_width = widths[order_id]\n",
" new_color = colors[order_id]\n",
" new_assignments = []\n",
" for assignment in all_valid_assignments:\n",
" if assignment.load + new_width > max_capacity:\n",
@@ -518,18 +394,21 @@
" new_colors.append(new_color)\n",
" if len(new_colors) > 2:\n",
" continue\n",
" new_assignment = valid_assignment(orders=assignment.orders +\n",
" [order_id],\n",
" load=assignment.load + new_width,\n",
" colors=new_colors)\n",
" new_assignment = valid_assignment(\n",
" orders=assignment.orders + [order_id],\n",
" load=assignment.load + new_width,\n",
" colors=new_colors,\n",
" )\n",
" new_assignments.append(new_assignment)\n",
" all_valid_assignments.extend(new_assignments)\n",
"\n",
" print('%i assignments created in %.2f s' %\n",
" (len(all_valid_assignments), time.time() - start_time))\n",
" print(\n",
" \"%i assignments created in %.2f s\"\n",
" % (len(all_valid_assignments), time.time() - start_time)\n",
" )\n",
" tuples = []\n",
" for assignment in all_valid_assignments:\n",
" solution = [0 for _ in range(len(colors))]\n",
" solution = [0] * len(colors)\n",
" for i in assignment.orders:\n",
" solution[i] = 1\n",
" solution.append(loss_array[assignment.load])\n",
@@ -549,16 +428,17 @@
" all_slabs = range(num_slabs)\n",
" all_colors = range(num_colors)\n",
" all_orders = range(len(orders))\n",
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
" print(\n",
" \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n",
" % (num_orders, num_slabs, num_capacities - 1)\n",
" )\n",
"\n",
" # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
" 1)\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n",
" ]\n",
" max_loss = max(loss_array)\n",
"\n",
@@ -566,21 +446,23 @@
"\n",
" # 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",
" ] for o in all_orders]\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",
" assign = [\n",
" [model.NewBoolVar(\"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",
"\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n",
" loss_array)\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(\n",
" capacities, colors, widths, loss_array\n",
" )\n",
" # Sort slab by descending load/loss. Remove duplicates.\n",
" valid_slabs = sorted(unsorted_valid_slabs,\n",
" key=lambda c: 1000 * c[-1] + c[-2])\n",
" valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
"\n",
" for s in all_slabs:\n",
" model.AddAllowedAssignments([assign[o][s] for o in all_orders] +\n",
" [losses[s], loads[s]], valid_slabs)\n",
" model.AddAllowedAssignments(\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",
@@ -595,7 +477,7 @@
"\n",
" # Collect equivalent orders.\n",
" if break_symmetries:\n",
" print('Breaking symmetries')\n",
" print(\"Breaking symmetries\")\n",
" width_to_unique_color_order = {}\n",
" ordered_equivalent_orders = []\n",
" orders_per_color = [\n",
@@ -619,28 +501,32 @@
" if w not in local_width_to_order:\n",
" local_width_to_order[w] = []\n",
" local_width_to_order[w].append(o)\n",
" for w, os in local_width_to_order.items():\n",
" for _, os in local_width_to_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
" ordered_equivalent_orders.append((os[p], os[p + 1]))\n",
" for w, os in width_to_unique_color_order.items():\n",
" for _, os in width_to_unique_color_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
" ordered_equivalent_orders.append((os[p], os[p + 1]))\n",
"\n",
" # Create position variables if there are symmetries to be broken.\n",
" if ordered_equivalent_orders:\n",
" print(' - creating %i symmetry breaking constraints' %\n",
" len(ordered_equivalent_orders))\n",
" print(\n",
" \" - creating %i symmetry breaking constraints\"\n",
" % len(ordered_equivalent_orders)\n",
" )\n",
" positions = {}\n",
" for p in ordered_equivalent_orders:\n",
" if p[0] not in positions:\n",
" positions[p[0]] = model.NewIntVar(\n",
" 0, num_slabs - 1, 'position_of_slab_%i' % p[0])\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[0]\n",
" )\n",
" model.AddMapDomain(positions[p[0]], assign[p[0]])\n",
" if p[1] not in positions:\n",
" positions[p[1]] = model.NewIntVar(\n",
" 0, num_slabs - 1, 'position_of_slab_%i' % p[1])\n",
" 0, num_slabs - 1, \"position_of_slab_%i\" % p[1]\n",
" )\n",
" model.AddMapDomain(positions[p[1]], assign[p[1]])\n",
" # Finally add the symmetry breaking constraint.\n",
" model.Add(positions[p[0]] <= positions[p[1]])\n",
@@ -648,22 +534,24 @@
" # Objective.\n",
" model.Minimize(sum(losses))\n",
"\n",
" print('Model created')\n",
" print(\"Model created\")\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.num_search_workers = 8\n",
" solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads,\n",
" losses)\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
"\n",
" solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\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",
" \"Loss = %i, time = %.2f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" )\n",
" else:\n",
" print('No solution')\n",
" print(\"No solution\")\n",
"\n",
"\n",
"def steel_mill_slab_with_column_generation(problem):\n",
@@ -674,76 +562,76 @@
" num_orders = len(orders)\n",
" num_capacities = len(capacities)\n",
" all_orders = range(len(orders))\n",
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
" print(\n",
" \"Solving steel mill with %i orders, %i slabs, and %i capacities\"\n",
" % (num_orders, num_slabs, num_capacities - 1)\n",
" )\n",
"\n",
" # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
" 1)\n",
" min(x for x in capacities if x >= c) - c for c in range(max_capacity + 1)\n",
" ]\n",
"\n",
" ### Model problem.\n",
"\n",
" # Generate all valid slabs (columns)\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n",
" loss_array)\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(\n",
" capacities, colors, widths, loss_array\n",
" )\n",
"\n",
" # Sort slab by descending load/loss. Remove duplicates.\n",
" valid_slabs = sorted(unsorted_valid_slabs,\n",
" key=lambda c: 1000 * c[-1] + c[-2])\n",
" valid_slabs = sorted(unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
" all_valid_slabs = range(len(valid_slabs))\n",
"\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.NewBoolVar(\"selected_%i\" % i) for i in all_valid_slabs]\n",
"\n",
" for order_id in all_orders:\n",
" model.Add(\n",
" sum(selected[i]\n",
" for i, slab in enumerate(valid_slabs)\n",
" if slab[order_id]) == 1)\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",
" sum(selected[i] * valid_slabs[i][-1]\n",
" for i in all_valid_slabs) == sum(widths))\n",
" sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)\n",
" )\n",
"\n",
" # Objective.\n",
" model.Minimize(\n",
" 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",
" print(\"Model created\")\n",
"\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_search_workers = 8\n",
" solver.parameters.log_search_progress = True\n",
" 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",
"\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",
" \"Loss = %i, time = %.2f s, %i conflicts\"\n",
" % (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())\n",
" )\n",
" else:\n",
" print('No solution')\n",
" print(\"No solution\")\n",
"\n",
"\n",
"def main(_):\n",
" if _SOLVER.value == 'sat':\n",
" if _SOLVER.value == \"sat\":\n",
" steel_mill_slab(_PROBLEM.value, _BREAK_SYMMETRIES.value)\n",
" elif _SOLVER.value == 'sat_table':\n",
" steel_mill_slab_with_valid_slabs(_PROBLEM.value,\n",
" _BREAK_SYMMETRIES.value)\n",
" elif _SOLVER.value == 'sat_column':\n",
" elif _SOLVER.value == \"sat_table\":\n",
" steel_mill_slab_with_valid_slabs(_PROBLEM.value, _BREAK_SYMMETRIES.value)\n",
" elif _SOLVER.value == \"sat_column\":\n",
" steel_mill_slab_with_column_generation(_PROBLEM.value)\n",
" else:\n",
" print(f'Unknown model {_SOLVER.value}')\n",
" print(f\"Unknown model {_SOLVER.value}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This model implements a sudoku solver.\n"
]
},
@@ -95,16 +96,22 @@
" line = list(range(0, line_size))\n",
" cell = list(range(0, cell_size))\n",
"\n",
" initial_grid = [[0, 6, 0, 0, 5, 0, 0, 2, 0], [0, 0, 0, 3, 0, 0, 0, 9, 0],\n",
" [7, 0, 0, 6, 0, 0, 0, 1, 0], [0, 0, 6, 0, 3, 0, 4, 0, 0],\n",
" [0, 0, 4, 0, 7, 0, 1, 0, 0], [0, 0, 5, 0, 9, 0, 8, 0, 0],\n",
" [0, 4, 0, 0, 0, 1, 0, 0, 6], [0, 3, 0, 0, 0, 8, 0, 0, 0],\n",
" [0, 2, 0, 0, 4, 0, 0, 5, 0]]\n",
" initial_grid = [\n",
" [0, 6, 0, 0, 5, 0, 0, 2, 0],\n",
" [0, 0, 0, 3, 0, 0, 0, 9, 0],\n",
" [7, 0, 0, 6, 0, 0, 0, 1, 0],\n",
" [0, 0, 6, 0, 3, 0, 4, 0, 0],\n",
" [0, 0, 4, 0, 7, 0, 1, 0, 0],\n",
" [0, 0, 5, 0, 9, 0, 8, 0, 0],\n",
" [0, 4, 0, 0, 0, 1, 0, 0, 6],\n",
" [0, 3, 0, 0, 0, 8, 0, 0, 0],\n",
" [0, 2, 0, 0, 4, 0, 0, 5, 0],\n",
" ]\n",
"\n",
" 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.NewIntVar(1, line_size, \"grid %i %i\" % (i, j))\n",
"\n",
" # AllDifferent on rows.\n",
" for i in line:\n",
@@ -120,8 +127,7 @@
" one_cell = []\n",
" for di in cell:\n",
" for dj in cell:\n",
" one_cell.append(grid[(i * cell_size + di,\n",
" j * cell_size + dj)])\n",
" one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)])\n",
"\n",
" model.AddAllDifferent(one_cell)\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"CP-SAT model for task allocation problem.\n",
"\n",
"see http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc-\n",
@@ -93,256 +94,210 @@
"def task_allocation_sat():\n",
" \"\"\"Solves the task allocation problem.\"\"\"\n",
" # Availability matrix.\n",
" available = [[\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0\n",
" available = [\n",
" # fmt:off\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n",
" 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 1, 1, 1, 1, 1, 1, 1, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n",
" 1, 1, 1, 1, 1, 1, 1, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 1, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n",
" 1, 1, 1, 1, 1, 1, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 1, 1, 1, 1, 1, 1, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n",
" 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ]]\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,\n",
" 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n",
" 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,\n",
" 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" [\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n",
" ],\n",
" # fmt:on\n",
" ]\n",
"\n",
" ntasks = len(available)\n",
" nslots = len(available[0])\n",
@@ -359,30 +314,31 @@
" 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.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",
"\n",
" for task in all_tasks:\n",
" model.Add(\n",
" sum(assign[(task, slot)]\n",
" for slot in all_slots\n",
" if available[task][slot] == 1) == 1)\n",
" sum(\n",
" assign[(task, slot)] for slot in all_slots if available[task][slot] == 1\n",
" )\n",
" == 1\n",
" )\n",
"\n",
" for slot in all_slots:\n",
" model.Add(\n",
" sum(assign[(task, slot)]\n",
" for task in all_tasks\n",
" if available[task][slot] == 1) <= capacity)\n",
" model.AddBoolOr([\n",
" assign[(task, slot)]\n",
" for task in all_tasks\n",
" if available[task][slot] == 1\n",
" ]).OnlyEnforceIf(slot_used[slot])\n",
" sum(\n",
" assign[(task, slot)] for task in all_tasks if available[task][slot] == 1\n",
" )\n",
" <= capacity\n",
" )\n",
" model.AddBoolOr(\n",
" [assign[(task, slot)] for task in all_tasks if available[task][slot] == 1]\n",
" ).OnlyEnforceIf(slot_used[slot])\n",
" for task in all_tasks:\n",
" if available[task][slot] == 1:\n",
" model.AddImplication(slot_used[slot].Not(),\n",
" assign[(task, slot)].Not())\n",
" model.AddImplication(slot_used[slot].Not(), assign[(task, slot)].Not())\n",
" else:\n",
" model.Add(assign[(task, slot)] == 0)\n",
"\n",
@@ -399,15 +355,15 @@
" solver.parameters.num_search_workers = 16\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(\"Statistics\")\n",
" print(\" - status =\", solver.StatusName(status))\n",
" print(\" - optimal solution =\", solver.ObjectiveValue())\n",
" print(\" - wall time : %f s\" % solver.WallTime())\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" task_allocation_sat()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Tasks and workers to group assignment to average sum(cost) / #workers.\n"
]
},
@@ -94,8 +95,10 @@
" self.__solution_count = 0\n",
"\n",
" def on_solution_callback(self):\n",
" print('Solution %i, time = %f s, objective = %i' %\n",
" (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n",
" print(\n",
" \"Solution %i, time = %f s, objective = %i\"\n",
" % (self.__solution_count, self.WallTime(), self.ObjectiveValue())\n",
" )\n",
" self.__solution_count += 1\n",
"\n",
"\n",
@@ -118,13 +121,13 @@
" 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.NewBoolVar(\"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.NewBoolVar(\"x[%i,%i]\" % (k, j))\n",
"\n",
" # Constraints\n",
"\n",
@@ -143,13 +146,11 @@
" 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",
" 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,\n",
" 'sum_of_costs_of_group_%i' % j)\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,\n",
" 'average_cost_of_group_%i' % j)\n",
" a = model.NewIntVar(0, sum_of_costs * scaling, \"average_cost_of_group_%i\" % j)\n",
" model.AddDivisionEquality(a, c, n)\n",
"\n",
" averages.append(a)\n",
@@ -160,7 +161,7 @@
" model.Add(sum(num_workers_in_group) == num_workers)\n",
"\n",
" # Objective.\n",
" obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj')\n",
" obj = model.NewIntVar(0, sum_of_costs * scaling, \"obj\")\n",
" model.AddMaxEquality(obj, averages)\n",
" model.Minimize(obj)\n",
"\n",
@@ -173,17 +174,18 @@
"\n",
" if status == cp_model.OPTIMAL:\n",
" for j in all_groups:\n",
" print('Group %i' % j)\n",
" print(\"Group %i\" % j)\n",
" for i in all_workers:\n",
" if solver.BooleanValue(x[i, j]):\n",
" print(' - worker %i' % i)\n",
" print(\" - worker %i\" % i)\n",
" for k in all_tasks:\n",
" if solver.BooleanValue(y[k, j]):\n",
" print(' - task %i with cost %i' % (k, task_cost[k]))\n",
" print(' - sum_of_costs = %i' %\n",
" (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling))\n",
" print(' - average cost = %f' %\n",
" (solver.Value(averages[j]) * 1.0 / scaling))\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",
" )\n",
" print(\" - average cost = %f\" % (solver.Value(averages[j]) * 1.0 / scaling))\n",
"\n",
"\n",
"tasks_and_workers_assignment_sat()\n",
@@ -191,7 +193,7 @@
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" tasks_and_workers_assignment_sat()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Simple travelling salesman problem between cities.\n"
]
},
@@ -85,6 +86,7 @@
"from ortools.sat.python import cp_model\n",
"\n",
"DISTANCE_MATRIX = [\n",
" # fmt:off\n",
" [0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],\n",
" [10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],\n",
" [4542, 6422, 0, 3644, 25173, 6552, 5092, 13584, 13372, 13766, 19805, 26537, 26117, 24804, 25590, 27784, 21148, 37981, 30693, 29315, 27148, 25071, 34943, 27472, 37281, 22389, 23592, 21433, 21655, 20011, 17087, 15612, 15872, 11653, 15666, 8842, 16843, 14618, 17563, 19589],\n",
@@ -125,14 +127,15 @@
" [10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],\n",
" [13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],\n",
" [15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],\n",
"] # yapf: disable\n",
" # fmt:on\n",
"]\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" num_nodes = len(DISTANCE_MATRIX)\n",
" all_nodes = range(num_nodes)\n",
" print('Num nodes =', num_nodes)\n",
" print(\"Num nodes =\", num_nodes)\n",
"\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
@@ -148,8 +151,8 @@
" if i == j:\n",
" continue\n",
"\n",
" lit = model.NewBoolVar('%i follows %i' % (j, i))\n",
" arcs.append([i, j, lit])\n",
" lit = model.NewBoolVar(\"%i follows %i\" % (j, i))\n",
" arcs.append((i, j, lit))\n",
" arc_literals[i, j] = lit\n",
"\n",
" obj_vars.append(lit)\n",
@@ -158,8 +161,7 @@
" model.AddCircuit(arcs)\n",
"\n",
" # Minimize weighted sum of arcs. Because this s\n",
" model.Minimize(\n",
" 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",
@@ -171,7 +173,7 @@
" print(solver.ResponseStats())\n",
"\n",
" current_node = 0\n",
" str_route = '%i' % current_node\n",
" str_route = \"%i\" % current_node\n",
" route_is_finished = False\n",
" route_distance = 0\n",
" while not route_is_finished:\n",
@@ -179,15 +181,15 @@
" if i == current_node:\n",
" continue\n",
" if solver.BooleanValue(arc_literals[current_node, i]):\n",
" str_route += ' -> %i' % i\n",
" str_route += \" -> %i\" % i\n",
" route_distance += DISTANCE_MATRIX[current_node][i]\n",
" current_node = i\n",
" if current_node == 0:\n",
" route_is_finished = True\n",
" break\n",
"\n",
" print('Route:', str_route)\n",
" print('Travelled distance:', route_distance)\n",
" print(\"Route:\", str_route)\n",
" print(\"Travelled distance:\", route_distance)\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves a simple shift scheduling problem.\n"
]
},
@@ -89,8 +90,15 @@
"class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, num_vendors, num_hours, possible_schedules,\n",
" selected_schedules, hours_stat, min_vendors):\n",
" def __init__(\n",
" self,\n",
" num_vendors,\n",
" num_hours,\n",
" possible_schedules,\n",
" selected_schedules,\n",
" hours_stat,\n",
" min_vendors,\n",
" ):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__solution_count = 0\n",
" self.__num_vendors = num_vendors\n",
@@ -103,17 +111,18 @@
" def on_solution_callback(self):\n",
" \"\"\"Called at each new solution.\"\"\"\n",
" self.__solution_count += 1\n",
" print('Solution %i: ', self.__solution_count)\n",
" print(' min vendors:', self.__min_vendors)\n",
" print(\"Solution %i: \", self.__solution_count)\n",
" print(\" min vendors:\", self.__min_vendors)\n",
" for i in range(self.__num_vendors):\n",
" print(\n",
" ' - vendor %i: ' % i, self.__possible_schedules[self.Value(\n",
" self.__selected_schedules[i])])\n",
" \" - vendor %i: \" % 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(\" - # workers on day%2i: \" % j, end=\" \")\n",
" print(self.Value(self.__hours_stat[j]), end=\" \")\n",
" print()\n",
" print()\n",
"\n",
@@ -140,12 +149,14 @@
" # Last columns are :\n",
" # index_of_the_schedule, sum of worked hours (per work type).\n",
" # The index is useful for branching.\n",
" possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n",
" [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n",
" [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n",
" [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n",
" [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]\n",
" possible_schedules = [\n",
" [1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],\n",
" [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],\n",
" [0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],\n",
" [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],\n",
" [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0],\n",
" ]\n",
"\n",
" num_possible_schedules = len(possible_schedules)\n",
" selected_schedules = []\n",
@@ -165,11 +176,10 @@
" 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.NewIntVar(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,\n",
" 's[%i]' % v)\n",
" hours = model.NewIntVar(0, num_hours, 'h[%i]' % v)\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_schedules.append(selected_schedule)\n",
" vendors_stat.append(hours)\n",
" tmp.append(selected_schedule)\n",
@@ -181,7 +191,7 @@
" # Statistics and constraints for each hour\n",
" #\n",
" for h in all_hours:\n",
" workers = model.NewIntVar(0, 1000, 'workers[%i]' % h)\n",
" workers = model.NewIntVar(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",
@@ -195,23 +205,27 @@
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solution_printer = SolutionPrinter(num_vendors, num_hours,\n",
" possible_schedules, selected_schedules,\n",
" hours_stat, min_vendors)\n",
" solution_printer = SolutionPrinter(\n",
" num_vendors,\n",
" num_hours,\n",
" possible_schedules,\n",
" selected_schedules,\n",
" hours_stat,\n",
" min_vendors,\n",
" )\n",
" status = solver.Solve(model, solution_printer)\n",
" print('Status = %s' % solver.StatusName(status))\n",
" print(\"Status = %s\" % solver.StatusName(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(' - number of solutions found: %i' %\n",
" solution_printer.solution_count())\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(\" - number of solutions found: %i\" % solution_printer.solution_count())\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
" vendor_scheduling_sat()\n",
"\n",
"\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Finding an optimal wedding seating chart.\n",
"\n",
"From\n",
@@ -124,9 +125,10 @@
" def on_solution_callback(self):\n",
" current_time = time.time()\n",
" objective = self.ObjectiveValue()\n",
" print(\"Solution %i, time = %f s, objective = %i\" %\n",
" (self.__solution_count, current_time - self.__start_time,\n",
" objective))\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",
"\n",
" for t in range(self.__num_tables):\n",
@@ -153,38 +155,52 @@
"\n",
" # Connection matrix: who knows who, and how strong\n",
" # is the relation\n",
" connections = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]\n",
" connections = [\n",
" [1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n",
" ]\n",
"\n",
" # Names of the guests. B: Bride side, G: Groom side\n",
" names = [\n",
" \"Deb (B)\", \"John (B)\", \"Martha (B)\", \"Travis (B)\", \"Allan (B)\",\n",
" \"Lois (B)\", \"Jayne (B)\", \"Brad (B)\", \"Abby (B)\", \"Mary Helen (G)\",\n",
" \"Lee (G)\", \"Annika (G)\", \"Carl (G)\", \"Colin (G)\", \"Shirley (G)\",\n",
" \"DeAnn (G)\", \"Lori (G)\"\n",
" \"Deb (B)\",\n",
" \"John (B)\",\n",
" \"Martha (B)\",\n",
" \"Travis (B)\",\n",
" \"Allan (B)\",\n",
" \"Lois (B)\",\n",
" \"Jayne (B)\",\n",
" \"Brad (B)\",\n",
" \"Abby (B)\",\n",
" \"Mary Helen (G)\",\n",
" \"Lee (G)\",\n",
" \"Annika (G)\",\n",
" \"Carl (G)\",\n",
" \"Colin (G)\",\n",
" \"Shirley (G)\",\n",
" \"DeAnn (G)\",\n",
" \"Lori (G)\",\n",
" ]\n",
" return num_tables, table_capacity, min_known_neighbors, connections, names\n",
"\n",
"\n",
"def solve_with_discrete_model():\n",
" \"\"\"Discrete approach.\"\"\"\n",
" num_tables, table_capacity, min_known_neighbors, connections, names = build_data(\n",
" )\n",
" num_tables, table_capacity, min_known_neighbors, connections, names = build_data()\n",
"\n",
" num_guests = len(connections)\n",
"\n",
@@ -200,28 +216,32 @@
" seats = {}\n",
" for t in all_tables:\n",
" for g in all_guests:\n",
" seats[(t,\n",
" g)] = model.NewBoolVar(\"guest %i seats on table %i\" % (g, t))\n",
" seats[(t, g)] = model.NewBoolVar(\"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",
" \"guest %i seats with guest %i\" % (g1, g2))\n",
" \"guest %i seats with guest %i\" % (g1, g2)\n",
" )\n",
"\n",
" same_table = {}\n",
" 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",
" \"guest %i seats with guest %i on table %i\" % (g1, g2, t))\n",
" \"guest %i seats with guest %i on table %i\" % (g1, g2, t)\n",
" )\n",
"\n",
" # Objective\n",
" model.Maximize(\n",
" sum(connections[g1][g2] * colocated[g1, g2]\n",
" sum(\n",
" connections[g1][g2] * colocated[g1, g2]\n",
" for g1 in range(num_guests - 1)\n",
" for g2 in range(g1 + 1, num_guests)\n",
" if connections[g1][g2] > 0))\n",
" if connections[g1][g2] > 0\n",
" )\n",
" )\n",
"\n",
" #\n",
" # Constraints\n",
@@ -240,29 +260,38 @@
" for g2 in range(g1 + 1, num_guests):\n",
" for t in all_tables:\n",
" # Link same_table and seats.\n",
" model.AddBoolOr([\n",
" seats[(t, g1)].Not(), seats[(t, g2)].Not(),\n",
" same_table[(g1, g2, t)]\n",
" ])\n",
" model.AddBoolOr(\n",
" [\n",
" seats[(t, g1)].Not(),\n",
" seats[(t, g2)].Not(),\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",
"\n",
" # Link colocated and same_table.\n",
" model.Add(\n",
" sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(\n",
" g1, g2)])\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",
" sum(same_table[(g, g2, t)]\n",
" sum(\n",
" same_table[(g, g2, t)]\n",
" for g2 in range(g + 1, num_guests)\n",
" for t in all_tables\n",
" if connections[g][g2] > 0) +\n",
" sum(same_table[(g1, g, t)]\n",
" if connections[g][g2] > 0\n",
" )\n",
" + sum(\n",
" same_table[(g1, g, t)]\n",
" for g1 in range(g)\n",
" for t in all_tables\n",
" if connections[g1][g] > 0) >= min_known_neighbors)\n",
" if connections[g1][g] > 0\n",
" )\n",
" >= min_known_neighbors\n",
" )\n",
"\n",
" # Symmetry breaking. First guest seats on the first table.\n",
" model.Add(seats[(0, 0)] == 1)\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solve a random Weighted Latency problem with the CP-SAT solver.\n"
]
},
@@ -87,16 +88,16 @@
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"_NUM_NODES = flags.DEFINE_integer('num_nodes', 12, 'Number of nodes to visit.')\n",
"_GRID_SIZE = flags.DEFINE_integer('grid_size', 20,\n",
" 'Size of the grid where nodes are.')\n",
"_PROFIT_RANGE = flags.DEFINE_integer('profit_range', 50, 'Range of profit.')\n",
"_SEED = flags.DEFINE_integer('seed', 0, 'Random seed.')\n",
"_PARAMS = flags.DEFINE_string('params',\n",
" 'num_search_workers:16, max_time_in_seconds:5',\n",
" 'Sat solver parameters.')\n",
"_NUM_NODES = flags.DEFINE_integer(\"num_nodes\", 12, \"Number of nodes to visit.\")\n",
"_GRID_SIZE = flags.DEFINE_integer(\"grid_size\", 20, \"Size of the grid where nodes are.\")\n",
"_PROFIT_RANGE = flags.DEFINE_integer(\"profit_range\", 50, \"Range of profit.\")\n",
"_SEED = flags.DEFINE_integer(\"seed\", 0, \"Random seed.\")\n",
"_PARAMS = flags.DEFINE_string(\n",
" \"params\", \"num_search_workers:16, max_time_in_seconds:5\", \"Sat solver parameters.\"\n",
")\n",
"_PROTO_FILE = flags.DEFINE_string(\n",
" 'proto_file', '', 'If not empty, output the proto to this file.')\n",
" \"proto_file\", \"\", \"If not empty, output the proto to this file.\"\n",
")\n",
"\n",
"\n",
"def build_model():\n",
@@ -126,10 +127,7 @@
"\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 = [\n",
" model.NewIntVar(0, horizon, f'x_{i}')\n",
" for i in range(_NUM_NODES.value + 1)\n",
" ]\n",
" times = [model.NewIntVar(0, horizon, f\"x_{i}\") for i in range(_NUM_NODES.value + 1)]\n",
"\n",
" # Node 0 is the start node.\n",
" model.Add(times[0] == 0)\n",
@@ -142,7 +140,7 @@
" 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.NewBoolVar(f\"{i}_to_{j}\")\n",
" arcs.append((i, j, lit))\n",
"\n",
" # Add transitions between nodes.\n",
@@ -169,7 +167,7 @@
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError('Too many command-line arguments.')\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" x, y, profits = build_model()\n",
" solve_with_cp_sat(x, y, profits)\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"This is the zebra problem as invented by Lewis Caroll.\n",
"\n",
"There are five houses.\n",
@@ -112,42 +113,41 @@
" # 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.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",
"\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.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",
"\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.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",
"\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.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",
"\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.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",
"\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,\n",
" old_gold)\n",
" model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold)\n",
"\n",
" model.Add(englishman == red)\n",
" model.Add(spaniard == dog)\n",
@@ -159,18 +159,18 @@
" model.Add(milk == 3)\n",
" model.Add(norwegian == 1)\n",
"\n",
" diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields')\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",
"\n",
" diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools')\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",
"\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",
" 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",
"\n",
@@ -180,16 +180,12 @@
"\n",
" if status == cp_model.OPTIMAL:\n",
" people = [englishman, spaniard, japanese, ukrainian, norwegian]\n",
" water_drinker = [\n",
" p for p in people if solver.Value(p) == solver.Value(water)\n",
" ][0]\n",
" zebra_owner = [\n",
" p for p in people if solver.Value(p) == solver.Value(zebra)\n",
" ][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",
" print(\"No solutions to the zebra problem, this is unusual!\")\n",
"\n",
"\n",
"solve_zebra()\n",

View File

@@ -92,17 +92,20 @@
" \"\"\"Linear Sum Assignment example.\"\"\"\n",
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
"\n",
" costs = np.array([\n",
" [90, 76, 75, 70],\n",
" [35, 85, 55, 65],\n",
" [125, 95, 90, 105],\n",
" [45, 110, 95, 115],\n",
" ])\n",
" costs = np.array(\n",
" [\n",
" [90, 76, 75, 70],\n",
" [35, 85, 55, 65],\n",
" [125, 95, 90, 105],\n",
" [45, 110, 95, 115],\n",
" ]\n",
" )\n",
"\n",
" # Let's transform this into 3 parallel vectors (start_nodes, end_nodes,\n",
" # arc_costs)\n",
" end_nodes_unraveled, start_nodes_unraveled = np.meshgrid(\n",
" np.arange(costs.shape[1]), np.arange(costs.shape[0]))\n",
" np.arange(costs.shape[1]), np.arange(costs.shape[0])\n",
" )\n",
" start_nodes = start_nodes_unraveled.ravel()\n",
" end_nodes = end_nodes_unraveled.ravel()\n",
" arc_costs = costs.ravel()\n",
@@ -112,15 +115,16 @@
" status = assignment.solve()\n",
"\n",
" if status == assignment.OPTIMAL:\n",
" print(f'Total cost = {assignment.optimal_cost()}\\n')\n",
" print(f\"Total cost = {assignment.optimal_cost()}\\n\")\n",
" for i in range(0, assignment.num_nodes()):\n",
" print(f'Worker {i} assigned to task {assignment.right_mate(i)}.' +\n",
" f' Cost = {assignment.assignment_cost(i)}')\n",
" print(\n",
" f\"Worker {i} assigned to task {assignment.right_mate(i)}.\"\n",
" + f\" Cost = {assignment.assignment_cost(i)}\"\n",
" )\n",
" elif status == assignment.INFEASIBLE:\n",
" print('No assignment is possible.')\n",
" print(\"No assignment is possible.\")\n",
" elif status == assignment.POSSIBLE_OVERFLOW:\n",
" print(\n",
" 'Some input costs are too large and may cause an integer overflow.')\n",
" print(\"Some input costs are too large and may cause an integer overflow.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -92,18 +92,20 @@
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
"\n",
" # Define the directed graph for the flow.\n",
" start_nodes = [0, 0, 0, 0] + [\n",
" 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4\n",
" ] + [5, 6, 7, 8]\n",
" end_nodes = [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8\n",
" ] + [9, 9, 9, 9]\n",
" capacities = [1, 1, 1, 1] + [\n",
" 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ] + [1, 1, 1, 1]\n",
" start_nodes = (\n",
" [0, 0, 0, 0] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + [5, 6, 7, 8]\n",
" )\n",
" end_nodes = (\n",
" [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8] + [9, 9, 9, 9]\n",
" )\n",
" capacities = (\n",
" [1, 1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1]\n",
" )\n",
" costs = (\n",
" [0, 0, 0, 0] +\n",
" [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115] +\n",
" [0, 0, 0, 0])\n",
" [0, 0, 0, 0]\n",
" + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115]\n",
" + [0, 0, 0, 0]\n",
" )\n",
"\n",
" source = 0\n",
" sink = 9\n",
@@ -112,8 +114,9 @@
"\n",
" # Add each arc.\n",
" for i in range(len(start_nodes)):\n",
" smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],\n",
" capacities[i], costs[i])\n",
" smcf.add_arc_with_capacity_and_unit_cost(\n",
" start_nodes[i], end_nodes[i], capacities[i], costs[i]\n",
" )\n",
" # Add node supplies.\n",
" for i in range(len(supplies)):\n",
" smcf.set_node_supply(i, supplies[i])\n",
@@ -122,20 +125,21 @@
" status = smcf.solve()\n",
"\n",
" if status == smcf.OPTIMAL:\n",
" print('Total cost = ', smcf.optimal_cost())\n",
" print(\"Total cost = \", smcf.optimal_cost())\n",
" print()\n",
" 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",
" print('Worker %d assigned to task %d. Cost = %d' %\n",
" (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))\n",
" print(\n",
" \"Worker %d assigned to task %d. Cost = %d\"\n",
" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc))\n",
" )\n",
" else:\n",
" print('There was an issue with the min cost flow input.')\n",
" print(f'Status: {status}')\n",
" print(\"There was an issue with the min cost flow input.\")\n",
" print(f\"Status: {status}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -94,20 +94,42 @@
" team_a = [1, 3, 5]\n",
" team_b = [2, 4, 6]\n",
"\n",
" start_nodes = ([0, 0] + [11, 11, 11] + [12, 12, 12] + [\n",
" 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6\n",
" ] + [7, 8, 9, 10])\n",
" end_nodes = ([11, 12] + team_a + team_b + [\n",
" 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8,\n",
" 9, 10\n",
" ] + [13, 13, 13, 13])\n",
" capacities = ([2, 2] + [1, 1, 1] + [1, 1, 1] + [\n",
" 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1\n",
" ] + [1, 1, 1, 1])\n",
" costs = ([0, 0] + [0, 0, 0] + [0, 0, 0] + [\n",
" 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60,\n",
" 105, 80, 75, 45, 65, 110, 95\n",
" ] + [0, 0, 0, 0])\n",
" start_nodes = (\n",
" # fmt: off\n",
" [0, 0]\n",
" + [11, 11, 11]\n",
" + [12, 12, 12]\n",
" + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]\n",
" + [7, 8, 9, 10]\n",
" # fmt: on\n",
" )\n",
" end_nodes = (\n",
" # fmt: off\n",
" [11, 12]\n",
" + team_a\n",
" + team_b\n",
" + [7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10]\n",
" + [13, 13, 13, 13]\n",
" # fmt: on\n",
" )\n",
" capacities = (\n",
" # fmt: off\n",
" [2, 2]\n",
" + [1, 1, 1]\n",
" + [1, 1, 1]\n",
" + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n",
" + [1, 1, 1, 1]\n",
" # fmt: on\n",
" )\n",
" costs = (\n",
" # fmt: off\n",
" [0, 0]\n",
" + [0, 0, 0]\n",
" + [0, 0, 0]\n",
" + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95]\n",
" + [0, 0, 0, 0]\n",
" # fmt: on\n",
" )\n",
"\n",
" source = 0\n",
" sink = 13\n",
@@ -117,8 +139,9 @@
"\n",
" # Add each arc.\n",
" for i in range(0, len(start_nodes)):\n",
" smcf.add_arc_with_capacity_and_unit_cost(start_nodes[i], end_nodes[i],\n",
" capacities[i], costs[i])\n",
" smcf.add_arc_with_capacity_and_unit_cost(\n",
" start_nodes[i], end_nodes[i], capacities[i], costs[i]\n",
" )\n",
"\n",
" # Add node supplies.\n",
" for i in range(0, len(supplies)):\n",
@@ -128,21 +151,26 @@
" status = smcf.solve()\n",
"\n",
" if status == smcf.OPTIMAL:\n",
" print('Total cost = ', smcf.optimal_cost())\n",
" print(\"Total cost = \", smcf.optimal_cost())\n",
" print()\n",
" for arc in range(smcf.num_arcs()):\n",
" # Can ignore arcs leading out of source or intermediate, or into sink.\n",
" if (smcf.tail(arc) != source and smcf.tail(arc) != 11 and\n",
" smcf.tail(arc) != 12 and smcf.head(arc) != sink):\n",
"\n",
" if (\n",
" smcf.tail(arc) != source\n",
" and smcf.tail(arc) != 11\n",
" and smcf.tail(arc) != 12\n",
" and smcf.head(arc) != sink\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",
" print('Worker %d assigned to task %d. Cost = %d' %\n",
" (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)))\n",
" print(\n",
" \"Worker %d assigned to task %d. Cost = %d\"\n",
" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc))\n",
" )\n",
" else:\n",
" print('There was an issue with the min cost flow input.')\n",
" print(f'Status: {status}')\n",
" print(\"There was an issue with the min cost flow input.\")\n",
" print(f\"Status: {status}\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -108,17 +108,17 @@
" status = smf.solve(0, 4)\n",
"\n",
" if status != smf.OPTIMAL:\n",
" print('There was an issue with the max flow input.')\n",
" print(f'Status: {status}')\n",
" print(\"There was an issue with the max flow input.\")\n",
" print(f\"Status: {status}\")\n",
" exit(1)\n",
" print('Max flow:', smf.optimal_flow())\n",
" print('')\n",
" print(' Arc Flow / Capacity')\n",
" print(\"Max flow:\", smf.optimal_flow())\n",
" print(\"\")\n",
" print(\" Arc Flow / Capacity\")\n",
" solution_flows = smf.flows(all_arcs)\n",
" for arc, flow, capacity in zip(all_arcs, solution_flows, capacities):\n",
" print(f'{smf.tail(arc)} / {smf.head(arc)} {flow:3} / {capacity:3}')\n",
" print('Source side min-cut:', smf.get_source_side_min_cut())\n",
" print('Sink side min-cut:', smf.get_sink_side_min_cut())\n",
" print(f\"{smf.tail(arc)} / {smf.head(arc)} {flow:3} / {capacity:3}\")\n",
" print(\"Source side min-cut:\", smf.get_source_side_min_cut())\n",
" print(\"Sink side min-cut:\", smf.get_sink_side_min_cut())\n",
"\n",
"\n",
"main()\n",

View File

@@ -106,7 +106,8 @@
"\n",
" # Add arcs, capacities and costs in bulk using numpy.\n",
" all_arcs = smcf.add_arcs_with_capacity_and_unit_cost(\n",
" start_nodes, end_nodes, capacities, unit_costs)\n",
" start_nodes, end_nodes, capacities, unit_costs\n",
" )\n",
"\n",
" # Add supply for each nodes.\n",
" smcf.set_nodes_supplies(np.arange(0, len(supplies)), supplies)\n",
@@ -115,17 +116,17 @@
" status = smcf.solve()\n",
"\n",
" if status != smcf.OPTIMAL:\n",
" print('There was an issue with the min cost flow input.')\n",
" print(f'Status: {status}')\n",
" print(\"There was an issue with the min cost flow input.\")\n",
" print(f\"Status: {status}\")\n",
" exit(1)\n",
" print(f'Minimum cost: {smcf.optimal_cost()}')\n",
" print('')\n",
" print(' Arc Flow / Capacity Cost')\n",
" print(f\"Minimum cost: {smcf.optimal_cost()}\")\n",
" print(\"\")\n",
" print(\" Arc Flow / Capacity Cost\")\n",
" solution_flows = smcf.flows(all_arcs)\n",
" costs = solution_flows * unit_costs\n",
" for arc, flow, cost in zip(all_arcs, solution_flows, costs):\n",
" print(\n",
" f'{smcf.tail(arc):1} -> {smcf.head(arc)} {flow:3} / {smcf.capacity(arc):3} {cost}'\n",
" f\"{smcf.tail(arc):1} -> {smcf.head(arc)} {flow:3} / {smcf.capacity(arc):3} {cost}\"\n",
" )\n",
"\n",
"\n",

View File

@@ -132,7 +132,7 @@
"\n",
" # Solver.\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if not solver:\n",
" return\n",
"\n",
@@ -142,27 +142,26 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n",
" x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # The total size of the tasks each worker takes on is at most total_size_max.\n",
" for worker in range(num_workers):\n",
" solver.Add(\n",
" solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n",
" solver.Add(solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" solver.Add(\n",
" solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n",
" solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\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] = solver.BoolVar(f'work[{worker}]')\n",
" work[worker] = solver.BoolVar(f\"work[{worker}]\")\n",
"\n",
" for worker in range(num_workers):\n",
" solver.Add(work[worker] == solver.Sum(\n",
" [x[worker, task] for task in range(num_tasks)]))\n",
" solver.Add(\n",
" work[worker] == solver.Sum([x[worker, task] for task in range(num_tasks)])\n",
" )\n",
"\n",
" # Group1\n",
" constraint_g1 = solver.Constraint(1, 1)\n",
@@ -172,7 +171,7 @@
" 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",
" p = solver.BoolVar(f\"g1_p{i}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g1.SetCoefficient(p, 1)\n",
@@ -185,7 +184,7 @@
" 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",
" p = solver.BoolVar(f\"g2_p{i}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g2.SetCoefficient(p, 1)\n",
@@ -198,7 +197,7 @@
" 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",
" p = solver.BoolVar(f\"g3_p{i}\")\n",
" constraint.SetCoefficient(p, -2)\n",
"\n",
" constraint_g3.SetCoefficient(p, 1)\n",
@@ -215,14 +214,16 @@
"\n",
" # Print solution.\n",
" if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n",
" print(f'Total cost = {solver.Objective().Value()}\\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 x[worker, task].solution_value() > 0.5:\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost: {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost: {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"MIP example that solves an assignment problem."
]
},
@@ -82,59 +83,76 @@
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import io\n",
"\n",
"import pandas as pd\n",
"\n",
"from ortools.linear_solver.python import model_builder\n",
"\n",
"\n",
"def main():\n",
" # Data\n",
" costs = np.array([\n",
" [90, 80, 75, 70],\n",
" [35, 85, 55, 65],\n",
" [125, 95, 90, 95],\n",
" [45, 110, 95, 115],\n",
" [50, 100, 90, 100],\n",
" ])\n",
" num_workers, num_tasks = costs.shape\n",
" data_str = \"\"\"\n",
" worker task cost\n",
" w1 t1 90\n",
" w1 t2 80\n",
" w1 t3 75\n",
" w1 t4 70\n",
" w2 t1 35\n",
" w2 t2 85\n",
" w2 t3 55\n",
" w2 t4 65\n",
" w3 t1 125\n",
" w3 t2 95\n",
" w3 t3 90\n",
" w3 t4 95\n",
" w4 t1 45\n",
" w4 t2 110\n",
" w4 t3 95\n",
" w4 t4 115\n",
" w5 t1 50\n",
" w5 t2 110\n",
" w5 t3 90\n",
" w5 t4 100\n",
" \"\"\"\n",
"\n",
" data = pd.read_table(io.StringIO(data_str), sep=r\"\\s+\")\n",
"\n",
" # Solver\n",
" # Create the model.\n",
" model = model_builder.ModelBuilder()\n",
"\n",
" # Variables\n",
" # x[i, j] is an array of 0-1 variables, which will be 1\n",
" # if worker i is assigned to task j.\n",
" x = model.new_bool_var_array(shape=[num_workers, num_tasks], name='x') # pytype: disable=wrong-arg-types # numpy-scalars\n",
" x = model.new_bool_var_series(name=\"x\", index=data.index)\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most 1 task.\n",
" for i in range(num_workers):\n",
" model.add(np.sum(x[i, :]) <= 1)\n",
" for unused_name, tasks in data.groupby(\"worker\"):\n",
" model.add(x[tasks.index].sum() <= 1)\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for j in range(num_tasks):\n",
" model.add(np.sum(x[:, j]) == 1)\n",
" for unused_name, workers in data.groupby(\"task\"):\n",
" model.add(x[workers.index].sum() == 1)\n",
"\n",
" # Objective\n",
" model.minimize(np.dot(x.flatten(), costs.flatten()))\n",
" model.minimize(data.cost.dot(x))\n",
"\n",
" # Create the solver with the CP-SAT backend, and solve the model.\n",
" solver = model_builder.ModelSolver('sat')\n",
" solver = model_builder.ModelSolver(\"sat\")\n",
" status = solver.solve(model)\n",
"\n",
" # Print solution.\n",
" if (status == model_builder.SolveStatus.OPTIMAL or\n",
" status == model_builder.SolveStatus.FEASIBLE):\n",
" print(f'Total cost = {solver.objective_value}\\n')\n",
" for i in range(num_workers):\n",
" for j in range(num_tasks):\n",
" # Test if x[i,j] is 1 (with tolerance for floating point arithmetic).\n",
" if solver.value(x[i, j]) > 0.5:\n",
" print(f'Worker {i} assigned to task {j}.' +\n",
" f' Cost: {costs[i][j]}')\n",
" if (\n",
" status == model_builder.SolveStatus.OPTIMAL\n",
" or status == model_builder.SolveStatus.FEASIBLE\n",
" ):\n",
" print(f\"Total cost = {solver.objective_value}\\n\")\n",
" selected = data.loc[solver.values(x).loc[lambda x: x == 1].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",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"MIP example that solves an assignment problem."
]
},
@@ -99,7 +100,7 @@
"\n",
" # Solver\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
"\n",
" if not solver:\n",
" return\n",
@@ -110,7 +111,7 @@
" x = {}\n",
" for i in range(num_workers):\n",
" for j in range(num_tasks):\n",
" x[i, j] = solver.IntVar(0, 1, '')\n",
" x[i, j] = solver.IntVar(0, 1, \"\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most 1 task.\n",
@@ -133,15 +134,14 @@
"\n",
" # Print solution.\n",
" if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n",
" print(f'Total cost = {solver.Objective().Value()}\\n')\n",
" print(f\"Total cost = {solver.Objective().Value()}\\n\")\n",
" for i in range(num_workers):\n",
" for j in range(num_tasks):\n",
" # Test if x[i,j] is 1 (with tolerance for floating point arithmetic).\n",
" if x[i, j].solution_value() > 0.5:\n",
" print(f'Worker {i} assigned to task {j}.' +\n",
" f' Cost: {costs[i][j]}')\n",
" print(f\"Worker {i} assigned to task {j}.\" + f\" Cost: {costs[i][j]}\")\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -109,7 +109,7 @@
"\n",
" # Solver\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
"\n",
" if not solver:\n",
" return\n",
@@ -120,20 +120,21 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n",
" x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # The total size of the tasks each worker takes on is at most total_size_max.\n",
" for worker in range(num_workers):\n",
" solver.Add(\n",
" solver.Sum([\n",
" task_sizes[task] * x[worker, task] for task in range(num_tasks)\n",
" ]) <= total_size_max)\n",
" solver.Sum(\n",
" [task_sizes[task] * x[worker, task] for task in range(num_tasks)]\n",
" )\n",
" <= total_size_max\n",
" )\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" solver.Add(\n",
" solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n",
" solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n",
"\n",
" # Objective\n",
" objective_terms = []\n",
@@ -147,14 +148,16 @@
"\n",
" # Print solution.\n",
" if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n",
" print(f'Total cost = {solver.Objective().Value()}\\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 x[worker, task].solution_value() > 0.5:\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost: {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost: {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -106,7 +106,7 @@
"\n",
" # Solver\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if not solver:\n",
" return\n",
"\n",
@@ -116,18 +116,16 @@
" x = {}\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" x[worker, task] = solver.BoolVar(f'x[{worker},{task}]')\n",
" x[worker, task] = solver.BoolVar(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned at most 1 task.\n",
" for worker in range(num_workers):\n",
" solver.Add(\n",
" solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n",
" solver.Add(solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1)\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for task in range(num_tasks):\n",
" solver.Add(\n",
" solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n",
" solver.Add(solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1)\n",
"\n",
" # Each team takes at most two tasks.\n",
" team1_tasks = []\n",
@@ -154,15 +152,17 @@
"\n",
" # Print solution.\n",
" if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:\n",
" print(f'Total cost = {solver.Objective().Value()}\\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 x[worker, task].solution_value() > 0.5:\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost = {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(f'Time = {solver.WallTime()} ms')\n",
" print(\"No solution found.\")\n",
" print(f\"Time = {solver.WallTime()} ms\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Minimal example to call the GLOP solver."
]
},
@@ -83,27 +84,26 @@
"outputs": [],
"source": [
"from ortools.linear_solver import pywraplp\n",
"from ortools.init import pywrapinit\n",
"\n",
"\n",
"def main():\n",
" # Create the linear solver with the GLOP backend.\n",
" solver = pywraplp.Solver.CreateSolver('GLOP')\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\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 = solver.NumVar(0, 1, \"x\")\n",
" y = solver.NumVar(0, 2, \"y\")\n",
"\n",
" print('Number of variables =', solver.NumVariables())\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 = solver.Constraint(0, 2, \"ct\")\n",
" ct.SetCoefficient(x, 1)\n",
" ct.SetCoefficient(y, 1)\n",
"\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Create the objective function, 3 * x + y.\n",
" objective = solver.Objective()\n",
@@ -113,18 +113,12 @@
"\n",
" solver.Solve()\n",
"\n",
" print('Solution:')\n",
" print('Objective value =', objective.Value())\n",
" print('x =', x.solution_value())\n",
" print('y =', y.solution_value())\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", objective.Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
"\n",
"\n",
"pywrapinit.CppBridge.InitLogging('basic_example.py')\n",
"cpp_flags = pywrapinit.CppFlags()\n",
"cpp_flags.logtostderr = True\n",
"cpp_flags.log_prefix = False\n",
"pywrapinit.CppBridge.SetFlags(cpp_flags)\n",
"\n",
"main()\n",
"\n"
]

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solve a simple bin packing problem using a MIP solver."
]
},
@@ -82,76 +83,103 @@
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import io\n",
"\n",
"import pandas as pd\n",
"\n",
"from ortools.linear_solver.python import model_builder\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Create the data for the example.\"\"\"\n",
" data = {}\n",
" weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]\n",
" data['weights'] = weights\n",
" data['items'] = list(range(len(weights)))\n",
" data['bins'] = data['items']\n",
" data['bin_capacity'] = 100\n",
" return data\n",
"\n",
" items_str = \"\"\"\n",
" item weight\n",
" i1 48\n",
" i2 30\n",
" i3 19\n",
" i4 36\n",
" i5 36\n",
" i6 27\n",
" i7 42\n",
" i8 42\n",
" i9 36\n",
" i10 24\n",
" i11 30\n",
" \"\"\"\n",
"\n",
" bins_str = \"\"\"\n",
" bin capacity\n",
" b1 100\n",
" b2 100\n",
" b3 100\n",
" b4 100\n",
" b5 100\n",
" b6 100\n",
" b7 100\n",
" \"\"\"\n",
"\n",
" items = pd.read_table(io.StringIO(items_str), index_col=0, sep=r\"\\s+\")\n",
" bins = pd.read_table(io.StringIO(bins_str), index_col=0, sep=r\"\\s+\")\n",
" return items, bins\n",
"\n",
"\n",
"def main():\n",
" data = create_data_model()\n",
" num_items = len(data['items'])\n",
" num_bins = len(data['bins'])\n",
" items, bins = create_data_model()\n",
"\n",
" # Create the model.\n",
" model = model_builder.ModelBuilder()\n",
"\n",
" # Variables\n",
" # x[i, j] = 1 if item i is packed in bin j.\n",
" x = model.new_bool_var_array(shape=[num_items, num_bins], name='x')\n",
" items_x_bins = pd.MultiIndex.from_product(\n",
" [items.index, bins.index], names=[\"item\", \"bin\"]\n",
" )\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.new_bool_var_array(shape=[num_bins], name='y')\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 i in data['items']:\n",
" model.add(np.sum(x[i, :]) == 1)\n",
" for unused_name, all_copies in x.groupby(\"item\"):\n",
" model.add(x[all_copies.index].sum() == 1)\n",
"\n",
" # The amount packed in each bin cannot exceed its capacity.\n",
" for j in data['bins']:\n",
" for selected_bin in bins.index:\n",
" items_in_bin = x.xs(selected_bin, level=\"bin\")\n",
" model.add(\n",
" np.dot(x[:, j], data['weights']) <= data['bin_capacity'] * y[j])\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(np.sum(y))\n",
" model.minimize(y.sum())\n",
"\n",
" # Create the solver with the CP-SAT backend, and solve the model.\n",
" solver = model_builder.ModelSolver('sat')\n",
" solver = model_builder.ModelSolver(\"sat\")\n",
" status = solver.solve(model)\n",
"\n",
" if status == model_builder.SolveStatus.OPTIMAL:\n",
" num_bins = 0.\n",
" for j in data['bins']:\n",
" if solver.value(y[j]) == 1:\n",
" bin_items = []\n",
" bin_weight = 0\n",
" for i in data['items']:\n",
" if solver.value(x[i, j]) > 0:\n",
" bin_items.append(i)\n",
" bin_weight += data['weights'][i]\n",
" if bin_weight > 0:\n",
" num_bins += 1\n",
" print('Bin number', j)\n",
" print(' Items packed:', bin_items)\n",
" print(' Total weight:', bin_weight)\n",
" print()\n",
" print(f\"Number of bins used = {solver.objective_value}\")\n",
"\n",
" x_values = solver.values(x)\n",
" y_values = solver.values(y)\n",
" active_bins = y_values.loc[lambda x: x == 1].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 == 1].index\n",
" for item in items_in_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",
"\n",
" print(f\"Total packed weight: {items.weight.sum()}\")\n",
" print()\n",
" print('Number of bins used:', num_bins)\n",
" print('Time = ', solver.wall_time, ' seconds')\n",
" print(f\"Time = {solver.wall_time} seconds\")\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solve a simple bin packing problem using a MIP solver."
]
},
@@ -89,10 +90,10 @@
" \"\"\"Create the data for the example.\"\"\"\n",
" data = {}\n",
" weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]\n",
" data['weights'] = weights\n",
" data['items'] = list(range(len(weights)))\n",
" data['bins'] = data['items']\n",
" data['bin_capacity'] = 100\n",
" data[\"weights\"] = weights\n",
" data[\"items\"] = list(range(len(weights)))\n",
" data[\"bins\"] = data[\"items\"]\n",
" data[\"bin_capacity\"] = 100\n",
" return data\n",
"\n",
"\n",
@@ -101,7 +102,7 @@
" data = create_data_model()\n",
"\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
"\n",
" if not solver:\n",
" return\n",
@@ -109,52 +110,53 @@
" # Variables\n",
" # x[i, j] = 1 if item i is packed in bin j.\n",
" x = {}\n",
" for i in data['items']:\n",
" for j in data['bins']:\n",
" x[(i, j)] = solver.IntVar(0, 1, 'x_%i_%i' % (i, j))\n",
" for i in data[\"items\"]:\n",
" for j in data[\"bins\"]:\n",
" x[(i, j)] = solver.IntVar(0, 1, \"x_%i_%i\" % (i, j))\n",
"\n",
" # y[j] = 1 if bin j is used.\n",
" y = {}\n",
" for j in data['bins']:\n",
" y[j] = solver.IntVar(0, 1, 'y[%i]' % j)\n",
" for j in data[\"bins\"]:\n",
" y[j] = solver.IntVar(0, 1, \"y[%i]\" % j)\n",
"\n",
" # Constraints\n",
" # Each item must be in exactly one bin.\n",
" for i in data['items']:\n",
" solver.Add(sum(x[i, j] for j in data['bins']) == 1)\n",
" for i in data[\"items\"]:\n",
" solver.Add(sum(x[i, j] for j in data[\"bins\"]) == 1)\n",
"\n",
" # The amount packed in each bin cannot exceed its capacity.\n",
" for j in data['bins']:\n",
" for j in data[\"bins\"]:\n",
" solver.Add(\n",
" sum(x[(i, j)] * data['weights'][i] for i in data['items']) <= y[j] *\n",
" data['bin_capacity'])\n",
" sum(x[(i, j)] * data[\"weights\"][i] for i in data[\"items\"])\n",
" <= y[j] * data[\"bin_capacity\"]\n",
" )\n",
"\n",
" # Objective: minimize the number of bins used.\n",
" solver.Minimize(solver.Sum([y[j] for j in data['bins']]))\n",
" solver.Minimize(solver.Sum([y[j] for j in data[\"bins\"]]))\n",
"\n",
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" num_bins = 0\n",
" for j in data['bins']:\n",
" for j in data[\"bins\"]:\n",
" if y[j].solution_value() == 1:\n",
" bin_items = []\n",
" bin_weight = 0\n",
" for i in data['items']:\n",
" for i in data[\"items\"]:\n",
" if x[i, j].solution_value() > 0:\n",
" bin_items.append(i)\n",
" bin_weight += data['weights'][i]\n",
" bin_weight += data[\"weights\"][i]\n",
" if bin_items:\n",
" num_bins += 1\n",
" print('Bin number', j)\n",
" print(' Items packed:', bin_items)\n",
" print(' Total weight:', bin_weight)\n",
" print(\"Bin number\", j)\n",
" print(\" Items packed:\", bin_items)\n",
" print(\" Total weight:\", bin_weight)\n",
" print()\n",
" print()\n",
" print('Number of bins used:', num_bins)\n",
" print('Time = ', solver.WallTime(), ' milliseconds')\n",
" print(\"Number of bins used:\", num_bins)\n",
" print(\"Time = \", solver.WallTime(), \" milliseconds\")\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Small example to illustrate solving a MIP problem."
]
},
@@ -88,14 +89,14 @@
"def IntegerProgrammingExample():\n",
" \"\"\"Integer programming sample.\"\"\"\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if not solver:\n",
" return\n",
"\n",
" # x, y, and z are non-negative integer variables.\n",
" x = solver.IntVar(0.0, solver.infinity(), 'x')\n",
" y = solver.IntVar(0.0, solver.infinity(), 'y')\n",
" z = solver.IntVar(0.0, solver.infinity(), 'z')\n",
" x = solver.IntVar(0.0, solver.infinity(), \"x\")\n",
" y = solver.IntVar(0.0, solver.infinity(), \"y\")\n",
" z = solver.IntVar(0.0, solver.infinity(), \"z\")\n",
"\n",
" # 2*x + 7*y + 3*z <= 50\n",
" constraint0 = solver.Constraint(-solver.infinity(), 50)\n",
@@ -125,11 +126,11 @@
" # Solve the problem and print the solution.\n",
" solver.Solve()\n",
" # Print the objective value of the solution.\n",
" print('Maximum objective function value = %d' % solver.Objective().Value())\n",
" print(\"Maximum objective function value = %d\" % solver.Objective().Value())\n",
" print()\n",
" # Print the value of each variable in the solution.\n",
" for variable in [x, y, z]:\n",
" print('%s = %d' % (variable.name(), variable.solution_value()))\n",
" print(\"%s = %d\" % (variable.name(), variable.solution_value()))\n",
"\n",
"\n",
"IntegerProgrammingExample()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Linear optimization example."
]
},
@@ -88,15 +89,15 @@
"def LinearProgrammingExample():\n",
" \"\"\"Linear programming sample.\"\"\"\n",
" # Instantiate a Glop solver, naming it LinearExample.\n",
" solver = pywraplp.Solver.CreateSolver('GLOP')\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\n",
" return\n",
"\n",
" # Create the two variables and let them take on any non-negative value.\n",
" x = solver.NumVar(0, solver.infinity(), 'x')\n",
" y = solver.NumVar(0, solver.infinity(), 'y')\n",
" x = solver.NumVar(0, solver.infinity(), \"x\")\n",
" y = solver.NumVar(0, solver.infinity(), \"y\")\n",
"\n",
" print('Number of variables =', solver.NumVariables())\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # Constraint 0: x + 2y <= 14.\n",
" solver.Add(x + 2 * y <= 14.0)\n",
@@ -107,7 +108,7 @@
" # Constraint 2: x - y <= 2.\n",
" solver.Add(x - y <= 2.0)\n",
"\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Objective function: 3x + 4y.\n",
" solver.Maximize(3 * x + 4 * y)\n",
@@ -116,16 +117,16 @@
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" print('Solution:')\n",
" print('Objective value =', solver.Objective().Value())\n",
" print('x =', x.solution_value())\n",
" print('y =', y.solution_value())\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", solver.Objective().Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print('Problem solved in %d iterations' % solver.iterations())\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
" print(\"Problem solved in %d iterations\" % solver.iterations())\n",
"\n",
"\n",
"LinearProgrammingExample()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"MIP example that uses a variable array."
]
},
@@ -88,16 +89,16 @@
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['constraint_coeffs'] = [\n",
" data[\"constraint_coeffs\"] = [\n",
" [5, 7, 9, 2, 1],\n",
" [18, 4, -9, 10, 12],\n",
" [4, 7, 3, 8, 5],\n",
" [5, 13, 16, 3, -7],\n",
" ]\n",
" data['bounds'] = [250, 285, 211, 315]\n",
" data['obj_coeffs'] = [7, 8, 2, 9, 6]\n",
" data['num_vars'] = 5\n",
" data['num_constraints'] = 4\n",
" data[\"bounds\"] = [250, 285, 211, 315]\n",
" data[\"obj_coeffs\"] = [7, 8, 2, 9, 6]\n",
" data[\"num_vars\"] = 5\n",
" data[\"num_constraints\"] = 4\n",
" return data\n",
"\n",
"\n",
@@ -105,21 +106,21 @@
"def main():\n",
" data = create_data_model()\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if not solver:\n",
" return\n",
"\n",
" infinity = solver.infinity()\n",
" x = {}\n",
" for j in range(data['num_vars']):\n",
" x[j] = solver.IntVar(0, infinity, 'x[%i]' % j)\n",
" print('Number of variables =', solver.NumVariables())\n",
" for j in range(data[\"num_vars\"]):\n",
" x[j] = solver.IntVar(0, infinity, \"x[%i]\" % j)\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" for i in range(data['num_constraints']):\n",
" constraint = solver.RowConstraint(0, data['bounds'][i], '')\n",
" for j in range(data['num_vars']):\n",
" constraint.SetCoefficient(x[j], data['constraint_coeffs'][i][j])\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" for i in range(data[\"num_constraints\"]):\n",
" constraint = solver.RowConstraint(0, data[\"bounds\"][i], \"\")\n",
" for j in range(data[\"num_vars\"]):\n",
" constraint.SetCoefficient(x[j], data[\"constraint_coeffs\"][i][j])\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
" # In Python, you can also set the constraints as follows.\n",
" # for i in range(data['num_constraints']):\n",
" # constraint_expr = \\\n",
@@ -127,8 +128,8 @@
" # solver.Add(sum(constraint_expr) <= data['bounds'][i])\n",
"\n",
" objective = solver.Objective()\n",
" for j in range(data['num_vars']):\n",
" objective.SetCoefficient(x[j], data['obj_coeffs'][j])\n",
" for j in range(data[\"num_vars\"]):\n",
" objective.SetCoefficient(x[j], data[\"obj_coeffs\"][j])\n",
" objective.SetMaximization()\n",
" # In Python, you can also set the objective as follows.\n",
" # obj_expr = [data['obj_coeffs'][j] * x[j] for j in range(data['num_vars'])]\n",
@@ -137,15 +138,15 @@
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" print('Objective value =', solver.Objective().Value())\n",
" for j in range(data['num_vars']):\n",
" print(x[j].name(), ' = ', x[j].solution_value())\n",
" print(\"Objective value =\", solver.Objective().Value())\n",
" for j in range(data[\"num_vars\"]):\n",
" print(x[j].name(), \" = \", x[j].solution_value())\n",
" print()\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print('Problem solved in %d iterations' % solver.iterations())\n",
" print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
" print(\"Problem solved in %d iterations\" % solver.iterations())\n",
" print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -88,74 +88,71 @@
"\n",
"def main():\n",
" data = {}\n",
" data['weights'] = [\n",
" 48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36\n",
" ]\n",
" data['values'] = [\n",
" 10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25\n",
" ]\n",
" assert len(data['weights']) == len(data['values'])\n",
" data['num_items'] = len(data['weights'])\n",
" data['all_items'] = range(data['num_items'])\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",
"\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",
" 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",
"\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SCIP')\n",
" solver = pywraplp.Solver.CreateSolver(\"SCIP\")\n",
" if solver is None:\n",
" print('SCIP solver unavailable.')\n",
" print(\"SCIP solver unavailable.\")\n",
" return\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] = solver.BoolVar(f'x_{i}_{b}')\n",
" for i in data[\"all_items\"]:\n",
" for b in data[\"all_bins\"]:\n",
" x[i, b] = solver.BoolVar(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",
" solver.Add(sum(x[i, b] for b in data['all_bins']) <= 1)\n",
" for i in data[\"all_items\"]:\n",
" solver.Add(sum(x[i, b] for b in data[\"all_bins\"]) <= 1)\n",
"\n",
" # The amount packed in each bin cannot exceed its capacity.\n",
" for b in data['all_bins']:\n",
" for b in data[\"all_bins\"]:\n",
" solver.Add(\n",
" sum(x[i, b] * data['weights'][i]\n",
" for i in data['all_items']) <= data['bin_capacities'][b])\n",
" sum(x[i, b] * data[\"weights\"][i] for i in data[\"all_items\"])\n",
" <= data[\"bin_capacities\"][b]\n",
" )\n",
"\n",
" # Objective.\n",
" # Maximize total value of packed items.\n",
" objective = solver.Objective()\n",
" for i in data['all_items']:\n",
" for b in data['all_bins']:\n",
" objective.SetCoefficient(x[i, b], data['values'][i])\n",
" for i in data[\"all_items\"]:\n",
" for b in data[\"all_bins\"]:\n",
" objective.SetCoefficient(x[i, b], data[\"values\"][i])\n",
" objective.SetMaximization()\n",
"\n",
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" print(f'Total packed value: {objective.Value()}')\n",
" print(f\"Total packed value: {objective.Value()}\")\n",
" total_weight = 0\n",
" for b in data['all_bins']:\n",
" print(f'Bin {b}')\n",
" for b in data[\"all_bins\"]:\n",
" print(f\"Bin {b}\")\n",
" bin_weight = 0\n",
" bin_value = 0\n",
" for i in data['all_items']:\n",
" 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",
" )\n",
" bin_weight += data['weights'][i]\n",
" bin_value += data['values'][i]\n",
" print(f'Packed bin weight: {bin_weight}')\n",
" print(f'Packed bin value: {bin_value}\\n')\n",
" bin_weight += data[\"weights\"][i]\n",
" bin_value += data[\"values\"][i]\n",
" print(f\"Packed bin weight: {bin_weight}\")\n",
" print(f\"Packed bin value: {bin_value}\\n\")\n",
" total_weight += bin_weight\n",
" print(f'Total packed weight: {total_weight}')\n",
" print(f\"Total packed weight: {total_weight}\")\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Minimal example to call the GLOP solver."
]
},
@@ -87,16 +88,16 @@
"\n",
"def main():\n",
" # Create the linear solver with the GLOP backend.\n",
" solver = pywraplp.Solver.CreateSolver('GLOP')\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\n",
" return\n",
"\n",
" infinity = solver.infinity()\n",
" # Create the variables x and y.\n",
" x = solver.NumVar(0.0, infinity, 'x')\n",
" y = solver.NumVar(0.0, infinity, 'y')\n",
" x = solver.NumVar(0.0, infinity, \"x\")\n",
" y = solver.NumVar(0.0, infinity, \"y\")\n",
"\n",
" print('Number of variables =', solver.NumVariables())\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # x + 7 * y <= 17.5.\n",
" solver.Add(x + 7 * y <= 17.5)\n",
@@ -104,25 +105,25 @@
" # x <= 3.5.\n",
" solver.Add(x <= 3.5)\n",
"\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Maximize x + 10 * y.\n",
" solver.Maximize(x + 10 * y)\n",
"\n",
" print(f'Solving with {solver.SolverVersion()}')\n",
" print(f\"Solving with {solver.SolverVersion()}\")\n",
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" print('Solution:')\n",
" print('Objective value =', solver.Objective().Value())\n",
" print('x =', x.solution_value())\n",
" print('y =', y.solution_value())\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", solver.Objective().Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print('Problem solved in %d iterations' % solver.iterations())\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
" print(\"Problem solved in %d iterations\" % solver.iterations())\n",
"\n",
"\n",
"main()\n",

View File

@@ -93,10 +93,10 @@
" model = model_builder.ModelBuilder()\n",
"\n",
" # Create the variables x and y.\n",
" x = model.new_num_var(0.0, math.inf, 'x')\n",
" y = model.new_num_var(0.0, math.inf, 'y')\n",
" x = model.new_num_var(0.0, math.inf, \"x\")\n",
" y = model.new_num_var(0.0, math.inf, \"y\")\n",
"\n",
" print('Number of variables =', model.num_variables)\n",
" print(\"Number of variables =\", model.num_variables)\n",
"\n",
" # x + 7 * y <= 17.5.\n",
" ct = model.add(x + 7 * y <= 17.5)\n",
@@ -104,28 +104,28 @@
" # x <= 3.5.\n",
" model.add(x <= 3.5)\n",
"\n",
" print('Number of constraints =', model.num_constraints)\n",
" print(\"Number of constraints =\", model.num_constraints)\n",
"\n",
" # Maximize x + 10 * y.\n",
" model.maximize(x + 10 * y)\n",
"\n",
" # Create the solver with the GLOP backend, and solve the model.\n",
" solver = model_builder.ModelSolver('glop')\n",
" solver = model_builder.ModelSolver(\"glop\")\n",
" status = solver.solve(model)\n",
"\n",
" if status == model_builder.SolveStatus.OPTIMAL:\n",
" print('Solution:')\n",
" print('Objective value =', solver.objective_value)\n",
" print('x =', solver.value(x))\n",
" print('y =', solver.value(y))\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", solver.objective_value)\n",
" print(\"x =\", solver.value(x))\n",
" print(\"y =\", solver.value(y))\n",
"\n",
" print('dual_value(ct) =', solver.dual_value(ct))\n",
" print('reduced_cost(x) =', solver.reduced_cost(x))\n",
" print(\"dual_value(ct) =\", solver.dual_value(ct))\n",
" print(\"reduced_cost(x) =\", solver.reduced_cost(x))\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in %f seconds' % solver.wall_time)\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in %f seconds\" % solver.wall_time)\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Integer programming examples that show how to use the APIs."
]
},
@@ -87,16 +88,16 @@
"\n",
"def main():\n",
" # Create the mip solver with the SCIP backend.\n",
" solver = pywraplp.Solver.CreateSolver('SAT')\n",
" solver = pywraplp.Solver.CreateSolver(\"SAT\")\n",
" if not solver:\n",
" return\n",
"\n",
" infinity = solver.infinity()\n",
" # x and y are integer non-negative variables.\n",
" x = solver.IntVar(0.0, infinity, 'x')\n",
" y = solver.IntVar(0.0, infinity, 'y')\n",
" x = solver.IntVar(0.0, infinity, \"x\")\n",
" y = solver.IntVar(0.0, infinity, \"y\")\n",
"\n",
" print('Number of variables =', solver.NumVariables())\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # x + 7 * y <= 17.5.\n",
" solver.Add(x + 7 * y <= 17.5)\n",
@@ -104,26 +105,26 @@
" # x <= 3.5.\n",
" solver.Add(x <= 3.5)\n",
"\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Maximize x + 10 * y.\n",
" solver.Maximize(x + 10 * y)\n",
"\n",
" print(f'Solving with {solver.SolverVersion()}')\n",
" print(f\"Solving with {solver.SolverVersion()}\")\n",
" status = solver.Solve()\n",
"\n",
" if status == pywraplp.Solver.OPTIMAL:\n",
" print('Solution:')\n",
" print('Objective value =', solver.Objective().Value())\n",
" print('x =', x.solution_value())\n",
" print('y =', y.solution_value())\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", solver.Objective().Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in %f milliseconds' % solver.wall_time())\n",
" print('Problem solved in %d iterations' % solver.iterations())\n",
" print('Problem solved in %d branch-and-bound nodes' % solver.nodes())\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in %f milliseconds\" % solver.wall_time())\n",
" print(\"Problem solved in %d iterations\" % solver.iterations())\n",
" print(\"Problem solved in %d branch-and-bound nodes\" % solver.nodes())\n",
"\n",
"\n",
"main()\n",

View File

@@ -93,10 +93,10 @@
" model = model_builder.ModelBuilder()\n",
"\n",
" # x and y are integer non-negative variables.\n",
" x = model.new_int_var(0.0, math.inf, 'x')\n",
" y = model.new_int_var(0.0, math.inf, 'y')\n",
" x = model.new_int_var(0.0, math.inf, \"x\")\n",
" y = model.new_int_var(0.0, math.inf, \"y\")\n",
"\n",
" print('Number of variables =', model.num_variables)\n",
" print(\"Number of variables =\", model.num_variables)\n",
"\n",
" # x + 7 * y <= 17.5.\n",
" model.add(x + 7 * y <= 17.5)\n",
@@ -104,25 +104,25 @@
" # x <= 3.5.\n",
" model.add(x <= 3.5)\n",
"\n",
" print('Number of constraints =', model.num_constraints)\n",
" print(\"Number of constraints =\", model.num_constraints)\n",
"\n",
" # Maximize x + 10 * y.\n",
" model.maximize(x + 10 * y)\n",
"\n",
" # Create the solver with the SCIP backend, and solve the model.\n",
" solver = model_builder.ModelSolver('scip')\n",
" solver = model_builder.ModelSolver(\"scip\")\n",
" status = solver.solve(model)\n",
"\n",
" if status == model_builder.SolveStatus.OPTIMAL:\n",
" print('Solution:')\n",
" print('Objective value =', solver.objective_value)\n",
" print('x =', solver.value(x))\n",
" print('y =', solver.value(y))\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", solver.objective_value)\n",
" print(\"x =\", solver.value(x))\n",
" print(\"y =\", solver.value(y))\n",
" else:\n",
" print('The problem does not have an optimal solution.')\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in %f seconds' % solver.wall_time)\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in %f seconds\" % solver.wall_time)\n",
"\n",
"\n",
"main()\n",

View File

@@ -94,208 +94,111 @@
" # Instantiate the data problem.\n",
" # Nutrient minimums.\n",
" nutrients = [\n",
" ['Calories (kcal)', 3],\n",
" ['Protein (g)', 70],\n",
" ['Calcium (g)', 0.8],\n",
" ['Iron (mg)', 12],\n",
" ['Vitamin A (KIU)', 5],\n",
" ['Vitamin B1 (mg)', 1.8],\n",
" ['Vitamin B2 (mg)', 2.7],\n",
" ['Niacin (mg)', 18],\n",
" ['Vitamin C (mg)', 75],\n",
" [\"Calories (kcal)\", 3],\n",
" [\"Protein (g)\", 70],\n",
" [\"Calcium (g)\", 0.8],\n",
" [\"Iron (mg)\", 12],\n",
" [\"Vitamin A (KIU)\", 5],\n",
" [\"Vitamin B1 (mg)\", 1.8],\n",
" [\"Vitamin B2 (mg)\", 2.7],\n",
" [\"Niacin (mg)\", 18],\n",
" [\"Vitamin C (mg)\", 75],\n",
" ]\n",
"\n",
" # Commodity, Unit, 1939 price (cents), Calories (kcal), Protein (g),\n",
" # Calcium (g), Iron (mg), Vitamin A (KIU), Vitamin B1 (mg), Vitamin B2 (mg),\n",
" # Niacin (mg), Vitamin C (mg)\n",
" data = [\n",
" [\n",
" 'Wheat Flour (Enriched)', '10 lb.', 36, 44.7, 1411, 2, 365, 0, 55.4,\n",
" 33.3, 441, 0\n",
" ],\n",
" ['Macaroni', '1 lb.', 14.1, 11.6, 418, 0.7, 54, 0, 3.2, 1.9, 68, 0],\n",
" [\n",
" 'Wheat Cereal (Enriched)', '28 oz.', 24.2, 11.8, 377, 14.4, 175, 0,\n",
" 14.4, 8.8, 114, 0\n",
" ],\n",
" ['Corn Flakes', '8 oz.', 7.1, 11.4, 252, 0.1, 56, 0, 13.5, 2.3, 68, 0],\n",
" [\n",
" 'Corn Meal', '1 lb.', 4.6, 36.0, 897, 1.7, 99, 30.9, 17.4, 7.9, 106,\n",
" 0\n",
" ],\n",
" [\n",
" 'Hominy Grits', '24 oz.', 8.5, 28.6, 680, 0.8, 80, 0, 10.6, 1.6,\n",
" 110, 0\n",
" ],\n",
" ['Rice', '1 lb.', 7.5, 21.2, 460, 0.6, 41, 0, 2, 4.8, 60, 0],\n",
" ['Rolled Oats', '1 lb.', 7.1, 25.3, 907, 5.1, 341, 0, 37.1, 8.9, 64, 0],\n",
" [\n",
" 'White Bread (Enriched)', '1 lb.', 7.9, 15.0, 488, 2.5, 115, 0,\n",
" 13.8, 8.5, 126, 0\n",
" ],\n",
" [\n",
" 'Whole Wheat Bread', '1 lb.', 9.1, 12.2, 484, 2.7, 125, 0, 13.9,\n",
" 6.4, 160, 0\n",
" ],\n",
" ['Rye Bread', '1 lb.', 9.1, 12.4, 439, 1.1, 82, 0, 9.9, 3, 66, 0],\n",
" ['Pound Cake', '1 lb.', 24.8, 8.0, 130, 0.4, 31, 18.9, 2.8, 3, 17, 0],\n",
" ['Soda Crackers', '1 lb.', 15.1, 12.5, 288, 0.5, 50, 0, 0, 0, 0, 0],\n",
" ['Milk', '1 qt.', 11, 6.1, 310, 10.5, 18, 16.8, 4, 16, 7, 177],\n",
" [\n",
" 'Evaporated Milk (can)', '14.5 oz.', 6.7, 8.4, 422, 15.1, 9, 26, 3,\n",
" 23.5, 11, 60\n",
" ],\n",
" ['Butter', '1 lb.', 30.8, 10.8, 9, 0.2, 3, 44.2, 0, 0.2, 2, 0],\n",
" ['Oleomargarine', '1 lb.', 16.1, 20.6, 17, 0.6, 6, 55.8, 0.2, 0, 0, 0],\n",
" ['Eggs', '1 doz.', 32.6, 2.9, 238, 1.0, 52, 18.6, 2.8, 6.5, 1, 0],\n",
" [\n",
" 'Cheese (Cheddar)', '1 lb.', 24.2, 7.4, 448, 16.4, 19, 28.1, 0.8,\n",
" 10.3, 4, 0\n",
" ],\n",
" ['Cream', '1/2 pt.', 14.1, 3.5, 49, 1.7, 3, 16.9, 0.6, 2.5, 0, 17],\n",
" [\n",
" 'Peanut Butter', '1 lb.', 17.9, 15.7, 661, 1.0, 48, 0, 9.6, 8.1,\n",
" 471, 0\n",
" ],\n",
" ['Mayonnaise', '1/2 pt.', 16.7, 8.6, 18, 0.2, 8, 2.7, 0.4, 0.5, 0, 0],\n",
" ['Crisco', '1 lb.', 20.3, 20.1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" ['Lard', '1 lb.', 9.8, 41.7, 0, 0, 0, 0.2, 0, 0.5, 5, 0],\n",
" [\n",
" 'Sirloin Steak', '1 lb.', 39.6, 2.9, 166, 0.1, 34, 0.2, 2.1, 2.9,\n",
" 69, 0\n",
" ],\n",
" ['Round Steak', '1 lb.', 36.4, 2.2, 214, 0.1, 32, 0.4, 2.5, 2.4, 87, 0],\n",
" ['Rib Roast', '1 lb.', 29.2, 3.4, 213, 0.1, 33, 0, 0, 2, 0, 0],\n",
" ['Chuck Roast', '1 lb.', 22.6, 3.6, 309, 0.2, 46, 0.4, 1, 4, 120, 0],\n",
" ['Plate', '1 lb.', 14.6, 8.5, 404, 0.2, 62, 0, 0.9, 0, 0, 0],\n",
" [\n",
" 'Liver (Beef)', '1 lb.', 26.8, 2.2, 333, 0.2, 139, 169.2, 6.4, 50.8,\n",
" 316, 525\n",
" ],\n",
" ['Leg of Lamb', '1 lb.', 27.6, 3.1, 245, 0.1, 20, 0, 2.8, 3.9, 86, 0],\n",
" [\n",
" 'Lamb Chops (Rib)', '1 lb.', 36.6, 3.3, 140, 0.1, 15, 0, 1.7, 2.7,\n",
" 54, 0\n",
" ],\n",
" ['Pork Chops', '1 lb.', 30.7, 3.5, 196, 0.2, 30, 0, 17.4, 2.7, 60, 0],\n",
" [\n",
" 'Pork Loin Roast', '1 lb.', 24.2, 4.4, 249, 0.3, 37, 0, 18.2, 3.6,\n",
" 79, 0\n",
" ],\n",
" ['Bacon', '1 lb.', 25.6, 10.4, 152, 0.2, 23, 0, 1.8, 1.8, 71, 0],\n",
" ['Ham, smoked', '1 lb.', 27.4, 6.7, 212, 0.2, 31, 0, 9.9, 3.3, 50, 0],\n",
" ['Salt Pork', '1 lb.', 16, 18.8, 164, 0.1, 26, 0, 1.4, 1.8, 0, 0],\n",
" [\n",
" 'Roasting Chicken', '1 lb.', 30.3, 1.8, 184, 0.1, 30, 0.1, 0.9, 1.8,\n",
" 68, 46\n",
" ],\n",
" ['Veal Cutlets', '1 lb.', 42.3, 1.7, 156, 0.1, 24, 0, 1.4, 2.4, 57, 0],\n",
" [\n",
" 'Salmon, Pink (can)', '16 oz.', 13, 5.8, 705, 6.8, 45, 3.5, 1, 4.9,\n",
" 209, 0\n",
" ],\n",
" ['Apples', '1 lb.', 4.4, 5.8, 27, 0.5, 36, 7.3, 3.6, 2.7, 5, 544],\n",
" ['Bananas', '1 lb.', 6.1, 4.9, 60, 0.4, 30, 17.4, 2.5, 3.5, 28, 498],\n",
" ['Lemons', '1 doz.', 26, 1.0, 21, 0.5, 14, 0, 0.5, 0, 4, 952],\n",
" ['Oranges', '1 doz.', 30.9, 2.2, 40, 1.1, 18, 11.1, 3.6, 1.3, 10, 1998],\n",
" ['Green Beans', '1 lb.', 7.1, 2.4, 138, 3.7, 80, 69, 4.3, 5.8, 37, 862],\n",
" ['Cabbage', '1 lb.', 3.7, 2.6, 125, 4.0, 36, 7.2, 9, 4.5, 26, 5369],\n",
" ['Carrots', '1 bunch', 4.7, 2.7, 73, 2.8, 43, 188.5, 6.1, 4.3, 89, 608],\n",
" ['Celery', '1 stalk', 7.3, 0.9, 51, 3.0, 23, 0.9, 1.4, 1.4, 9, 313],\n",
" ['Lettuce', '1 head', 8.2, 0.4, 27, 1.1, 22, 112.4, 1.8, 3.4, 11, 449],\n",
" ['Onions', '1 lb.', 3.6, 5.8, 166, 3.8, 59, 16.6, 4.7, 5.9, 21, 1184],\n",
" [\n",
" 'Potatoes', '15 lb.', 34, 14.3, 336, 1.8, 118, 6.7, 29.4, 7.1, 198,\n",
" 2522\n",
" ],\n",
" ['Spinach', '1 lb.', 8.1, 1.1, 106, 0, 138, 918.4, 5.7, 13.8, 33, 2755],\n",
" [\n",
" 'Sweet Potatoes', '1 lb.', 5.1, 9.6, 138, 2.7, 54, 290.7, 8.4, 5.4,\n",
" 83, 1912\n",
" ],\n",
" [\n",
" 'Peaches (can)', 'No. 2 1/2', 16.8, 3.7, 20, 0.4, 10, 21.5, 0.5, 1,\n",
" 31, 196\n",
" ],\n",
" [\n",
" 'Pears (can)', 'No. 2 1/2', 20.4, 3.0, 8, 0.3, 8, 0.8, 0.8, 0.8, 5,\n",
" 81\n",
" ],\n",
" [\n",
" 'Pineapple (can)', 'No. 2 1/2', 21.3, 2.4, 16, 0.4, 8, 2, 2.8, 0.8,\n",
" 7, 399\n",
" ],\n",
" [\n",
" 'Asparagus (can)', 'No. 2', 27.7, 0.4, 33, 0.3, 12, 16.3, 1.4, 2.1,\n",
" 17, 272\n",
" ],\n",
" [\n",
" 'Green Beans (can)', 'No. 2', 10, 1.0, 54, 2, 65, 53.9, 1.6, 4.3,\n",
" 32, 431\n",
" ],\n",
" [\n",
" 'Pork and Beans (can)', '16 oz.', 7.1, 7.5, 364, 4, 134, 3.5, 8.3,\n",
" 7.7, 56, 0\n",
" ],\n",
" ['Corn (can)', 'No. 2', 10.4, 5.2, 136, 0.2, 16, 12, 1.6, 2.7, 42, 218],\n",
" [\n",
" 'Peas (can)', 'No. 2', 13.8, 2.3, 136, 0.6, 45, 34.9, 4.9, 2.5, 37,\n",
" 370\n",
" ],\n",
" [\n",
" 'Tomatoes (can)', 'No. 2', 8.6, 1.3, 63, 0.7, 38, 53.2, 3.4, 2.5,\n",
" 36, 1253\n",
" ],\n",
" [\n",
" 'Tomato Soup (can)', '10 1/2 oz.', 7.6, 1.6, 71, 0.6, 43, 57.9, 3.5,\n",
" 2.4, 67, 862\n",
" ],\n",
" [\n",
" 'Peaches, Dried', '1 lb.', 15.7, 8.5, 87, 1.7, 173, 86.8, 1.2, 4.3,\n",
" 55, 57\n",
" ],\n",
" [\n",
" 'Prunes, Dried', '1 lb.', 9, 12.8, 99, 2.5, 154, 85.7, 3.9, 4.3, 65,\n",
" 257\n",
" ],\n",
" [\n",
" 'Raisins, Dried', '15 oz.', 9.4, 13.5, 104, 2.5, 136, 4.5, 6.3, 1.4,\n",
" 24, 136\n",
" ],\n",
" [\n",
" 'Peas, Dried', '1 lb.', 7.9, 20.0, 1367, 4.2, 345, 2.9, 28.7, 18.4,\n",
" 162, 0\n",
" ],\n",
" [\n",
" 'Lima Beans, Dried', '1 lb.', 8.9, 17.4, 1055, 3.7, 459, 5.1, 26.9,\n",
" 38.2, 93, 0\n",
" ],\n",
" [\n",
" 'Navy Beans, Dried', '1 lb.', 5.9, 26.9, 1691, 11.4, 792, 0, 38.4,\n",
" 24.6, 217, 0\n",
" ],\n",
" ['Coffee', '1 lb.', 22.4, 0, 0, 0, 0, 0, 4, 5.1, 50, 0],\n",
" ['Tea', '1/4 lb.', 17.4, 0, 0, 0, 0, 0, 0, 2.3, 42, 0],\n",
" ['Cocoa', '8 oz.', 8.6, 8.7, 237, 3, 72, 0, 2, 11.9, 40, 0],\n",
" ['Chocolate', '8 oz.', 16.2, 8.0, 77, 1.3, 39, 0, 0.9, 3.4, 14, 0],\n",
" ['Sugar', '10 lb.', 51.7, 34.9, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" ['Corn Syrup', '24 oz.', 13.7, 14.7, 0, 0.5, 74, 0, 0, 0, 5, 0],\n",
" ['Molasses', '18 oz.', 13.6, 9.0, 0, 10.3, 244, 0, 1.9, 7.5, 146, 0],\n",
" [\n",
" 'Strawberry Preserves', '1 lb.', 20.5, 6.4, 11, 0.4, 7, 0.2, 0.2,\n",
" 0.4, 3, 0\n",
" ],\n",
" # fmt: off\n",
" ['Wheat Flour (Enriched)', '10 lb.', 36, 44.7, 1411, 2, 365, 0, 55.4, 33.3, 441, 0],\n",
" ['Macaroni', '1 lb.', 14.1, 11.6, 418, 0.7, 54, 0, 3.2, 1.9, 68, 0],\n",
" ['Wheat Cereal (Enriched)', '28 oz.', 24.2, 11.8, 377, 14.4, 175, 0, 14.4, 8.8, 114, 0],\n",
" ['Corn Flakes', '8 oz.', 7.1, 11.4, 252, 0.1, 56, 0, 13.5, 2.3, 68, 0],\n",
" ['Corn Meal', '1 lb.', 4.6, 36.0, 897, 1.7, 99, 30.9, 17.4, 7.9, 106, 0],\n",
" ['Hominy Grits', '24 oz.', 8.5, 28.6, 680, 0.8, 80, 0, 10.6, 1.6, 110, 0],\n",
" ['Rice', '1 lb.', 7.5, 21.2, 460, 0.6, 41, 0, 2, 4.8, 60, 0],\n",
" ['Rolled Oats', '1 lb.', 7.1, 25.3, 907, 5.1, 341, 0, 37.1, 8.9, 64, 0],\n",
" ['White Bread (Enriched)', '1 lb.', 7.9, 15.0, 488, 2.5, 115, 0, 13.8, 8.5, 126, 0],\n",
" ['Whole Wheat Bread', '1 lb.', 9.1, 12.2, 484, 2.7, 125, 0, 13.9, 6.4, 160, 0],\n",
" ['Rye Bread', '1 lb.', 9.1, 12.4, 439, 1.1, 82, 0, 9.9, 3, 66, 0],\n",
" ['Pound Cake', '1 lb.', 24.8, 8.0, 130, 0.4, 31, 18.9, 2.8, 3, 17, 0],\n",
" ['Soda Crackers', '1 lb.', 15.1, 12.5, 288, 0.5, 50, 0, 0, 0, 0, 0],\n",
" ['Milk', '1 qt.', 11, 6.1, 310, 10.5, 18, 16.8, 4, 16, 7, 177],\n",
" ['Evaporated Milk (can)', '14.5 oz.', 6.7, 8.4, 422, 15.1, 9, 26, 3, 23.5, 11, 60],\n",
" ['Butter', '1 lb.', 30.8, 10.8, 9, 0.2, 3, 44.2, 0, 0.2, 2, 0],\n",
" ['Oleomargarine', '1 lb.', 16.1, 20.6, 17, 0.6, 6, 55.8, 0.2, 0, 0, 0],\n",
" ['Eggs', '1 doz.', 32.6, 2.9, 238, 1.0, 52, 18.6, 2.8, 6.5, 1, 0],\n",
" ['Cheese (Cheddar)', '1 lb.', 24.2, 7.4, 448, 16.4, 19, 28.1, 0.8, 10.3, 4, 0],\n",
" ['Cream', '1/2 pt.', 14.1, 3.5, 49, 1.7, 3, 16.9, 0.6, 2.5, 0, 17],\n",
" ['Peanut Butter', '1 lb.', 17.9, 15.7, 661, 1.0, 48, 0, 9.6, 8.1, 471, 0],\n",
" ['Mayonnaise', '1/2 pt.', 16.7, 8.6, 18, 0.2, 8, 2.7, 0.4, 0.5, 0, 0],\n",
" ['Crisco', '1 lb.', 20.3, 20.1, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" ['Lard', '1 lb.', 9.8, 41.7, 0, 0, 0, 0.2, 0, 0.5, 5, 0],\n",
" ['Sirloin Steak', '1 lb.', 39.6, 2.9, 166, 0.1, 34, 0.2, 2.1, 2.9, 69, 0],\n",
" ['Round Steak', '1 lb.', 36.4, 2.2, 214, 0.1, 32, 0.4, 2.5, 2.4, 87, 0],\n",
" ['Rib Roast', '1 lb.', 29.2, 3.4, 213, 0.1, 33, 0, 0, 2, 0, 0],\n",
" ['Chuck Roast', '1 lb.', 22.6, 3.6, 309, 0.2, 46, 0.4, 1, 4, 120, 0],\n",
" ['Plate', '1 lb.', 14.6, 8.5, 404, 0.2, 62, 0, 0.9, 0, 0, 0],\n",
" ['Liver (Beef)', '1 lb.', 26.8, 2.2, 333, 0.2, 139, 169.2, 6.4, 50.8, 316, 525],\n",
" ['Leg of Lamb', '1 lb.', 27.6, 3.1, 245, 0.1, 20, 0, 2.8, 3.9, 86, 0],\n",
" ['Lamb Chops (Rib)', '1 lb.', 36.6, 3.3, 140, 0.1, 15, 0, 1.7, 2.7, 54, 0],\n",
" ['Pork Chops', '1 lb.', 30.7, 3.5, 196, 0.2, 30, 0, 17.4, 2.7, 60, 0],\n",
" ['Pork Loin Roast', '1 lb.', 24.2, 4.4, 249, 0.3, 37, 0, 18.2, 3.6, 79, 0],\n",
" ['Bacon', '1 lb.', 25.6, 10.4, 152, 0.2, 23, 0, 1.8, 1.8, 71, 0],\n",
" ['Ham, smoked', '1 lb.', 27.4, 6.7, 212, 0.2, 31, 0, 9.9, 3.3, 50, 0],\n",
" ['Salt Pork', '1 lb.', 16, 18.8, 164, 0.1, 26, 0, 1.4, 1.8, 0, 0],\n",
" ['Roasting Chicken', '1 lb.', 30.3, 1.8, 184, 0.1, 30, 0.1, 0.9, 1.8, 68, 46],\n",
" ['Veal Cutlets', '1 lb.', 42.3, 1.7, 156, 0.1, 24, 0, 1.4, 2.4, 57, 0],\n",
" ['Salmon, Pink (can)', '16 oz.', 13, 5.8, 705, 6.8, 45, 3.5, 1, 4.9, 209, 0],\n",
" ['Apples', '1 lb.', 4.4, 5.8, 27, 0.5, 36, 7.3, 3.6, 2.7, 5, 544],\n",
" ['Bananas', '1 lb.', 6.1, 4.9, 60, 0.4, 30, 17.4, 2.5, 3.5, 28, 498],\n",
" ['Lemons', '1 doz.', 26, 1.0, 21, 0.5, 14, 0, 0.5, 0, 4, 952],\n",
" ['Oranges', '1 doz.', 30.9, 2.2, 40, 1.1, 18, 11.1, 3.6, 1.3, 10, 1998],\n",
" ['Green Beans', '1 lb.', 7.1, 2.4, 138, 3.7, 80, 69, 4.3, 5.8, 37, 862],\n",
" ['Cabbage', '1 lb.', 3.7, 2.6, 125, 4.0, 36, 7.2, 9, 4.5, 26, 5369],\n",
" ['Carrots', '1 bunch', 4.7, 2.7, 73, 2.8, 43, 188.5, 6.1, 4.3, 89, 608],\n",
" ['Celery', '1 stalk', 7.3, 0.9, 51, 3.0, 23, 0.9, 1.4, 1.4, 9, 313],\n",
" ['Lettuce', '1 head', 8.2, 0.4, 27, 1.1, 22, 112.4, 1.8, 3.4, 11, 449],\n",
" ['Onions', '1 lb.', 3.6, 5.8, 166, 3.8, 59, 16.6, 4.7, 5.9, 21, 1184],\n",
" ['Potatoes', '15 lb.', 34, 14.3, 336, 1.8, 118, 6.7, 29.4, 7.1, 198, 2522],\n",
" ['Spinach', '1 lb.', 8.1, 1.1, 106, 0, 138, 918.4, 5.7, 13.8, 33, 2755],\n",
" ['Sweet Potatoes', '1 lb.', 5.1, 9.6, 138, 2.7, 54, 290.7, 8.4, 5.4, 83, 1912],\n",
" ['Peaches (can)', 'No. 2 1/2', 16.8, 3.7, 20, 0.4, 10, 21.5, 0.5, 1, 31, 196],\n",
" ['Pears (can)', 'No. 2 1/2', 20.4, 3.0, 8, 0.3, 8, 0.8, 0.8, 0.8, 5, 81],\n",
" ['Pineapple (can)', 'No. 2 1/2', 21.3, 2.4, 16, 0.4, 8, 2, 2.8, 0.8, 7, 399],\n",
" ['Asparagus (can)', 'No. 2', 27.7, 0.4, 33, 0.3, 12, 16.3, 1.4, 2.1, 17, 272],\n",
" ['Green Beans (can)', 'No. 2', 10, 1.0, 54, 2, 65, 53.9, 1.6, 4.3, 32, 431],\n",
" ['Pork and Beans (can)', '16 oz.', 7.1, 7.5, 364, 4, 134, 3.5, 8.3, 7.7, 56, 0],\n",
" ['Corn (can)', 'No. 2', 10.4, 5.2, 136, 0.2, 16, 12, 1.6, 2.7, 42, 218],\n",
" ['Peas (can)', 'No. 2', 13.8, 2.3, 136, 0.6, 45, 34.9, 4.9, 2.5, 37, 370],\n",
" ['Tomatoes (can)', 'No. 2', 8.6, 1.3, 63, 0.7, 38, 53.2, 3.4, 2.5, 36, 1253],\n",
" ['Tomato Soup (can)', '10 1/2 oz.', 7.6, 1.6, 71, 0.6, 43, 57.9, 3.5, 2.4, 67, 862],\n",
" ['Peaches, Dried', '1 lb.', 15.7, 8.5, 87, 1.7, 173, 86.8, 1.2, 4.3, 55, 57],\n",
" ['Prunes, Dried', '1 lb.', 9, 12.8, 99, 2.5, 154, 85.7, 3.9, 4.3, 65, 257],\n",
" ['Raisins, Dried', '15 oz.', 9.4, 13.5, 104, 2.5, 136, 4.5, 6.3, 1.4, 24, 136],\n",
" ['Peas, Dried', '1 lb.', 7.9, 20.0, 1367, 4.2, 345, 2.9, 28.7, 18.4, 162, 0],\n",
" ['Lima Beans, Dried', '1 lb.', 8.9, 17.4, 1055, 3.7, 459, 5.1, 26.9, 38.2, 93, 0],\n",
" ['Navy Beans, Dried', '1 lb.', 5.9, 26.9, 1691, 11.4, 792, 0, 38.4, 24.6, 217, 0],\n",
" ['Coffee', '1 lb.', 22.4, 0, 0, 0, 0, 0, 4, 5.1, 50, 0],\n",
" ['Tea', '1/4 lb.', 17.4, 0, 0, 0, 0, 0, 0, 2.3, 42, 0],\n",
" ['Cocoa', '8 oz.', 8.6, 8.7, 237, 3, 72, 0, 2, 11.9, 40, 0],\n",
" ['Chocolate', '8 oz.', 16.2, 8.0, 77, 1.3, 39, 0, 0.9, 3.4, 14, 0],\n",
" ['Sugar', '10 lb.', 51.7, 34.9, 0, 0, 0, 0, 0, 0, 0, 0],\n",
" ['Corn Syrup', '24 oz.', 13.7, 14.7, 0, 0.5, 74, 0, 0, 0, 5, 0],\n",
" ['Molasses', '18 oz.', 13.6, 9.0, 0, 10.3, 244, 0, 1.9, 7.5, 146, 0],\n",
" ['Strawberry Preserves', '1 lb.', 20.5, 6.4, 11, 0.4, 7, 0.2, 0.2, 0.4, 3, 0],\n",
" # fmt: on\n",
" ]\n",
"\n",
" # Instantiate a Glop solver and naming it.\n",
" solver = pywraplp.Solver.CreateSolver('GLOP')\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\n",
" return\n",
"\n",
" # Declare an array to hold our variables.\n",
" foods = [solver.NumVar(0.0, solver.infinity(), item[0]) for item in data]\n",
"\n",
" print('Number of variables =', solver.NumVariables())\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # Create the constraints, one per nutrient.\n",
" constraints = []\n",
@@ -304,7 +207,7 @@
" for j, item in enumerate(data):\n",
" constraints[i].SetCoefficient(foods[j], item[i + 3])\n",
"\n",
" print('Number of constraints =', solver.NumConstraints())\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Objective function: Minimize the sum of (price-normalized) foods.\n",
" objective = solver.Objective()\n",
@@ -316,31 +219,32 @@
"\n",
" # Check that the problem has an optimal solution.\n",
" if status != solver.OPTIMAL:\n",
" print('The problem does not have an optimal solution!')\n",
" print(\"The problem does not have an optimal solution!\")\n",
" if status == solver.FEASIBLE:\n",
" print('A potentially suboptimal solution was found.')\n",
" print(\"A potentially suboptimal solution was found.\")\n",
" else:\n",
" print('The solver could not solve the problem.')\n",
" print(\"The solver could not solve the problem.\")\n",
" exit(1)\n",
"\n",
" # Display the amounts (in dollars) to purchase of each food.\n",
" nutrients_result = [0] * len(nutrients)\n",
" print('\\nAnnual Foods:')\n",
" print(\"\\nAnnual Foods:\")\n",
" for i, food in enumerate(foods):\n",
" if food.solution_value() > 0.0:\n",
" print('{}: ${}'.format(data[i][0], 365. * food.solution_value()))\n",
" print(\"{}: ${}\".format(data[i][0], 365.0 * food.solution_value()))\n",
" for j, _ in enumerate(nutrients):\n",
" nutrients_result[j] += data[i][j + 3] * food.solution_value()\n",
" print('\\nOptimal annual price: ${:.4f}'.format(365. * objective.Value()))\n",
" print(\"\\nOptimal annual price: ${:.4f}\".format(365.0 * objective.Value()))\n",
"\n",
" print('\\nNutrients per day:')\n",
" print(\"\\nNutrients per day:\")\n",
" for i, nutrient in enumerate(nutrients):\n",
" print('{}: {:.2f} (min {})'.format(nutrient[0], nutrients_result[i],\n",
" nutrient[1]))\n",
" print(\n",
" \"{}: {:.2f} (min {})\".format(nutrient[0], nutrients_result[i], nutrient[1])\n",
" )\n",
"\n",
" print('\\nAdvanced usage:')\n",
" print('Problem solved in ', solver.wall_time(), ' milliseconds')\n",
" print('Problem solved in ', solver.iterations(), ' iterations')\n",
" print(\"\\nAdvanced usage:\")\n",
" print(\"Problem solved in \", solver.wall_time(), \" milliseconds\")\n",
" print(\"Problem solved in \", solver.iterations(), \" iterations\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solves a simple LP using PDLP's direct Python API.\n",
"\n",
"Note: The direct API is generally for advanced use cases. It is matrix-based,\n",
@@ -93,24 +94,24 @@
"\n",
"from ortools.pdlp import solve_log_pb2\n",
"from ortools.pdlp import solvers_pb2\n",
"from ortools.pdlp.python import pywrap_pdlp\n",
"from ortools.init import pywrapinit\n",
"from ortools.pdlp.python import pdlp\n",
"from ortools.init.python import init\n",
"\n",
"\n",
"def simple_lp() -> pywrap_pdlp.QuadraticProgram:\n",
"def simple_lp() -> pdlp.QuadraticProgram:\n",
" \"\"\"Returns a small LP.\n",
"\n",
" min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t.\n",
" 2 x_0 + x_1 + x_2 + 2 x_3 = 12\n",
" x_0 + x_2 <= 7\n",
" 4 x_0 >= -4\n",
" -1 <= 1.5 x_2 - x_3 <= 1\n",
" -infinity <= x_0 <= infinity\n",
" -2 <= x_1 <= infinity\n",
" -infinity <= x_2 <= 6\n",
" 2.5 <= x_3 <= 3.5\n",
" \"\"\"\n",
" lp = pywrap_pdlp.QuadraticProgram()\n",
" min 5.5 x_0 - 2 x_1 - x_2 + x_3 - 14 s.t.\n",
" 2 x_0 + x_1 + x_2 + 2 x_3 = 12\n",
" x_0 + x_2 <= 7\n",
" 4 x_0 >= -4\n",
" -1 <= 1.5 x_2 - x_3 <= 1\n",
" -infinity <= x_0 <= infinity\n",
" -2 <= x_1 <= infinity\n",
" -infinity <= x_2 <= 6\n",
" 2.5 <= x_3 <= 3.5\n",
" \"\"\"\n",
" lp = pdlp.QuadraticProgram()\n",
" lp.objective_offset = -14\n",
" lp.objective_vector = [5.5, -2, -1, 1]\n",
" lp.constraint_lower_bounds = [12, -np.inf, -4, -1]\n",
@@ -120,8 +121,9 @@
" # Most use cases should initialize the sparse constraint matrix without\n",
" # constructing a dense matrix first! We use a np.array here for convenience\n",
" # only.\n",
" constraint_matrix = np.array([[2, 1, 1, 2], [1, 0, 1, 0], [4, 0, 0, 0],\n",
" [0, 0, 1.5, -1]])\n",
" constraint_matrix = np.array(\n",
" [[2, 1, 1, 2], [1, 0, 1, 0], [4, 0, 0, 0], [0, 0, 1.5, -1]]\n",
" )\n",
" lp.constraint_matrix = scipy.sparse.csc_matrix(constraint_matrix)\n",
" return lp\n",
"\n",
@@ -140,41 +142,41 @@
"\n",
" # Call the main solve function. Note that a quirk of the pywrap11 API forces\n",
" # us to serialize the `params` and deserialize the `solve_log` proto messages.\n",
" result = pywrap_pdlp.primal_dual_hybrid_gradient(simple_lp(),\n",
" params.SerializeToString())\n",
" result = pdlp.primal_dual_hybrid_gradient(simple_lp(), params.SerializeToString())\n",
" solve_log = solve_log_pb2.SolveLog.FromString(result.solve_log_str)\n",
"\n",
" if solve_log.termination_reason == solve_log_pb2.TERMINATION_REASON_OPTIMAL:\n",
" print('Solve successful')\n",
" print(\"Solve successful\")\n",
" else:\n",
" print(\n",
" 'Solve not successful. Status:',\n",
" solve_log_pb2.TerminationReason.Name(solve_log.termination_reason))\n",
" \"Solve not successful. Status:\",\n",
" solve_log_pb2.TerminationReason.Name(solve_log.termination_reason),\n",
" )\n",
"\n",
" # Solutions vectors are always returned. *However*, their interpretation\n",
" # depends on termination_reason! See primal_dual_hybrid_gradient.h for more\n",
" # details on what the vectors mean if termination_reason is not\n",
" # TERMINATION_REASON_OPTIMAL.\n",
" print('Primal solution:', result.primal_solution)\n",
" print('Dual solution:', result.dual_solution)\n",
" print('Reduced costs:', result.reduced_costs)\n",
" print(\"Primal solution:\", result.primal_solution)\n",
" print(\"Dual solution:\", result.dual_solution)\n",
" print(\"Reduced costs:\", result.reduced_costs)\n",
"\n",
" solution_type = solve_log.solution_type\n",
" print('Solution type:', solve_log_pb2.PointType.Name(solution_type))\n",
" print(\"Solution type:\", solve_log_pb2.PointType.Name(solution_type))\n",
" for ci in solve_log.solution_stats.convergence_information:\n",
" if ci.candidate_type == solution_type:\n",
" print('Primal objective:', ci.primal_objective)\n",
" print('Dual objective:', ci.dual_objective)\n",
" print(\"Primal objective:\", ci.primal_objective)\n",
" print(\"Dual objective:\", ci.dual_objective)\n",
"\n",
" print('Iterations:', solve_log.iteration_count)\n",
" print('Solve time (sec):', solve_log.solve_time_sec)\n",
" print(\"Iterations:\", solve_log.iteration_count)\n",
" print(\"Solve time (sec):\", solve_log.solve_time_sec)\n",
"\n",
"\n",
"pywrapinit.CppBridge.InitLogging('simple_pdlp_program.py')\n",
"cpp_flags = pywrapinit.CppFlags()\n",
"cpp_flags.logtostderr = True\n",
"init.CppBridge.init_logging(\"simple_pdlp_program.py\")\n",
"cpp_flags = init.CppFlags()\n",
"cpp_flags.stderrthreshold = 0\n",
"cpp_flags.log_prefix = False\n",
"pywrapinit.CppBridge.SetFlags(cpp_flags)\n",
"init.CppBridge.set_flags(cpp_flags)\n",
"main()\n",
"\n"
]

View File

@@ -137,7 +137,7 @@
" 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.NewBoolVar(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
@@ -151,12 +151,11 @@
" # 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.NewBoolVar(f\"work[{worker}]\")\n",
"\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" model.Add(work[worker] == sum(\n",
" 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",
@@ -176,14 +175,16 @@
"\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.ObjectiveValue()}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost = {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Solve a simple assignment problem."
]
},
@@ -82,47 +83,58 @@
"metadata": {},
"outputs": [],
"source": [
"import io\n",
"\n",
"import pandas as pd\n",
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def main():\n",
" # Data\n",
" costs = [\n",
" [90, 80, 75, 70],\n",
" [35, 85, 55, 65],\n",
" [125, 95, 90, 95],\n",
" [45, 110, 95, 115],\n",
" [50, 100, 90, 100],\n",
" ]\n",
" num_workers = len(costs)\n",
" num_tasks = len(costs[0])\n",
" data_str = \"\"\"\n",
" worker task cost\n",
" w1 t1 90\n",
" w1 t2 80\n",
" w1 t3 75\n",
" w1 t4 70\n",
" w2 t1 35\n",
" w2 t2 85\n",
" w2 t3 55\n",
" w2 t4 65\n",
" w3 t1 125\n",
" w3 t2 95\n",
" w3 t3 90\n",
" w3 t4 95\n",
" w4 t1 45\n",
" w4 t2 110\n",
" w4 t3 95\n",
" w4 t4 115\n",
" w5 t1 50\n",
" w5 t2 110\n",
" w5 t3 90\n",
" w5 t4 100\n",
" \"\"\"\n",
"\n",
" data = pd.read_table(io.StringIO(data_str), sep=r\"\\s+\")\n",
"\n",
" # Model\n",
" model = cp_model.CpModel()\n",
"\n",
" # Variables\n",
" x = []\n",
" for i in range(num_workers):\n",
" t = []\n",
" for j in range(num_tasks):\n",
" t.append(model.NewBoolVar(f'x[{i},{j}]'))\n",
" x.append(t)\n",
" x = model.NewBoolVarSeries(name=\"x\", index=data.index)\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
" for i in range(num_workers):\n",
" model.AddAtMostOne(x[i][j] for j in range(num_tasks))\n",
" for unused_name, tasks in data.groupby(\"worker\"):\n",
" model.AddAtMostOne(x[tasks.index])\n",
"\n",
" # Each task is assigned to exactly one worker.\n",
" for j in range(num_tasks):\n",
" model.AddExactlyOne(x[i][j] for i in range(num_workers))\n",
" for unused_name, workers in data.groupby(\"task\"):\n",
" model.AddExactlyOne(x[workers.index])\n",
"\n",
" # Objective\n",
" objective_terms = []\n",
" for i in range(num_workers):\n",
" for j in range(num_tasks):\n",
" objective_terms.append(costs[i][j] * x[i][j])\n",
" model.Minimize(sum(objective_terms))\n",
" model.Minimize(data.cost.dot(x))\n",
"\n",
" # Solve\n",
" solver = cp_model.CpSolver()\n",
@@ -130,15 +142,12 @@
"\n",
" # Print solution.\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" print(f'Total cost = {solver.ObjectiveValue()}')\n",
" print()\n",
" for i in range(num_workers):\n",
" for j in range(num_tasks):\n",
" if solver.BooleanValue(x[i][j]):\n",
" print(\n",
" f'Worker {i} assigned to task {j} Cost = {costs[i][j]}')\n",
" print(f\"Total cost = {solver.ObjectiveValue()}\\n\")\n",
" selected = data.loc[solver.BooleanValues(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",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -114,14 +114,15 @@
" 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.NewBoolVar(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",
" sum(task_sizes[task] * x[worker, task]\n",
" for task in range(num_tasks)) <= total_size_max)\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",
@@ -140,14 +141,16 @@
"\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.ObjectiveValue()}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost = {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -111,7 +111,7 @@
" 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.NewBoolVar(f\"x[{worker},{task}]\")\n",
"\n",
" # Constraints\n",
" # Each worker is assigned to at most one task.\n",
@@ -148,14 +148,16 @@
"\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.ObjectiveValue()}\\n\")\n",
" for worker in range(num_workers):\n",
" for task in range(num_tasks):\n",
" if solver.BooleanValue(x[worker, task]):\n",
" print(f'Worker {worker} assigned to task {task}.' +\n",
" f' Cost = {costs[worker][task]}')\n",
" print(\n",
" f\"Worker {worker} assigned to task {task}.\"\n",
" + f\" Cost = {costs[worker][task]}\"\n",
" )\n",
" else:\n",
" print('No solution found.')\n",
" print(\"No solution found.\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -72,6 +72,7 @@
"id": "description",
"metadata": {},
"source": [
"\n",
"Code sample that solves a model and gets the infeasibility assumptions."
]
},
@@ -91,12 +92,12 @@
" 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.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",
"\n",
" # Creates the constraints.\n",
" model.Add(x > y).OnlyEnforceIf(a)\n",
@@ -111,10 +112,12 @@
" status = solver.Solve(model)\n",
"\n",
" # Print solution.\n",
" print(f'Status = {solver.StatusName(status)}')\n",
" print(f\"Status = {solver.StatusName(status)}\")\n",
" if status == cp_model.INFEASIBLE:\n",
" print('SufficientAssumptionsForInfeasibility = '\n",
" f'{solver.SufficientAssumptionsForInfeasibility()}')\n",
" print(\n",
" \"SufficientAssumptionsForInfeasibility = \"\n",
" f\"{solver.SufficientAssumptionsForInfeasibility()}\"\n",
" )\n",
"\n",
"\n",
"main()\n",

View File

@@ -0,0 +1,193 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2022 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": [
"# bin_packing_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/bin_packing_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/bin_packing_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",
"Solve a simple bin packing problem using a MIP solver."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"import io\n",
"\n",
"import pandas as pd\n",
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Create the data for the example.\"\"\"\n",
"\n",
" items_str = \"\"\"\n",
" item weight\n",
" i1 48\n",
" i2 30\n",
" i3 19\n",
" i4 36\n",
" i5 36\n",
" i6 27\n",
" i7 42\n",
" i8 42\n",
" i9 36\n",
" i10 24\n",
" i11 30\n",
" \"\"\"\n",
"\n",
" bins_str = \"\"\"\n",
" bin capacity\n",
" b1 100\n",
" b2 100\n",
" b3 100\n",
" b4 100\n",
" b5 100\n",
" b6 100\n",
" b7 100\n",
" \"\"\"\n",
"\n",
" items = pd.read_table(io.StringIO(items_str), index_col=0, sep=r\"\\s+\")\n",
" bins = pd.read_table(io.StringIO(bins_str), index_col=0, sep=r\"\\s+\")\n",
" return items, bins\n",
"\n",
"\n",
"def main():\n",
" items, bins = create_data_model()\n",
"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Variables\n",
" # x[i, j] = 1 if item i is packed in bin j.\n",
" 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",
"\n",
" # y[j] = 1 if bin j is used.\n",
" y = model.NewBoolVarSeries(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",
"\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",
" 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",
"\n",
" # Create the solver with the CP-SAT backend, and solve the model.\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL:\n",
" print(f\"Number of bins used = {solver.ObjectiveValue()}\")\n",
"\n",
" x_values = solver.BooleanValues(x)\n",
" y_values = solver.BooleanValues(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",
" 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",
"\n",
" print(f\"Total packed weight: {items.weight.sum()}\")\n",
" print()\n",
" print(f\"Time = {solver.WallTime()} seconds\")\n",
" else:\n",
" print(\"The problem does not have an optimal solution.\")\n",
"\n",
"\n",
"main()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

Some files were not shown because too many files have changed in this diff Show More