654 lines
27 KiB
Plaintext
654 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "google",
|
|
"metadata": {},
|
|
"source": [
|
|
"##### Copyright 2025 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": [
|
|
"# steel_mill_slab_sat"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "link",
|
|
"metadata": {},
|
|
"source": [
|
|
"<table align=\"left\">\n",
|
|
"<td>\n",
|
|
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/examples/steel_mill_slab_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
|
|
"</td>\n",
|
|
"<td>\n",
|
|
"<a href=\"https://github.com/google/or-tools/blob/main/examples/python/steel_mill_slab_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
|
|
"</td>\n",
|
|
"</table>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "doc",
|
|
"metadata": {},
|
|
"source": [
|
|
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "install",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install ortools"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "description",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n",
|
|
"Solves the Stell Mill Slab problem with 4 different techniques.\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "code",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import collections\n",
|
|
"import time\n",
|
|
"\n",
|
|
"from ortools.sat.colab import flags\n",
|
|
"from ortools.sat.python import cp_model\n",
|
|
"\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",
|
|
")\n",
|
|
"_SOLVER = flags.define_string(\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(\n",
|
|
" problem_id: int,\n",
|
|
") -> tuple[int, list[int], int, list[tuple[int, int]]]:\n",
|
|
" \"\"\"Build problem data.\"\"\"\n",
|
|
" if problem_id == 0:\n",
|
|
" capacities = [\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 = [ # (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 = [ # (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 = [ # (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",
|
|
" else: # problem_id == 3, default problem.\n",
|
|
" capacities = [0, 17, 44]\n",
|
|
" num_colors = 8\n",
|
|
" num_slabs = 10\n",
|
|
" orders = [ # (size, color)\n",
|
|
" (4, 1),\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",
|
|
" ]\n",
|
|
"\n",
|
|
" return (num_slabs, capacities, num_colors, orders)\n",
|
|
"\n",
|
|
"\n",
|
|
"class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
|
|
" \"\"\"Print intermediate solutions.\"\"\"\n",
|
|
"\n",
|
|
" def __init__(self, orders, assign, load, loss) -> None:\n",
|
|
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
|
|
" self.__orders = orders\n",
|
|
" self.__assign = assign\n",
|
|
" self.__load = load\n",
|
|
" self.__loss = loss\n",
|
|
" self.__solution_count = 0\n",
|
|
" self.__all_orders = range(len(orders))\n",
|
|
" self.__all_slabs = range(len(assign[0]))\n",
|
|
" self.__start_time = time.time()\n",
|
|
"\n",
|
|
" def on_solution_callback(self) -> None:\n",
|
|
" \"\"\"Called on each new solution.\"\"\"\n",
|
|
" current_time = time.time()\n",
|
|
" objective = sum(self.value(l) for l in self.__loss)\n",
|
|
" print(\n",
|
|
" f\"Solution {self.__solution_count}, time =\"\n",
|
|
" f\" {current_time - self.__start_time} s, objective = {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",
|
|
" ]\n",
|
|
" for s in self.__all_slabs:\n",
|
|
" if orders_in_slab[s]:\n",
|
|
" line = (\n",
|
|
" f\" - slab {s}, load = {self.value(self.__load[s])}, loss =\"\n",
|
|
" f\" {self.value(self.__loss[s])}, orders = [\"\n",
|
|
" )\n",
|
|
" for o in orders_in_slab[s]:\n",
|
|
" line += f\"#{o}(w{self.__orders[o][0]}, c{self.__orders[o][1]})\"\n",
|
|
" line += \"]\"\n",
|
|
" print(line)\n",
|
|
"\n",
|
|
"\n",
|
|
"def steel_mill_slab(problem_id: int, break_symmetries: bool) -> None:\n",
|
|
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
|
|
" ### Load problem.\n",
|
|
" num_slabs, capacities, num_colors, orders = build_problem(problem_id)\n",
|
|
"\n",
|
|
" num_orders = len(orders)\n",
|
|
" num_capacities = len(capacities)\n",
|
|
" all_slabs = range(num_slabs)\n",
|
|
" all_colors = range(num_colors)\n",
|
|
" all_orders = range(len(orders))\n",
|
|
" print(\n",
|
|
" f\"Solving steel mill with {num_orders} orders, {num_slabs} slabs, and\"\n",
|
|
" f\" {num_capacities - 1} capacities\"\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 + 1)\n",
|
|
" ]\n",
|
|
" max_loss = max(loss_array)\n",
|
|
" orders_per_color = [\n",
|
|
" [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n",
|
|
" ]\n",
|
|
" unique_color_orders = [\n",
|
|
" o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1\n",
|
|
" ]\n",
|
|
"\n",
|
|
" ### Model problem.\n",
|
|
"\n",
|
|
" # Create the model and the decision variables.\n",
|
|
" model = cp_model.CpModel()\n",
|
|
" assign = [\n",
|
|
" [model.new_bool_var(f\"assign_{o}_to_slab_{s}\") for s in all_slabs]\n",
|
|
" for o in all_orders\n",
|
|
" ]\n",
|
|
" loads = [model.new_int_var(0, max_capacity, f\"load_of_slab_{s}\") for s in all_slabs]\n",
|
|
" color_is_in_slab = [\n",
|
|
" [model.new_bool_var(f\"color_{c + 1}_in_slab_{s}\") for c in all_colors]\n",
|
|
" for s in all_slabs\n",
|
|
" ]\n",
|
|
"\n",
|
|
" # Compute load of all slabs.\n",
|
|
" for s in all_slabs:\n",
|
|
" model.add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n",
|
|
"\n",
|
|
" # Orders are assigned to one slab.\n",
|
|
" for o in all_orders:\n",
|
|
" model.add_exactly_one(assign[o])\n",
|
|
"\n",
|
|
" # Redundant constraint (sum of loads == sum of widths).\n",
|
|
" model.add(sum(loads) == sum(widths))\n",
|
|
"\n",
|
|
" # Link present_colors and assign.\n",
|
|
" for c in all_colors:\n",
|
|
" for s in all_slabs:\n",
|
|
" for o in orders_per_color[c]:\n",
|
|
" model.add_implication(assign[o][s], color_is_in_slab[s][c])\n",
|
|
" model.add_implication(~color_is_in_slab[s][c], ~assign[o][s])\n",
|
|
"\n",
|
|
" # At most two colors per slab.\n",
|
|
" for s in all_slabs:\n",
|
|
" model.add(sum(color_is_in_slab[s]) <= 2)\n",
|
|
"\n",
|
|
" # Project previous constraint on unique_color_orders\n",
|
|
" for s in all_slabs:\n",
|
|
" model.add(sum(assign[o][s] for o in unique_color_orders) <= 2)\n",
|
|
"\n",
|
|
" # Symmetry breaking.\n",
|
|
" for s in range(num_slabs - 1):\n",
|
|
" model.add(loads[s] >= loads[s + 1])\n",
|
|
"\n",
|
|
" # Collect equivalent orders.\n",
|
|
" width_to_unique_color_order = {}\n",
|
|
" ordered_equivalent_orders = []\n",
|
|
" for c in all_colors:\n",
|
|
" colored_orders = orders_per_color[c]\n",
|
|
" if not colored_orders:\n",
|
|
" continue\n",
|
|
" if len(colored_orders) == 1:\n",
|
|
" o = colored_orders[0]\n",
|
|
" w = widths[o]\n",
|
|
" if w not in width_to_unique_color_order:\n",
|
|
" width_to_unique_color_order[w] = [o]\n",
|
|
" else:\n",
|
|
" width_to_unique_color_order[w].append(o)\n",
|
|
" else:\n",
|
|
" local_width_to_order = {}\n",
|
|
" for o in colored_orders:\n",
|
|
" w = widths[o]\n",
|
|
" if w not in local_width_to_order:\n",
|
|
" local_width_to_order[w] = []\n",
|
|
" local_width_to_order[w].append(o)\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 _, 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(\n",
|
|
" f\" - creating {len(ordered_equivalent_orders)} symmetry breaking\"\n",
|
|
" \" constraints\"\n",
|
|
" )\n",
|
|
" positions = {}\n",
|
|
" for p in ordered_equivalent_orders:\n",
|
|
" if p[0] not in positions:\n",
|
|
" positions[p[0]] = model.new_int_var(\n",
|
|
" 0, num_slabs - 1, f\"position_of_slab_{p[0]}\"\n",
|
|
" )\n",
|
|
" model.add_map_domain(positions[p[0]], assign[p[0]])\n",
|
|
" if p[1] not in positions:\n",
|
|
" positions[p[1]] = model.new_int_var(\n",
|
|
" 0, num_slabs - 1, f\"position_of_slab_{p[1]}\"\n",
|
|
" )\n",
|
|
" model.add_map_domain(positions[p[1]], assign[p[1]])\n",
|
|
" # Finally add the symmetry breaking constraint.\n",
|
|
" model.add(positions[p[0]] <= positions[p[1]])\n",
|
|
"\n",
|
|
" # Objective.\n",
|
|
" obj = model.new_int_var(0, num_slabs * max_loss, \"obj\")\n",
|
|
" losses = [model.new_int_var(0, max_loss, f\"loss_{s}\") for s in all_slabs]\n",
|
|
" for s in all_slabs:\n",
|
|
" model.add_element(loads[s], loss_array, losses[s])\n",
|
|
" model.add(obj == sum(losses))\n",
|
|
" model.minimize(obj)\n",
|
|
"\n",
|
|
" ### Solve model.\n",
|
|
" solver = cp_model.CpSolver()\n",
|
|
" if _PARAMS.value:\n",
|
|
" solver.parameters.parse_text_format(_PARAMS.value)\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",
|
|
" f\"Loss = {solver.objective_value}, time = {solver.wall_time} s,\"\n",
|
|
" f\" {solver.num_conflicts} conflicts\"\n",
|
|
" )\n",
|
|
" else:\n",
|
|
" print(\"No solution\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def collect_valid_slabs_dp(\n",
|
|
" capacities: list[int],\n",
|
|
" colors: list[int],\n",
|
|
" widths: list[int],\n",
|
|
" loss_array: list[int],\n",
|
|
") -> list[list[int]]:\n",
|
|
" \"\"\"Collect valid columns (assign, loss) for one slab.\"\"\"\n",
|
|
" start_time = time.time()\n",
|
|
"\n",
|
|
" max_capacity = max(capacities)\n",
|
|
"\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, new_color in enumerate(colors):\n",
|
|
" new_width = widths[order_id]\n",
|
|
" new_assignments = []\n",
|
|
" for assignment in all_valid_assignments:\n",
|
|
" if assignment.load + new_width > max_capacity:\n",
|
|
" continue\n",
|
|
" new_colors = list(assignment.colors)\n",
|
|
" if new_color not in new_colors:\n",
|
|
" new_colors.append(new_color)\n",
|
|
" if len(new_colors) > 2:\n",
|
|
" continue\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(\n",
|
|
" f\"{len(all_valid_assignments)} assignments created in\"\n",
|
|
" f\" {time.time() - start_time:2f} s\"\n",
|
|
" )\n",
|
|
" tuples = []\n",
|
|
" for assignment in all_valid_assignments:\n",
|
|
" solution = [0] * len(colors)\n",
|
|
" for i in assignment.orders:\n",
|
|
" solution[i] = 1\n",
|
|
" solution.append(loss_array[assignment.load])\n",
|
|
" solution.append(assignment.load)\n",
|
|
" tuples.append(solution)\n",
|
|
"\n",
|
|
" return tuples\n",
|
|
"\n",
|
|
"\n",
|
|
"def steel_mill_slab_with_valid_slabs(problem_id: int, break_symmetries: bool) -> None:\n",
|
|
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
|
|
" ### Load problem.\n",
|
|
" (num_slabs, capacities, num_colors, orders) = build_problem(problem_id)\n",
|
|
"\n",
|
|
" num_orders = len(orders)\n",
|
|
" num_capacities = len(capacities)\n",
|
|
" all_slabs = range(num_slabs)\n",
|
|
" all_colors = range(num_colors)\n",
|
|
" all_orders = range(len(orders))\n",
|
|
" print(\n",
|
|
" f\"Solving steel mill with {num_orders} orders, {num_slabs} slabs, and\"\n",
|
|
" f\" {num_capacities - 1} capacities\"\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 + 1)\n",
|
|
" ]\n",
|
|
" max_loss = max(loss_array)\n",
|
|
"\n",
|
|
" ### Model problem.\n",
|
|
"\n",
|
|
" # Create the model and the decision variables.\n",
|
|
" model = cp_model.CpModel()\n",
|
|
" assign = [\n",
|
|
" [model.new_bool_var(r\"assign_{o}_to_slab_{s}\") for s in all_slabs]\n",
|
|
" for o in all_orders\n",
|
|
" ]\n",
|
|
" loads = [model.new_int_var(0, max_capacity, f\"load_{s}\") for s in all_slabs]\n",
|
|
" losses = [model.new_int_var(0, max_loss, f\"loss_{s}\") for s in all_slabs]\n",
|
|
"\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, key=lambda c: 1000 * c[-1] + c[-2])\n",
|
|
"\n",
|
|
" for s in all_slabs:\n",
|
|
" model.add_allowed_assignments(\n",
|
|
" [assign[o][s] for o in all_orders] + [losses[s], loads[s]], valid_slabs\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Orders are assigned to one slab.\n",
|
|
" for o in all_orders:\n",
|
|
" model.add_exactly_one(assign[o])\n",
|
|
"\n",
|
|
" # Redundant constraint (sum of loads == sum of widths).\n",
|
|
" model.add(sum(loads) == sum(widths))\n",
|
|
"\n",
|
|
" # Symmetry breaking.\n",
|
|
" for s in range(num_slabs - 1):\n",
|
|
" model.add(loads[s] >= loads[s + 1])\n",
|
|
"\n",
|
|
" # Collect equivalent orders.\n",
|
|
" if break_symmetries:\n",
|
|
" print(\"Breaking symmetries\")\n",
|
|
" width_to_unique_color_order = {}\n",
|
|
" ordered_equivalent_orders = []\n",
|
|
" orders_per_color = [\n",
|
|
" [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n",
|
|
" ]\n",
|
|
" for c in all_colors:\n",
|
|
" colored_orders = orders_per_color[c]\n",
|
|
" if not colored_orders:\n",
|
|
" continue\n",
|
|
" if len(colored_orders) == 1:\n",
|
|
" o = colored_orders[0]\n",
|
|
" w = widths[o]\n",
|
|
" if w not in width_to_unique_color_order:\n",
|
|
" width_to_unique_color_order[w] = [o]\n",
|
|
" else:\n",
|
|
" width_to_unique_color_order[w].append(o)\n",
|
|
" else:\n",
|
|
" local_width_to_order = {}\n",
|
|
" for o in colored_orders:\n",
|
|
" w = widths[o]\n",
|
|
" if w not in local_width_to_order:\n",
|
|
" local_width_to_order[w] = []\n",
|
|
" local_width_to_order[w].append(o)\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 _, 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(\n",
|
|
" f\" - creating {len(ordered_equivalent_orders)} symmetry breaking\"\n",
|
|
" \" constraints\"\n",
|
|
" )\n",
|
|
" positions = {}\n",
|
|
" for p in ordered_equivalent_orders:\n",
|
|
" if p[0] not in positions:\n",
|
|
" positions[p[0]] = model.new_int_var(\n",
|
|
" 0, num_slabs - 1, f\"position_of_slab_{p[0]}\"\n",
|
|
" )\n",
|
|
" model.add_map_domain(positions[p[0]], assign[p[0]])\n",
|
|
" if p[1] not in positions:\n",
|
|
" positions[p[1]] = model.new_int_var(\n",
|
|
" 0, num_slabs - 1, f\"position_of_slab_{p[1]}\"\n",
|
|
" )\n",
|
|
" model.add_map_domain(positions[p[1]], assign[p[1]])\n",
|
|
" # Finally add the symmetry breaking constraint.\n",
|
|
" model.add(positions[p[0]] <= positions[p[1]])\n",
|
|
"\n",
|
|
" # Objective.\n",
|
|
" model.minimize(sum(losses))\n",
|
|
"\n",
|
|
" print(\"Model created\")\n",
|
|
"\n",
|
|
" ### Solve model.\n",
|
|
" solver = cp_model.CpSolver()\n",
|
|
" if _PARAMS.value:\n",
|
|
" solver.parameters.parse_text_format(_PARAMS.value)\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",
|
|
" f\"Loss = {solver.objective_value}, time = {solver.wall_time:2f} s,\"\n",
|
|
" f\" {solver.num_conflicts} conflicts\"\n",
|
|
" )\n",
|
|
" else:\n",
|
|
" print(\"No solution\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def steel_mill_slab_with_column_generation(problem_id: int) -> None:\n",
|
|
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
|
|
" ### Load problem.\n",
|
|
" (num_slabs, capacities, _, orders) = build_problem(problem_id)\n",
|
|
"\n",
|
|
" num_orders = len(orders)\n",
|
|
" num_capacities = len(capacities)\n",
|
|
" all_orders = range(len(orders))\n",
|
|
" print(\n",
|
|
" f\"Solving steel mill with {num_orders} orders, {num_slabs} slabs, and\"\n",
|
|
" f\" {num_capacities - 1} capacities\"\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 + 1)\n",
|
|
" ]\n",
|
|
"\n",
|
|
" ### Model problem.\n",
|
|
"\n",
|
|
" # Generate all valid slabs (columns)\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, 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.new_bool_var(f\"selected_{i}\") for i in all_valid_slabs]\n",
|
|
"\n",
|
|
" for order_id in all_orders:\n",
|
|
" model.add(\n",
|
|
" sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id])\n",
|
|
" == 1\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Redundant constraint (sum of loads == sum of widths).\n",
|
|
" model.add(\n",
|
|
" sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Objective.\n",
|
|
" model.minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))\n",
|
|
"\n",
|
|
" print(\"Model created\")\n",
|
|
"\n",
|
|
" ### Solve model.\n",
|
|
" solver = cp_model.CpSolver()\n",
|
|
" if _PARAMS.value:\n",
|
|
" solver.parameters.parse_text_format(_PARAMS.value)\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",
|
|
" f\"Loss = {solver.objective_value}, time = {solver.wall_time:2f} s,\"\n",
|
|
" f\" {solver.num_conflicts} conflicts\"\n",
|
|
" )\n",
|
|
" else:\n",
|
|
" print(\"No solution\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def main(_):\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, _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",
|
|
"\n",
|
|
"\n",
|
|
"main()\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|