examples: regenerate notebook
This commit is contained in:
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.algorithms.python import knapsack_solver\n",
|
"from ortools.algorithms.python import knapsack_solver\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the solver.\n",
|
" # Create the solver.\n",
|
||||||
" solver = knapsack_solver.KnapsackSolver(\n",
|
" solver = knapsack_solver.KnapsackSolver(\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.algorithms.python import knapsack_solver\n",
|
"from ortools.algorithms.python import knapsack_solver\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the solver.\n",
|
" # Create the solver.\n",
|
||||||
" solver = knapsack_solver.KnapsackSolver(\n",
|
" solver = knapsack_solver.KnapsackSolver(\n",
|
||||||
|
|||||||
@@ -95,7 +95,6 @@
|
|||||||
"import numpy as np\n",
|
"import numpy as np\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_PARAMS = flags.define_string(\n",
|
"_PARAMS = flags.define_string(\n",
|
||||||
@@ -217,7 +216,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solver.parameters.log_search_progress = log\n",
|
" solver.parameters.log_search_progress = log\n",
|
||||||
" solver.parameters.max_time_in_seconds = time_limit\n",
|
" solver.parameters.max_time_in_seconds = time_limit\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -90,7 +90,6 @@
|
|||||||
"import collections\n",
|
"import collections\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"#----------------------------------------------------------------------------\n",
|
"#----------------------------------------------------------------------------\n",
|
||||||
"# Command line arguments.\n",
|
"# Command line arguments.\n",
|
||||||
@@ -376,7 +375,7 @@
|
|||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" solver.parameters.max_time_in_seconds = 60 * 60 * 2\n",
|
" solver.parameters.max_time_in_seconds = 60 * 60 * 2\n",
|
||||||
" if parameters:\n",
|
" if parameters:\n",
|
||||||
" text_format.Merge(parameters, solver.parameters)\n",
|
" solver.parameters.merge_text_format(parameters)\n",
|
||||||
" solution_printer = SolutionPrinter(makespan)\n",
|
" solution_printer = SolutionPrinter(makespan)\n",
|
||||||
" status = solver.Solve(model, solution_printer)\n",
|
" status = solver.Solve(model, solution_printer)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -244,7 +244,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def get_optimal_schedule(\n",
|
"def get_optimal_schedule(\n",
|
||||||
" demand: list[tuple[float, str, int]]\n",
|
" demand: list[tuple[float, str, int]],\n",
|
||||||
") -> list[tuple[int, list[tuple[int, str]]]]:\n",
|
") -> list[tuple[int, list[tuple[int, str]]]]:\n",
|
||||||
" \"\"\"Computes the optimal schedule for the installation input.\n",
|
" \"\"\"Computes the optimal schedule for the installation input.\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"import numpy as np\n",
|
"import numpy as np\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.linear_solver.python import model_builder as mb\n",
|
"from ortools.linear_solver.python import model_builder as mb\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -387,7 +386,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" solver.Solve(model)\n",
|
" solver.Solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -78,7 +78,8 @@
|
|||||||
"Each item has a color and a value. We want the sum of values of each group to\n",
|
"Each item has a color and a value. We want the sum of values of each group to\n",
|
||||||
"be as close to the average as possible.\n",
|
"be as close to the average as possible.\n",
|
||||||
"Furthermore, if one color is an a group, at least k items with this color must\n",
|
"Furthermore, if one color is an a group, at least k items with this color must\n",
|
||||||
"be in that group.\n"
|
"be in that group.\n",
|
||||||
|
"\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,7 +97,6 @@
|
|||||||
"import math\n",
|
"import math\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_OUTPUT_PROTO = flags.define_string(\n",
|
"_OUTPUT_PROTO = flags.define_string(\n",
|
||||||
@@ -149,7 +148,7 @@
|
|||||||
" [25, \"15:40\", \"15:56\", 940, 956, 16],\n",
|
" [25, \"15:40\", \"15:56\", 940, 956, 16],\n",
|
||||||
" [26, \"15:58\", \"16:45\", 958, 1005, 47],\n",
|
" [26, \"15:58\", \"16:45\", 958, 1005, 47],\n",
|
||||||
" [27, \"16:04\", \"17:30\", 964, 1050, 86],\n",
|
" [27, \"16:04\", \"17:30\", 964, 1050, 86],\n",
|
||||||
"] # yapf:disable\n",
|
"]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"SAMPLE_SHIFTS_SMALL = [\n",
|
"SAMPLE_SHIFTS_SMALL = [\n",
|
||||||
" #\n",
|
" #\n",
|
||||||
@@ -211,7 +210,7 @@
|
|||||||
" [47, \"18:34\", \"19:58\", 1114, 1198, 84],\n",
|
" [47, \"18:34\", \"19:58\", 1114, 1198, 84],\n",
|
||||||
" [48, \"19:56\", \"20:34\", 1196, 1234, 38],\n",
|
" [48, \"19:56\", \"20:34\", 1196, 1234, 38],\n",
|
||||||
" [49, \"20:05\", \"20:48\", 1205, 1248, 43],\n",
|
" [49, \"20:05\", \"20:48\", 1205, 1248, 43],\n",
|
||||||
"] # yapf:disable\n",
|
"]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"SAMPLE_SHIFTS_MEDIUM = [\n",
|
"SAMPLE_SHIFTS_MEDIUM = [\n",
|
||||||
" [0, \"04:30\", \"04:53\", 270, 293, 23],\n",
|
" [0, \"04:30\", \"04:53\", 270, 293, 23],\n",
|
||||||
@@ -414,7 +413,7 @@
|
|||||||
" [197, \"00:02\", \"00:12\", 1442, 1452, 10],\n",
|
" [197, \"00:02\", \"00:12\", 1442, 1452, 10],\n",
|
||||||
" [198, \"00:07\", \"00:39\", 1447, 1479, 32],\n",
|
" [198, \"00:07\", \"00:39\", 1447, 1479, 32],\n",
|
||||||
" [199, \"00:25\", \"01:12\", 1465, 1512, 47],\n",
|
" [199, \"00:25\", \"01:12\", 1465, 1512, 47],\n",
|
||||||
"] # yapf:disable\n",
|
"]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"SAMPLE_SHIFTS_LARGE = [\n",
|
"SAMPLE_SHIFTS_LARGE = [\n",
|
||||||
" [0, \"04:18\", \"05:00\", 258, 300, 42],\n",
|
" [0, \"04:18\", \"05:00\", 258, 300, 42],\n",
|
||||||
@@ -1773,7 +1772,7 @@
|
|||||||
" [1353, \"00:47\", \"01:26\", 1487, 1526, 39],\n",
|
" [1353, \"00:47\", \"01:26\", 1487, 1526, 39],\n",
|
||||||
" [1354, \"00:54\", \"01:04\", 1494, 1504, 10],\n",
|
" [1354, \"00:54\", \"01:04\", 1494, 1504, 10],\n",
|
||||||
" [1355, \"00:57\", \"01:07\", 1497, 1507, 10],\n",
|
" [1355, \"00:57\", \"01:07\", 1497, 1507, 10],\n",
|
||||||
"] # yapf:disable\n",
|
"]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def bus_driver_scheduling(minimize_drivers: bool, max_num_drivers: int) -> int:\n",
|
"def bus_driver_scheduling(minimize_drivers: bool, max_num_drivers: int) -> int:\n",
|
||||||
@@ -2049,7 +2048,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
349
examples/notebook/examples/car_sequencing_optimization_sat.ipynb
Normal file
349
examples/notebook/examples/car_sequencing_optimization_sat.ipynb
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"# car_sequencing_optimization_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/car_sequencing_optimization_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/car_sequencing_optimization_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 the car sequencing problem as an optimization problem.\n",
|
||||||
|
"\n",
|
||||||
|
"Problem Description: The Car Sequencing Problem with Optimization\n",
|
||||||
|
"-----------------------------------------------------------------\n",
|
||||||
|
"\n",
|
||||||
|
"See https://en.wikipedia.org/wiki/Car_sequencing_problem for more details.\n",
|
||||||
|
"\n",
|
||||||
|
"We are tasked with determining the optimal production sequence for a set of cars\n",
|
||||||
|
"on an assembly line. This is a classic and challenging combinatorial\n",
|
||||||
|
"optimization problem with the following characteristics:\n",
|
||||||
|
"\n",
|
||||||
|
"Fixed Production Demand: There is a specific, non-negotiable number of cars of\n",
|
||||||
|
"different types (or 'classes') that must be produced. In our case, we have 6\n",
|
||||||
|
"distinct classes of cars, and we must produce exactly 5 of each, for a total of\n",
|
||||||
|
"30 'real' cars.\n",
|
||||||
|
"\n",
|
||||||
|
"Diverse Car Configurations: Each car class is defined by a unique combination of\n",
|
||||||
|
"optional features. For example, 'Class 1' might require a sunroof (Option 1) and\n",
|
||||||
|
"a special engine (Option 4), while 'Class 3' only requires air conditioning\n",
|
||||||
|
"(Option 2).\n",
|
||||||
|
"\n",
|
||||||
|
"Specialized Assembly Stations: The assembly line is composed of a series of\n",
|
||||||
|
"specialized stations. Each station is responsible for installing one specific\n",
|
||||||
|
"option. For example, there is one station for sunroofs, one for special engines,\n",
|
||||||
|
"and so on.\n",
|
||||||
|
"\n",
|
||||||
|
"Capacity-Limited Stations: The core challenge of the problem lies here. The\n",
|
||||||
|
"stations cannot handle an unlimited, dense flow of cars requiring their specific\n",
|
||||||
|
"option. Their capacity is defined by a 'sliding window' constraint. For example,\n",
|
||||||
|
"the sunroof station might have a constraint of 'at most 1 car with a sunroof in\n",
|
||||||
|
"any sequence of 3 consecutive cars'. This means sequences like [Sunroof, No, No,\n",
|
||||||
|
"Sunroof] are valid, but [Sunroof, No, Sunroof, No] are not.\n",
|
||||||
|
"\n",
|
||||||
|
"The Need for Spacing (Optimization): The combination of high demand for certain\n",
|
||||||
|
"options and tight capacity constraints may make it impossible to produce the 30\n",
|
||||||
|
"real cars consecutively. To create a valid sequence, we may need to insert\n",
|
||||||
|
"'dummy' or 'filler' cars into the production line. These dummy cars have no\n",
|
||||||
|
"options and therefore do not consume capacity at any station. They serve purely\n",
|
||||||
|
"as spacers to break up dense sequences of option-heavy cars.\n",
|
||||||
|
"\n",
|
||||||
|
"The Goal: The objective is to find a production sequence that fulfills the\n",
|
||||||
|
"demand for all 30 real cars while using the minimum number of dummy cars. This\n",
|
||||||
|
"is equivalent to finding the shortest possible total production schedule (real\n",
|
||||||
|
"cars + dummy cars).\n",
|
||||||
|
"\n",
|
||||||
|
"Modeling and Solution Approach with CP-SAT\n",
|
||||||
|
"------------------------------------------\n",
|
||||||
|
"\n",
|
||||||
|
"To solve this problem, we use the CP-SAT solver from Google's OR-Tools library.\n",
|
||||||
|
"This is a constraint programming approach, which works by defining variables,\n",
|
||||||
|
"constraints, and an objective function.\n",
|
||||||
|
"\n",
|
||||||
|
"1. Decision Variables\n",
|
||||||
|
"The fundamental decision the solver must make is: 'Which class of car should be\n",
|
||||||
|
"placed in each production slot?'\n",
|
||||||
|
"We define a large number of boolean variables: produces[c][s]. This variable is\n",
|
||||||
|
"True if a car of class c is scheduled in slot s, and False otherwise. We create\n",
|
||||||
|
"these for all car classes (including the dummy class) and for an extended number\n",
|
||||||
|
"of slots (30 real + a buffer of 20 for dummies).\n",
|
||||||
|
"We introduce a key integer variable: makespan. This variable represents the\n",
|
||||||
|
"total length of the 'meaningful' part of our schedule. It's the slot number\n",
|
||||||
|
"where the first dummy car appears, after which all subsequent cars are also\n",
|
||||||
|
"dummies.\n",
|
||||||
|
"\n",
|
||||||
|
"2. Constraints (The Rules of the Game)\n",
|
||||||
|
"We translate the problem's rules into mathematical constraints that the solver\n",
|
||||||
|
"must obey:\n",
|
||||||
|
"\n",
|
||||||
|
"One Car Per Slot: For every production slot s, exactly one car class can be\n",
|
||||||
|
"assigned. We enforce this using an AddExactlyOne constraint over all\n",
|
||||||
|
"produces[c][s] variables for that slot.\n",
|
||||||
|
"\n",
|
||||||
|
"Fulfill Real Car Demand: The total number of times each real car class c appears\n",
|
||||||
|
"across all slots must equal its required demand (5 in our case). This is a\n",
|
||||||
|
"simple Add(sum(...) == 5) constraint.\n",
|
||||||
|
"\n",
|
||||||
|
"Station Capacity (Sliding Window): This is the most critical constraint. For\n",
|
||||||
|
"each option (e.g., 'sunroof') and its capacity rule (e.g., '1 in 3'), we create\n",
|
||||||
|
"constraints for every possible sliding window. For every subsequence of 3 slots,\n",
|
||||||
|
"we sum up the produces variables corresponding to car classes that require that\n",
|
||||||
|
"option and constrain this sum to be less than or equal to 1.\n",
|
||||||
|
"\n",
|
||||||
|
"Makespan Definition: This is the clever part of the model. We link our makespan\n",
|
||||||
|
"objective variable to the placement of dummy cars using logical equivalences for\n",
|
||||||
|
"each slot s:\n",
|
||||||
|
"(makespan <= s) is equivalent to (slot s contains a dummy car)\n",
|
||||||
|
"This ensures that if the solver chooses a makespan of 32, for example, it is\n",
|
||||||
|
"forced to place dummy cars in slots 32, 33, 34, and so on. Conversely, if the\n",
|
||||||
|
"solver is forced to place a dummy car in slot 32 to satisfy a capacity\n",
|
||||||
|
"constraint, the makespan must be at most 32.\n",
|
||||||
|
"\n",
|
||||||
|
"3. The Objective Function\n",
|
||||||
|
"\n",
|
||||||
|
"The objective is simple and directly tied to our goal:\n",
|
||||||
|
"\n",
|
||||||
|
"Minimize makespan: By instructing the solver to find a solution with the\n",
|
||||||
|
"smallest possible value for the makespan variable, we are asking it to find the\n",
|
||||||
|
"shortest possible production schedule that satisfies all the rules. This\n",
|
||||||
|
"inherently minimizes the number of dummy cars used.\n",
|
||||||
|
"\n",
|
||||||
|
"By defining the problem in this way, we let the CP-SAT solver explore the vast\n",
|
||||||
|
"search space of possible sequences efficiently, using its powerful constraint\n",
|
||||||
|
"propagation and search techniques to find an optimal arrangement that meets all\n",
|
||||||
|
"our complex requirements.\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from collections.abc import Sequence\n",
|
||||||
|
"\n",
|
||||||
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def solve_car_sequencing_optimization() -> None:\n",
|
||||||
|
" \"\"\"Solves the car sequencing problem with an optimization approach.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 1. Data\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" num_real_cars: int = 30\n",
|
||||||
|
" max_dummy_cars: int = 20\n",
|
||||||
|
" num_slots = num_real_cars + max_dummy_cars\n",
|
||||||
|
" all_slots = range(num_slots)\n",
|
||||||
|
"\n",
|
||||||
|
" class_options = [\n",
|
||||||
|
" # Options: 1 2 3 4 5\n",
|
||||||
|
" [0, 0, 0, 0, 0], # Class 0 (Dummy)\n",
|
||||||
|
" [1, 0, 0, 1, 0], # Class 1\n",
|
||||||
|
" [0, 1, 0, 0, 1], # Class 2\n",
|
||||||
|
" [0, 1, 0, 0, 0], # Class 3\n",
|
||||||
|
" [0, 0, 1, 1, 0], # Class 4\n",
|
||||||
|
" [0, 0, 1, 0, 0], # Class 5\n",
|
||||||
|
" [0, 0, 0, 0, 1], # Class 6\n",
|
||||||
|
" ]\n",
|
||||||
|
" num_classes = len(class_options)\n",
|
||||||
|
" all_classes = range(num_classes)\n",
|
||||||
|
" real_classes = range(1, num_classes)\n",
|
||||||
|
" dummy_class = 0\n",
|
||||||
|
"\n",
|
||||||
|
" demands = [5, 5, 5, 5, 5, 5]\n",
|
||||||
|
"\n",
|
||||||
|
" capacity_constraints = [(1, 3), (1, 2), (1, 3), (2, 5), (1, 5)]\n",
|
||||||
|
" num_options = len(capacity_constraints)\n",
|
||||||
|
" all_options = range(num_options)\n",
|
||||||
|
"\n",
|
||||||
|
" classes_with_option = [\n",
|
||||||
|
" [c for c in real_classes if class_options[c][o] == 1] for o in all_options\n",
|
||||||
|
" ]\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 2. Model Creation\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 3. Decision Variables\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" produces = {}\n",
|
||||||
|
" for c in all_classes:\n",
|
||||||
|
" for s in all_slots:\n",
|
||||||
|
" produces[(c, s)] = model.new_bool_var(f\"produces_c{c}_s{s}\")\n",
|
||||||
|
"\n",
|
||||||
|
" makespan = model.new_int_var(num_real_cars, num_slots, \"makespan\")\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 4. Constraints\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 1: Only one car produced per slot.\n",
|
||||||
|
" for s in all_slots:\n",
|
||||||
|
" model.add_exactly_one([produces[(c, s)] for c in all_classes])\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 2: Meet the demand of real cars.\n",
|
||||||
|
" for i, c in enumerate(real_classes):\n",
|
||||||
|
" model.add(sum(produces[(c, s)] for s in all_slots) == demands[i])\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 3: Enforce the capacity constraints on options.\n",
|
||||||
|
" for o in all_options:\n",
|
||||||
|
" max_cars, subsequence_len = capacity_constraints[o]\n",
|
||||||
|
" for start in range(num_slots - subsequence_len + 1):\n",
|
||||||
|
" window = range(start, start + subsequence_len)\n",
|
||||||
|
" cars_with_option_in_window = []\n",
|
||||||
|
" for c in classes_with_option[o]:\n",
|
||||||
|
" for s in window:\n",
|
||||||
|
" cars_with_option_in_window.append(produces[(c, s)])\n",
|
||||||
|
" model.add(sum(cars_with_option_in_window) <= max_cars)\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 4 (Link objective and dummy cars at the end of the schedule)\n",
|
||||||
|
" for s in all_slots:\n",
|
||||||
|
" makespan_le_s = model.new_bool_var(f\"makespan_le_{s}\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Enforce makespan_le_s <=> (makespan <= s)\n",
|
||||||
|
" model.add(makespan <= s).only_enforce_if(makespan_le_s)\n",
|
||||||
|
" # Use ~ for negation\n",
|
||||||
|
" model.add(makespan > s).only_enforce_if(~makespan_le_s)\n",
|
||||||
|
"\n",
|
||||||
|
" # Enforce makespan_le_s => produces[dummy_class, s]\n",
|
||||||
|
" model.add_implication(makespan_le_s, produces[dummy_class, s])\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 5. Objective\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" model.minimize(makespan)\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 6. Solve and Print Solution\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" solver.parameters.max_time_in_seconds = 30.0\n",
|
||||||
|
" solver.parameters.num_search_workers = 1 # The problem is easy to solve.\n",
|
||||||
|
" # solver.parameters.log_search_progress = True # uncomment to see the log.\n",
|
||||||
|
"\n",
|
||||||
|
" status = solver.Solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
|
||||||
|
" final_makespan = int(solver.ObjectiveValue())\n",
|
||||||
|
" num_dummies_needed = final_makespan - num_real_cars\n",
|
||||||
|
"\n",
|
||||||
|
" print(\n",
|
||||||
|
" f'\\n{\"Optimal\" if status == cp_model.OPTIMAL else \"Feasible\"}'\n",
|
||||||
|
" f\" solution found with a makespan of {final_makespan}.\"\n",
|
||||||
|
" )\n",
|
||||||
|
" print(\n",
|
||||||
|
" f\"This requires the conceptual equivalent of {num_dummies_needed} dummy\"\n",
|
||||||
|
" \" car(s) to be used as spacers.\"\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" sequence = [-1] * num_slots\n",
|
||||||
|
" for s in all_slots:\n",
|
||||||
|
" for c in all_classes:\n",
|
||||||
|
" if solver.Value(produces[(c, s)]) == 1:\n",
|
||||||
|
" sequence[s] = c\n",
|
||||||
|
" break\n",
|
||||||
|
"\n",
|
||||||
|
" print(\"\\nFull Production Sequence (Class 0 is dummy):\")\n",
|
||||||
|
" print(\"Slot: | \" + \" | \".join(f\"{i:2}\" for i in range(num_slots)) + \" |\")\n",
|
||||||
|
" print(\"-------|-\" + \"--|-\" * num_slots)\n",
|
||||||
|
" print(\"Class: | \" + \" | \".join(f\"{c:2}\" for c in sequence) + \" |\")\n",
|
||||||
|
"\n",
|
||||||
|
" elif status == cp_model.INFEASIBLE:\n",
|
||||||
|
" print(\"\\nNo solution found.\")\n",
|
||||||
|
"\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(f\"\\nSomething went wrong. Solver status: {status}\")\n",
|
||||||
|
"\n",
|
||||||
|
" print(\"\\nSolver statistics:\")\n",
|
||||||
|
" print(solver.response_stats())\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def main(argv: Sequence[str]) -> None:\n",
|
||||||
|
" if len(argv) > 1:\n",
|
||||||
|
" raise app.UsageError(\"Too many command-line arguments.\")\n",
|
||||||
|
" solve_car_sequencing_optimization()\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"main()\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -73,8 +73,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"\n",
|
"\n",
|
||||||
"Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.\n",
|
"Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.\n"
|
||||||
"\n"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,833 +0,0 @@
|
|||||||
{
|
|
||||||
"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": [
|
|
||||||
"# cvrptw_plot"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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/cvrptw_plot.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/cvrptw_plot.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": [
|
|
||||||
"Capacitated Vehicle Routing Problem with Time Windows (and optional orders).\n",
|
|
||||||
"\n",
|
|
||||||
" This is a sample using the routing library python wrapper to solve a\n",
|
|
||||||
" CVRPTW problem.\n",
|
|
||||||
" A description of the problem can be found here:\n",
|
|
||||||
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
|
|
||||||
" The variant which is tackled by this model includes a capacity dimension,\n",
|
|
||||||
" time windows and optional orders, with a penalty cost if orders are not\n",
|
|
||||||
" performed.\n",
|
|
||||||
" To help explore the problem, two classes are provided Customers() and\n",
|
|
||||||
" Vehicles(): used to randomly locate orders and depots, and to randomly\n",
|
|
||||||
" generate demands, time-window constraints and vehicles.\n",
|
|
||||||
" Distances are computed using the Great Circle distances. Distances are in km\n",
|
|
||||||
" and times in seconds.\n",
|
|
||||||
"\n",
|
|
||||||
" A function for the displaying of the vehicle plan\n",
|
|
||||||
" display_vehicle_output\n",
|
|
||||||
"\n",
|
|
||||||
" The optimization engine uses local search to improve solutions, first\n",
|
|
||||||
" solutions being generated using a cheapest addition heuristic.\n",
|
|
||||||
" Numpy and Matplotlib are required for the problem creation and display.\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "code",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import os\n",
|
|
||||||
"import numpy as np\n",
|
|
||||||
"from matplotlib import pyplot as plt\n",
|
|
||||||
"from collections import namedtuple\n",
|
|
||||||
"from ortools.constraint_solver import pywrapcp\n",
|
|
||||||
"from ortools.constraint_solver import routing_enums_pb2\n",
|
|
||||||
"from datetime import datetime, timedelta\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"class Customers():\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" A class that generates and holds customers information.\n",
|
|
||||||
"\n",
|
|
||||||
" Randomly normally distribute a number of customers and locations within\n",
|
|
||||||
" a region described by a rectangle. Generate a random demand for each\n",
|
|
||||||
" customer. Generate a random time window for each customer.\n",
|
|
||||||
" May either be initiated with the extents, as a dictionary describing\n",
|
|
||||||
" two corners of a rectangle in latitude and longitude OR as a center\n",
|
|
||||||
" point (lat, lon), and box_size in km. The default arguments are for a\n",
|
|
||||||
" 10 x 10 km square centered in Sheffield).\n",
|
|
||||||
"\n",
|
|
||||||
" Args: extents (Optional[Dict]): A dictionary describing a rectangle in\n",
|
|
||||||
" latitude and longitude with the keys 'llcrnrlat', 'llcrnrlon' &\n",
|
|
||||||
" 'urcrnrlat' & 'urcrnrlat' center (Optional(Tuple): A tuple of\n",
|
|
||||||
" (latitude, longitude) describing the centre of the rectangle. box_size\n",
|
|
||||||
" (Optional float: The length in km of the box's sides. num_stops (int):\n",
|
|
||||||
" The number of customers, including the depots that are placed normally\n",
|
|
||||||
" distributed in the rectangle. min_demand (int): Lower limit on the\n",
|
|
||||||
" randomly generated demand at each customer. max_demand (int): Upper\n",
|
|
||||||
" limit on the randomly generated demand at each customer.\n",
|
|
||||||
" min_tw: shortest random time window for a customer, in hours.\n",
|
|
||||||
" max_tw: longest random time window for a customer, in hours.\n",
|
|
||||||
" Examples: To place 100 customers randomly within 100 km x 100 km\n",
|
|
||||||
" rectangle, centered in the default location, with a random demand of\n",
|
|
||||||
" between 5 and 10 units: >>> customers = Customers(num_stops=100,\n",
|
|
||||||
" box_size=100, ... min_demand=5, max_demand=10)\n",
|
|
||||||
" alternatively, to place 75 customers in the same area with default\n",
|
|
||||||
" arguments for demand: >>> extents = {'urcrnrlon': 0.03403, 'llcrnrlon':\n",
|
|
||||||
" -2.98325, ... 'urcrnrlat': 54.28127, 'llcrnrlat': 52.48150} >>>\n",
|
|
||||||
" customers = Customers(num_stops=75, extents=extents)\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" def __init__(self,\n",
|
|
||||||
" extents=None,\n",
|
|
||||||
" center=(53.381393, -1.474611),\n",
|
|
||||||
" box_size=10,\n",
|
|
||||||
" num_stops=100,\n",
|
|
||||||
" min_demand=0,\n",
|
|
||||||
" max_demand=25,\n",
|
|
||||||
" min_tw=1,\n",
|
|
||||||
" max_tw=5):\n",
|
|
||||||
" self.number = num_stops #: The number of customers and depots\n",
|
|
||||||
" #: Location, a named tuple for locations.\n",
|
|
||||||
" Location = namedtuple('Location', ['lat', 'lon'])\n",
|
|
||||||
" if extents is not None:\n",
|
|
||||||
" self.extents = extents #: The lower left and upper right points\n",
|
|
||||||
" #: Location[lat,lon]: the centre point of the area.\n",
|
|
||||||
" self.center = Location(\n",
|
|
||||||
" extents['urcrnrlat'] - 0.5 *\n",
|
|
||||||
" (extents['urcrnrlat'] - extents['llcrnrlat']),\n",
|
|
||||||
" extents['urcrnrlon'] - 0.5 *\n",
|
|
||||||
" (extents['urcrnrlon'] - extents['llcrnrlon']))\n",
|
|
||||||
" else:\n",
|
|
||||||
" #: Location[lat,lon]: the centre point of the area.\n",
|
|
||||||
" (clat, clon) = self.center = Location(center[0], center[1])\n",
|
|
||||||
" rad_earth = 6367 # km\n",
|
|
||||||
" circ_earth = np.pi * rad_earth\n",
|
|
||||||
" #: The lower left and upper right points\n",
|
|
||||||
" self.extents = {\n",
|
|
||||||
" 'llcrnrlon': (clon - 180 * box_size /\n",
|
|
||||||
" (circ_earth * np.cos(np.deg2rad(clat)))),\n",
|
|
||||||
" 'llcrnrlat':\n",
|
|
||||||
" clat - 180 * box_size / circ_earth,\n",
|
|
||||||
" 'urcrnrlon': (clon + 180 * box_size /\n",
|
|
||||||
" (circ_earth * np.cos(np.deg2rad(clat)))),\n",
|
|
||||||
" 'urcrnrlat':\n",
|
|
||||||
" clat + 180 * box_size / circ_earth\n",
|
|
||||||
" }\n",
|
|
||||||
" # The 'name' of the stop, indexed from 0 to num_stops-1\n",
|
|
||||||
" stops = np.array(range(0, num_stops))\n",
|
|
||||||
" # normaly distributed random distribution of stops within the box\n",
|
|
||||||
" stdv = 6 # the number of standard deviations 99.9% will be within +-3\n",
|
|
||||||
" lats = (self.extents['llcrnrlat'] + np.random.randn(num_stops) *\n",
|
|
||||||
" (self.extents['urcrnrlat'] - self.extents['llcrnrlat']) / stdv)\n",
|
|
||||||
" lons = (self.extents['llcrnrlon'] + np.random.randn(num_stops) *\n",
|
|
||||||
" (self.extents['urcrnrlon'] - self.extents['llcrnrlon']) / stdv)\n",
|
|
||||||
" # uniformly distributed integer demands.\n",
|
|
||||||
" demands = np.random.randint(min_demand, max_demand, num_stops)\n",
|
|
||||||
"\n",
|
|
||||||
" self.time_horizon = 24 * 60**2 # A 24 hour period.\n",
|
|
||||||
"\n",
|
|
||||||
" # The customers demand min_tw to max_tw hour time window for each\n",
|
|
||||||
" # delivery\n",
|
|
||||||
" time_windows = np.random.randint(min_tw * 3600, max_tw * 3600,\n",
|
|
||||||
" num_stops)\n",
|
|
||||||
" # The last time a delivery window can start\n",
|
|
||||||
" latest_time = self.time_horizon - time_windows\n",
|
|
||||||
" start_times = [None for o in time_windows]\n",
|
|
||||||
" stop_times = [None for o in time_windows]\n",
|
|
||||||
" # Make random timedeltas, nominally from the start of the day.\n",
|
|
||||||
" for idx in range(self.number):\n",
|
|
||||||
" stime = int(np.random.randint(0, latest_time[idx]))\n",
|
|
||||||
" start_times[idx] = timedelta(seconds=stime)\n",
|
|
||||||
" stop_times[idx] = (\n",
|
|
||||||
" start_times[idx] + timedelta(seconds=int(time_windows[idx])))\n",
|
|
||||||
" # A named tuple for the customer\n",
|
|
||||||
" Customer = namedtuple(\n",
|
|
||||||
" 'Customer',\n",
|
|
||||||
" [\n",
|
|
||||||
" 'index', # the index of the stop\n",
|
|
||||||
" 'demand', # the demand for the stop\n",
|
|
||||||
" 'lat', # the latitude of the stop\n",
|
|
||||||
" 'lon', # the longitude of the stop\n",
|
|
||||||
" 'tw_open', # timedelta window open\n",
|
|
||||||
" 'tw_close'\n",
|
|
||||||
" ]) # timedelta window cls\n",
|
|
||||||
"\n",
|
|
||||||
" self.customers = [\n",
|
|
||||||
" Customer(idx, dem, lat, lon, tw_open, tw_close)\n",
|
|
||||||
" for idx, dem, lat, lon, tw_open, tw_close in zip(\n",
|
|
||||||
" stops, demands, lats, lons, start_times, stop_times)\n",
|
|
||||||
" ]\n",
|
|
||||||
"\n",
|
|
||||||
" # The number of seconds needed to 'unload' 1 unit of goods.\n",
|
|
||||||
" self.service_time_per_dem = 300 # seconds\n",
|
|
||||||
"\n",
|
|
||||||
" def set_manager(self, manager):\n",
|
|
||||||
" self.manager = manager\n",
|
|
||||||
"\n",
|
|
||||||
" def central_start_node(self, invert=False):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a random starting node, with probability weighted by distance\n",
|
|
||||||
" from the centre of the extents, so that a central starting node is\n",
|
|
||||||
" likely.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: invert (Optional bool): When True, a peripheral starting node is\n",
|
|
||||||
" most likely.\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" int: a node index.\n",
|
|
||||||
"\n",
|
|
||||||
" Examples:\n",
|
|
||||||
" >>> customers.central_start_node(invert=True)\n",
|
|
||||||
" 42\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" num_nodes = len(self.customers)\n",
|
|
||||||
" dist = np.empty((num_nodes, 1))\n",
|
|
||||||
" for idx_to in range(num_nodes):\n",
|
|
||||||
" dist[idx_to] = self._haversine(self.center.lon, self.center.lat,\n",
|
|
||||||
" self.customers[idx_to].lon,\n",
|
|
||||||
" self.customers[idx_to].lat)\n",
|
|
||||||
" furthest = np.max(dist)\n",
|
|
||||||
"\n",
|
|
||||||
" if invert:\n",
|
|
||||||
" prob = dist * 1.0 / sum(dist)\n",
|
|
||||||
" else:\n",
|
|
||||||
" prob = (furthest - dist * 1.0) / sum(furthest - dist)\n",
|
|
||||||
" indexes = np.array([range(num_nodes)])\n",
|
|
||||||
" start_node = np.random.choice(\n",
|
|
||||||
" indexes.flatten(), size=1, replace=True, p=prob.flatten())\n",
|
|
||||||
" return start_node[0]\n",
|
|
||||||
"\n",
|
|
||||||
" def make_distance_mat(self, method='haversine'):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a distance matrix and make it a member of Customer, using the\n",
|
|
||||||
" method given in the call. Currently only Haversine (GC distance) is\n",
|
|
||||||
" implemented, but Manhattan, or using a maps API could be added here.\n",
|
|
||||||
" Raises an AssertionError for all other methods.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: method (Optional[str]): method of distance calculation to use. The\n",
|
|
||||||
" Haversine formula is the only method implemented.\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" Numpy array of node to node distances.\n",
|
|
||||||
"\n",
|
|
||||||
" Examples:\n",
|
|
||||||
" >>> dist_mat = customers.make_distance_mat(method='haversine')\n",
|
|
||||||
" >>> dist_mat = customers.make_distance_mat(method='manhattan')\n",
|
|
||||||
" AssertionError\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" self.distmat = np.zeros((self.number, self.number))\n",
|
|
||||||
" methods = {'haversine': self._haversine}\n",
|
|
||||||
" assert (method in methods)\n",
|
|
||||||
" for frm_idx in range(self.number):\n",
|
|
||||||
" for to_idx in range(self.number):\n",
|
|
||||||
" if frm_idx != to_idx:\n",
|
|
||||||
" frm_c = self.customers[frm_idx]\n",
|
|
||||||
" to_c = self.customers[to_idx]\n",
|
|
||||||
" self.distmat[frm_idx, to_idx] = self._haversine(\n",
|
|
||||||
" frm_c.lon, frm_c.lat, to_c.lon, to_c.lat)\n",
|
|
||||||
" return (self.distmat)\n",
|
|
||||||
"\n",
|
|
||||||
" def _haversine(self, lon1, lat1, lon2, lat2):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Calculate the great circle distance between two points\n",
|
|
||||||
" on the earth specified in decimal degrees of latitude and longitude.\n",
|
|
||||||
" https://en.wikipedia.org/wiki/Haversine_formula\n",
|
|
||||||
"\n",
|
|
||||||
" Args:\n",
|
|
||||||
" lon1: longitude of pt 1,\n",
|
|
||||||
" lat1: latitude of pt 1,\n",
|
|
||||||
" lon2: longitude of pt 2,\n",
|
|
||||||
" lat2: latitude of pt 2\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" the distace in km between pt1 and pt2\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" # convert decimal degrees to radians\n",
|
|
||||||
" lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])\n",
|
|
||||||
"\n",
|
|
||||||
" # haversine formula\n",
|
|
||||||
" dlon = lon2 - lon1\n",
|
|
||||||
" dlat = lat2 - lat1\n",
|
|
||||||
" a = (np.sin(dlat / 2)**2 +\n",
|
|
||||||
" np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2)\n",
|
|
||||||
" c = 2 * np.arcsin(np.sqrt(a))\n",
|
|
||||||
"\n",
|
|
||||||
" # 6367 km is the radius of the Earth\n",
|
|
||||||
" km = 6367 * c\n",
|
|
||||||
" return km\n",
|
|
||||||
"\n",
|
|
||||||
" def get_total_demand(self):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return the total demand of all customers.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" return (sum([c.demand for c in self.customers]))\n",
|
|
||||||
"\n",
|
|
||||||
" def return_dist_callback(self, **kwargs):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a callback function for the distance matrix.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: **kwargs: Arbitrary keyword arguments passed on to\n",
|
|
||||||
" make_distance_mat()\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" function: dist_return(a,b) A function that takes the 'from' node\n",
|
|
||||||
" index and the 'to' node index and returns the distance in km.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" self.make_distance_mat(**kwargs)\n",
|
|
||||||
"\n",
|
|
||||||
" def dist_return(from_index, to_index):\n",
|
|
||||||
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
|
|
||||||
" from_node = self.manager.IndexToNode(from_index)\n",
|
|
||||||
" to_node = self.manager.IndexToNode(to_index)\n",
|
|
||||||
" return (self.distmat[from_node][to_node])\n",
|
|
||||||
"\n",
|
|
||||||
" return dist_return\n",
|
|
||||||
"\n",
|
|
||||||
" def return_dem_callback(self):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a callback function that gives the demands.\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" function: dem_return(a) A function that takes the 'from' node\n",
|
|
||||||
" index and returns the distance in km.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" def dem_return(from_index):\n",
|
|
||||||
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
|
|
||||||
" from_node = self.manager.IndexToNode(from_index)\n",
|
|
||||||
" return (self.customers[from_node].demand)\n",
|
|
||||||
"\n",
|
|
||||||
" return dem_return\n",
|
|
||||||
"\n",
|
|
||||||
" def zero_depot_demands(self, depot):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Zero out the demands and time windows of depot. The Depots do not have\n",
|
|
||||||
" demands or time windows so this function clears them.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: depot (int): index of the stop to modify into a depot.\n",
|
|
||||||
" Examples: >>> customers.zero_depot_demands(5) >>>\n",
|
|
||||||
" customers.customers[5].demand == 0 True\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" start_depot = self.customers[depot]\n",
|
|
||||||
" self.customers[depot] = start_depot._replace(\n",
|
|
||||||
" demand=0, tw_open=None, tw_close=None)\n",
|
|
||||||
"\n",
|
|
||||||
" def make_service_time_call_callback(self):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a callback function that provides the time spent servicing the\n",
|
|
||||||
" customer. Here is it proportional to the demand given by\n",
|
|
||||||
" self.service_time_per_dem, default 300 seconds per unit demand.\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" function [dem_return(a, b)]: A function that takes the from/a node\n",
|
|
||||||
" index and the to/b node index and returns the service time at a\n",
|
|
||||||
"\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" def service_time_return(a, b):\n",
|
|
||||||
" return (self.customers[a].demand * self.service_time_per_dem)\n",
|
|
||||||
"\n",
|
|
||||||
" return service_time_return\n",
|
|
||||||
"\n",
|
|
||||||
" def make_transit_time_callback(self, speed_kmph=10):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Creates a callback function for transit time. Assuming an average\n",
|
|
||||||
" speed of speed_kmph\n",
|
|
||||||
" Args:\n",
|
|
||||||
" speed_kmph: the average speed in km/h\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" function [transit_time_return(a, b)]: A function that takes the\n",
|
|
||||||
" from/a node index and the to/b node index and returns the\n",
|
|
||||||
" transit time from a to b.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" def transit_time_return(a, b):\n",
|
|
||||||
" return (self.distmat[a][b] / (speed_kmph * 1.0 / 60**2))\n",
|
|
||||||
"\n",
|
|
||||||
" return transit_time_return\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"class Vehicles():\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" A Class to create and hold vehicle information.\n",
|
|
||||||
"\n",
|
|
||||||
" The Vehicles in a CVRPTW problem service the customers and belong to a\n",
|
|
||||||
" depot. The class Vehicles creates a list of named tuples describing the\n",
|
|
||||||
" Vehicles. The main characteristics are the vehicle capacity, fixed cost,\n",
|
|
||||||
" and cost per km. The fixed cost of using a certain type of vehicles can be\n",
|
|
||||||
" higher or lower than others. If a vehicle is used, i.e. this vehicle serves\n",
|
|
||||||
" at least one node, then this cost is added to the objective function.\n",
|
|
||||||
"\n",
|
|
||||||
" Note:\n",
|
|
||||||
" If numpy arrays are given for capacity and cost, then they must be of\n",
|
|
||||||
" the same length, and the number of vehicles are inferred from them.\n",
|
|
||||||
" If scalars are given, the fleet is homogeneous, and the number of\n",
|
|
||||||
" vehicles is determined by number.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: capacity (scalar or numpy array): The integer capacity of demand\n",
|
|
||||||
" units. cost (scalar or numpy array): The fixed cost of the vehicle. number\n",
|
|
||||||
" (Optional [int]): The number of vehicles in a homogeneous fleet.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
" def __init__(self, capacity=100, cost=100, number=None):\n",
|
|
||||||
"\n",
|
|
||||||
" Vehicle = namedtuple('Vehicle', ['index', 'capacity', 'cost'])\n",
|
|
||||||
"\n",
|
|
||||||
" if number is None:\n",
|
|
||||||
" self.number = np.size(capacity)\n",
|
|
||||||
" else:\n",
|
|
||||||
" self.number = number\n",
|
|
||||||
" idxs = np.array(range(0, self.number))\n",
|
|
||||||
"\n",
|
|
||||||
" if np.isscalar(capacity):\n",
|
|
||||||
" capacities = capacity * np.ones_like(idxs)\n",
|
|
||||||
" elif np.size(capacity) != self.number:\n",
|
|
||||||
" print('capacity is neither scalar, nor the same size as num!')\n",
|
|
||||||
" else:\n",
|
|
||||||
" capacities = capacity\n",
|
|
||||||
"\n",
|
|
||||||
" if np.isscalar(cost):\n",
|
|
||||||
" costs = cost * np.ones_like(idxs)\n",
|
|
||||||
" elif np.size(cost) != self.number:\n",
|
|
||||||
" print(np.size(cost))\n",
|
|
||||||
" print('cost is neither scalar, nor the same size as num!')\n",
|
|
||||||
" else:\n",
|
|
||||||
" costs = cost\n",
|
|
||||||
"\n",
|
|
||||||
" self.vehicles = [\n",
|
|
||||||
" Vehicle(idx, capacity, cost)\n",
|
|
||||||
" for idx, capacity, cost in zip(idxs, capacities, costs)\n",
|
|
||||||
" ]\n",
|
|
||||||
"\n",
|
|
||||||
" def get_total_capacity(self):\n",
|
|
||||||
" return (sum([c.capacity for c in self.vehicles]))\n",
|
|
||||||
"\n",
|
|
||||||
" def return_starting_callback(self, customers, sameStartFinish=False):\n",
|
|
||||||
" # create a different starting and finishing depot for each vehicle\n",
|
|
||||||
" self.starts = [\n",
|
|
||||||
" int(customers.central_start_node()) for o in range(self.number)\n",
|
|
||||||
" ]\n",
|
|
||||||
" if sameStartFinish:\n",
|
|
||||||
" self.ends = self.starts\n",
|
|
||||||
" else:\n",
|
|
||||||
" self.ends = [\n",
|
|
||||||
" int(customers.central_start_node(invert=True))\n",
|
|
||||||
" for o in range(self.number)\n",
|
|
||||||
" ]\n",
|
|
||||||
" # the depots will not have demands, so zero them.\n",
|
|
||||||
" for depot in self.starts:\n",
|
|
||||||
" customers.zero_depot_demands(depot)\n",
|
|
||||||
" for depot in self.ends:\n",
|
|
||||||
" customers.zero_depot_demands(depot)\n",
|
|
||||||
"\n",
|
|
||||||
" def start_return(v):\n",
|
|
||||||
" return (self.starts[v])\n",
|
|
||||||
"\n",
|
|
||||||
" return start_return\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def discrete_cmap(N, base_cmap=None):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Create an N-bin discrete colormap from the specified input map\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" # Note that if base_cmap is a string or None, you can simply do\n",
|
|
||||||
" # return plt.cm.get_cmap(base_cmap, N)\n",
|
|
||||||
" # The following works for string, None, or a colormap instance:\n",
|
|
||||||
"\n",
|
|
||||||
" base = plt.cm.get_cmap(base_cmap)\n",
|
|
||||||
" color_list = base(np.linspace(0, 1, N))\n",
|
|
||||||
" cmap_name = base.name + str(N)\n",
|
|
||||||
" return base.from_list(cmap_name, color_list, N)\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def vehicle_output_string(manager, routing, plan):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Return a string displaying the output of the routing instance and\n",
|
|
||||||
" assignment (plan).\n",
|
|
||||||
"\n",
|
|
||||||
" Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.\n",
|
|
||||||
" plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" (string) plan_output: describing each vehicle's plan.\n",
|
|
||||||
"\n",
|
|
||||||
" (List) dropped: list of dropped orders.\n",
|
|
||||||
"\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" dropped = []\n",
|
|
||||||
" for order in range(routing.Size()):\n",
|
|
||||||
" if (plan.Value(routing.NextVar(order)) == order):\n",
|
|
||||||
" dropped.append(str(order))\n",
|
|
||||||
"\n",
|
|
||||||
" capacity_dimension = routing.GetDimensionOrDie('Capacity')\n",
|
|
||||||
" time_dimension = routing.GetDimensionOrDie('Time')\n",
|
|
||||||
" plan_output = ''\n",
|
|
||||||
"\n",
|
|
||||||
" for route_number in range(routing.vehicles()):\n",
|
|
||||||
" order = routing.Start(route_number)\n",
|
|
||||||
" plan_output += 'Route {0}:'.format(route_number)\n",
|
|
||||||
" if routing.IsEnd(plan.Value(routing.NextVar(order))):\n",
|
|
||||||
" plan_output += ' Empty \\n'\n",
|
|
||||||
" else:\n",
|
|
||||||
" while True:\n",
|
|
||||||
" load_var = capacity_dimension.CumulVar(order)\n",
|
|
||||||
" time_var = time_dimension.CumulVar(order)\n",
|
|
||||||
" node = manager.IndexToNode(order)\n",
|
|
||||||
" plan_output += \\\n",
|
|
||||||
" ' {node} Load({load}) Time({tmin}, {tmax}) -> '.format(\n",
|
|
||||||
" node=node,\n",
|
|
||||||
" load=plan.Value(load_var),\n",
|
|
||||||
" tmin=str(timedelta(seconds=plan.Min(time_var))),\n",
|
|
||||||
" tmax=str(timedelta(seconds=plan.Max(time_var))))\n",
|
|
||||||
"\n",
|
|
||||||
" if routing.IsEnd(order):\n",
|
|
||||||
" plan_output += ' EndRoute {0}. \\n'.format(route_number)\n",
|
|
||||||
" break\n",
|
|
||||||
" order = plan.Value(routing.NextVar(order))\n",
|
|
||||||
" plan_output += '\\n'\n",
|
|
||||||
"\n",
|
|
||||||
" return (plan_output, dropped)\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def build_vehicle_route(manager, routing, plan, customers, veh_number):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Build a route for a vehicle by starting at the strat node and\n",
|
|
||||||
" continuing to the end node.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: routing (ortools.constraint_solver.pywrapcp.RoutingModel): routing.\n",
|
|
||||||
" plan (ortools.constraint_solver.pywrapcp.Assignment): the assignment.\n",
|
|
||||||
" customers (Customers): the customers instance. veh_number (int): index of\n",
|
|
||||||
" the vehicle\n",
|
|
||||||
"\n",
|
|
||||||
" Returns:\n",
|
|
||||||
" (List) route: indexes of the customers for vehicle veh_number\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" veh_used = routing.IsVehicleUsed(plan, veh_number)\n",
|
|
||||||
" print('Vehicle {0} is used {1}'.format(veh_number, veh_used))\n",
|
|
||||||
" if veh_used:\n",
|
|
||||||
" route = []\n",
|
|
||||||
" node = routing.Start(veh_number) # Get the starting node index\n",
|
|
||||||
" route.append(customers.customers[manager.IndexToNode(node)])\n",
|
|
||||||
" while not routing.IsEnd(node):\n",
|
|
||||||
" route.append(customers.customers[manager.IndexToNode(node)])\n",
|
|
||||||
" node = plan.Value(routing.NextVar(node))\n",
|
|
||||||
"\n",
|
|
||||||
" route.append(customers.customers[manager.IndexToNode(node)])\n",
|
|
||||||
" return route\n",
|
|
||||||
" else:\n",
|
|
||||||
" return None\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def plot_vehicle_routes(veh_route, ax1, customers, vehicles):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" Plot the vehicle routes on matplotlib axis ax1.\n",
|
|
||||||
"\n",
|
|
||||||
" Args: veh_route (dict): a dictionary of routes keyed by vehicle idx. ax1\n",
|
|
||||||
" (matplotlib.axes._subplots.AxesSubplot): Matplotlib axes customers\n",
|
|
||||||
" (Customers): the customers instance. vehicles (Vehicles): the vehicles\n",
|
|
||||||
" instance.\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" veh_used = [v for v in veh_route if veh_route[v] is not None]\n",
|
|
||||||
"\n",
|
|
||||||
" cmap = discrete_cmap(vehicles.number + 2, 'nipy_spectral')\n",
|
|
||||||
"\n",
|
|
||||||
" for veh_number in veh_used:\n",
|
|
||||||
"\n",
|
|
||||||
" lats, lons = zip(*[(c.lat, c.lon) for c in veh_route[veh_number]])\n",
|
|
||||||
" lats = np.array(lats)\n",
|
|
||||||
" lons = np.array(lons)\n",
|
|
||||||
" s_dep = customers.customers[vehicles.starts[veh_number]]\n",
|
|
||||||
" s_fin = customers.customers[vehicles.ends[veh_number]]\n",
|
|
||||||
" ax1.annotate(\n",
|
|
||||||
" 'v({veh}) S @ {node}'.format(\n",
|
|
||||||
" veh=veh_number, node=vehicles.starts[veh_number]),\n",
|
|
||||||
" xy=(s_dep.lon, s_dep.lat),\n",
|
|
||||||
" xytext=(10, 10),\n",
|
|
||||||
" xycoords='data',\n",
|
|
||||||
" textcoords='offset points',\n",
|
|
||||||
" arrowprops=dict(\n",
|
|
||||||
" arrowstyle='->',\n",
|
|
||||||
" connectionstyle='angle3,angleA=90,angleB=0',\n",
|
|
||||||
" shrinkA=0.05),\n",
|
|
||||||
" )\n",
|
|
||||||
" ax1.annotate(\n",
|
|
||||||
" 'v({veh}) F @ {node}'.format(\n",
|
|
||||||
" veh=veh_number, node=vehicles.ends[veh_number]),\n",
|
|
||||||
" xy=(s_fin.lon, s_fin.lat),\n",
|
|
||||||
" xytext=(10, -20),\n",
|
|
||||||
" xycoords='data',\n",
|
|
||||||
" textcoords='offset points',\n",
|
|
||||||
" arrowprops=dict(\n",
|
|
||||||
" arrowstyle='->',\n",
|
|
||||||
" connectionstyle='angle3,angleA=-90,angleB=0',\n",
|
|
||||||
" shrinkA=0.05),\n",
|
|
||||||
" )\n",
|
|
||||||
" ax1.plot(lons, lats, 'o', mfc=cmap(veh_number + 1))\n",
|
|
||||||
" ax1.quiver(\n",
|
|
||||||
" lons[:-1],\n",
|
|
||||||
" lats[:-1],\n",
|
|
||||||
" lons[1:] - lons[:-1],\n",
|
|
||||||
" lats[1:] - lats[:-1],\n",
|
|
||||||
" scale_units='xy',\n",
|
|
||||||
" angles='xy',\n",
|
|
||||||
" scale=1,\n",
|
|
||||||
" color=cmap(veh_number + 1))\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"def main():\n",
|
|
||||||
" # Create a set of customer, (and depot) stops.\n",
|
|
||||||
" customers = Customers(\n",
|
|
||||||
" num_stops=50,\n",
|
|
||||||
" min_demand=1,\n",
|
|
||||||
" max_demand=15,\n",
|
|
||||||
" box_size=40,\n",
|
|
||||||
" min_tw=3,\n",
|
|
||||||
" max_tw=6)\n",
|
|
||||||
"\n",
|
|
||||||
" # Create a list of inhomgenious vehicle capacities as integer units.\n",
|
|
||||||
" capacity = [50, 75, 100, 125, 150, 175, 200, 250]\n",
|
|
||||||
"\n",
|
|
||||||
" # Create a list of inhomogeneous fixed vehicle costs.\n",
|
|
||||||
" cost = [int(100 + 2 * np.sqrt(c)) for c in capacity]\n",
|
|
||||||
"\n",
|
|
||||||
" # Create a set of vehicles, the number set by the length of capacity.\n",
|
|
||||||
" vehicles = Vehicles(capacity=capacity, cost=cost)\n",
|
|
||||||
"\n",
|
|
||||||
" # check to see that the problem is feasible, if we don't have enough\n",
|
|
||||||
" # vehicles to cover the demand, there is no point in going further.\n",
|
|
||||||
" assert (customers.get_total_demand() < vehicles.get_total_capacity())\n",
|
|
||||||
"\n",
|
|
||||||
" # Set the starting nodes, and create a callback fn for the starting node.\n",
|
|
||||||
" start_fn = vehicles.return_starting_callback(\n",
|
|
||||||
" customers, sameStartFinish=False)\n",
|
|
||||||
"\n",
|
|
||||||
" # Create the routing index manager.\n",
|
|
||||||
" manager = pywrapcp.RoutingIndexManager(\n",
|
|
||||||
" customers.number, # int number\n",
|
|
||||||
" vehicles.number, # int number\n",
|
|
||||||
" vehicles.starts, # List of int start depot\n",
|
|
||||||
" vehicles.ends) # List of int end depot\n",
|
|
||||||
"\n",
|
|
||||||
" customers.set_manager(manager)\n",
|
|
||||||
"\n",
|
|
||||||
" # Set model parameters\n",
|
|
||||||
" model_parameters = pywrapcp.DefaultRoutingModelParameters()\n",
|
|
||||||
"\n",
|
|
||||||
" # The solver parameters can be accessed from the model parameters. For example :\n",
|
|
||||||
" # model_parameters.solver_parameters.CopyFrom(\n",
|
|
||||||
" # pywrapcp.Solver.DefaultSolverParameters())\n",
|
|
||||||
" # model_parameters.solver_parameters.trace_propagation = True\n",
|
|
||||||
"\n",
|
|
||||||
" # Make the routing model instance.\n",
|
|
||||||
" routing = pywrapcp.RoutingModel(manager, model_parameters)\n",
|
|
||||||
"\n",
|
|
||||||
" parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
|
|
||||||
" # Setting first solution heuristic (cheapest addition).\n",
|
|
||||||
" parameters.first_solution_strategy = (\n",
|
|
||||||
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
|
|
||||||
" # Routing: forbids use of TSPOpt neighborhood, (this is the default behaviour)\n",
|
|
||||||
" parameters.local_search_operators.use_tsp_opt = pywrapcp.BOOL_FALSE\n",
|
|
||||||
" # Disabling Large Neighborhood Search, (this is the default behaviour)\n",
|
|
||||||
" parameters.local_search_operators.use_path_lns = pywrapcp.BOOL_FALSE\n",
|
|
||||||
" parameters.local_search_operators.use_inactive_lns = pywrapcp.BOOL_FALSE\n",
|
|
||||||
"\n",
|
|
||||||
" parameters.time_limit.seconds = 10\n",
|
|
||||||
" parameters.use_full_propagation = True\n",
|
|
||||||
" #parameters.log_search = True\n",
|
|
||||||
"\n",
|
|
||||||
" # Create callback fns for distances, demands, service and transit-times.\n",
|
|
||||||
" dist_fn = customers.return_dist_callback()\n",
|
|
||||||
" dist_fn_index = routing.RegisterTransitCallback(dist_fn)\n",
|
|
||||||
"\n",
|
|
||||||
" dem_fn = customers.return_dem_callback()\n",
|
|
||||||
" dem_fn_index = routing.RegisterUnaryTransitCallback(dem_fn)\n",
|
|
||||||
"\n",
|
|
||||||
" # Create and register a transit callback.\n",
|
|
||||||
" serv_time_fn = customers.make_service_time_call_callback()\n",
|
|
||||||
" transit_time_fn = customers.make_transit_time_callback()\n",
|
|
||||||
" def tot_time_fn(from_index, to_index):\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" The time function we want is both transit time and service time.\n",
|
|
||||||
" \"\"\"\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 serv_time_fn(from_node, to_node) + transit_time_fn(from_node, to_node)\n",
|
|
||||||
"\n",
|
|
||||||
" tot_time_fn_index = routing.RegisterTransitCallback(tot_time_fn)\n",
|
|
||||||
"\n",
|
|
||||||
" # Set the cost function (distance callback) for each arc, homogeneous for\n",
|
|
||||||
" # all vehicles.\n",
|
|
||||||
" routing.SetArcCostEvaluatorOfAllVehicles(dist_fn_index)\n",
|
|
||||||
"\n",
|
|
||||||
" # Set vehicle costs for each vehicle, not homogeneous.\n",
|
|
||||||
" for veh in vehicles.vehicles:\n",
|
|
||||||
" routing.SetFixedCostOfVehicle(veh.cost, int(veh.index))\n",
|
|
||||||
"\n",
|
|
||||||
" # Add a dimension for vehicle capacities\n",
|
|
||||||
" null_capacity_slack = 0\n",
|
|
||||||
" routing.AddDimensionWithVehicleCapacity(\n",
|
|
||||||
" dem_fn_index, # demand callback\n",
|
|
||||||
" null_capacity_slack,\n",
|
|
||||||
" capacity, # capacity array\n",
|
|
||||||
" True,\n",
|
|
||||||
" 'Capacity')\n",
|
|
||||||
" # Add a dimension for time and a limit on the total time_horizon\n",
|
|
||||||
" routing.AddDimension(\n",
|
|
||||||
" tot_time_fn_index, # total time function callback\n",
|
|
||||||
" customers.time_horizon,\n",
|
|
||||||
" customers.time_horizon,\n",
|
|
||||||
" True,\n",
|
|
||||||
" 'Time')\n",
|
|
||||||
"\n",
|
|
||||||
" time_dimension = routing.GetDimensionOrDie('Time')\n",
|
|
||||||
" for cust in customers.customers:\n",
|
|
||||||
" if cust.tw_open is not None:\n",
|
|
||||||
" time_dimension.CumulVar(manager.NodeToIndex(cust.index)).SetRange(\n",
|
|
||||||
" cust.tw_open.seconds, cust.tw_close.seconds)\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" To allow the dropping of orders, we add disjunctions to all the customer\n",
|
|
||||||
" nodes. Each disjunction is a list of 1 index, which allows that customer to\n",
|
|
||||||
" be active or not, with a penalty if not. The penalty should be larger\n",
|
|
||||||
" than the cost of servicing that customer, or it will always be dropped!\n",
|
|
||||||
" \"\"\"\n",
|
|
||||||
" # To add disjunctions just to the customers, make a list of non-depots.\n",
|
|
||||||
" non_depot = set(range(customers.number))\n",
|
|
||||||
" non_depot.difference_update(vehicles.starts)\n",
|
|
||||||
" non_depot.difference_update(vehicles.ends)\n",
|
|
||||||
" penalty = 400000 # The cost for dropping a node from the plan.\n",
|
|
||||||
" nodes = [routing.AddDisjunction([manager.NodeToIndex(c)], penalty) for c in non_depot]\n",
|
|
||||||
"\n",
|
|
||||||
" # This is how you would implement partial routes if you already knew part\n",
|
|
||||||
" # of a feasible solution for example:\n",
|
|
||||||
" # partial = np.random.choice(list(non_depot), size=(4,5), replace=False)\n",
|
|
||||||
"\n",
|
|
||||||
" # routing.CloseModel()\n",
|
|
||||||
" # partial_list = [partial[0,:].tolist(),\n",
|
|
||||||
" # partial[1,:].tolist(),\n",
|
|
||||||
" # partial[2,:].tolist(),\n",
|
|
||||||
" # partial[3,:].tolist(),\n",
|
|
||||||
" # [],[],[],[]]\n",
|
|
||||||
" # print(routing.ApplyLocksToAllVehicles(partial_list, False))\n",
|
|
||||||
"\n",
|
|
||||||
" # Solve the problem !\n",
|
|
||||||
" assignment = routing.SolveWithParameters(parameters)\n",
|
|
||||||
"\n",
|
|
||||||
" # The rest is all optional for saving, printing or plotting the solution.\n",
|
|
||||||
" if assignment:\n",
|
|
||||||
" ## save the assignment, (Google Protobuf format)\n",
|
|
||||||
" #save_file_base = os.path.realpath(__file__).split('.')[0]\n",
|
|
||||||
" #if routing.WriteAssignment(save_file_base + '_assignment.ass'):\n",
|
|
||||||
" # print('succesfully wrote assignment to file ' + save_file_base +\n",
|
|
||||||
" # '_assignment.ass')\n",
|
|
||||||
"\n",
|
|
||||||
" print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))\n",
|
|
||||||
"\n",
|
|
||||||
" plan_output, dropped = vehicle_output_string(manager, routing, assignment)\n",
|
|
||||||
" print(plan_output)\n",
|
|
||||||
" print('dropped nodes: ' + ', '.join(dropped))\n",
|
|
||||||
"\n",
|
|
||||||
" # you could print debug information like this:\n",
|
|
||||||
" # print(routing.DebugOutputAssignment(assignment, 'Capacity'))\n",
|
|
||||||
"\n",
|
|
||||||
" vehicle_routes = {}\n",
|
|
||||||
" for veh in range(vehicles.number):\n",
|
|
||||||
" vehicle_routes[veh] = build_vehicle_route(manager, routing, assignment,\n",
|
|
||||||
" customers, veh)\n",
|
|
||||||
"\n",
|
|
||||||
" # Plotting of the routes in matplotlib.\n",
|
|
||||||
" fig = plt.figure()\n",
|
|
||||||
" ax = fig.add_subplot(111)\n",
|
|
||||||
" # Plot all the nodes as black dots.\n",
|
|
||||||
" clon, clat = zip(*[(c.lon, c.lat) for c in customers.customers])\n",
|
|
||||||
" ax.plot(clon, clat, 'k.')\n",
|
|
||||||
" # plot the routes as arrows\n",
|
|
||||||
" plot_vehicle_routes(vehicle_routes, ax, customers, vehicles)\n",
|
|
||||||
" plt.show()\n",
|
|
||||||
"\n",
|
|
||||||
" else:\n",
|
|
||||||
" print('No assignment')\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"main()\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"language_info": {
|
|
||||||
"name": "python"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
||||||
@@ -92,8 +92,8 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from ortools.sat.colab import visualization\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"from ortools.sat.colab import visualization\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def main(_) -> None:\n",
|
"def main(_) -> None:\n",
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
" branches = collector.Branches(i)\n",
|
" branches = collector.Branches(i)\n",
|
||||||
" failures = collector.Failures(i)\n",
|
" failures = collector.Failures(i)\n",
|
||||||
" print(\n",
|
" print(\n",
|
||||||
" (\"Solution #%i: value = %i, failures = %i, branches = %i,\" \"time = %i ms\")\n",
|
" \"Solution #%i: value = %i, failures = %i, branches = %i,time = %i ms\"\n",
|
||||||
" % (i, obj_value, failures, branches, time)\n",
|
" % (i, obj_value, failures, branches, time)\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" time = solver.WallTime()\n",
|
" time = solver.WallTime()\n",
|
||||||
|
|||||||
@@ -93,8 +93,8 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from typing import Sequence\n",
|
"from typing import Sequence\n",
|
||||||
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_ORDER = flags.define_integer(\"order\", 8, \"Order of the ruler.\")\n",
|
"_ORDER = flags.define_integer(\"order\", 8, \"Order of the ruler.\")\n",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
" # Solve the model.\n",
|
" # Solve the model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solution_printer = cp_model.ObjectiveSolutionPrinter()\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",
|
" status = solver.solve(model, solution_printer)\n",
|
||||||
|
|||||||
376
examples/notebook/examples/horse_jumping_show.ipynb
Normal file
376
examples/notebook/examples/horse_jumping_show.ipynb
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"# horse_jumping_show"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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/horse_jumping_show.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/horse_jumping_show.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",
|
||||||
|
"Horse Jumping Show.\n",
|
||||||
|
"\n",
|
||||||
|
"A major three-day horse jumping competition is scheduled next winter in Geneva.\n",
|
||||||
|
"The show features riders and horses from all over the world, competing in\n",
|
||||||
|
"several different competitions throughout the show. Six months before the show,\n",
|
||||||
|
"riders submit the entries (i.e., rider name, horse, competition) to the\n",
|
||||||
|
"organizers. Riders can submit multiple entries, for example, to compete in the\n",
|
||||||
|
"same competition with multiple horses, or to compete in several competitions.\n",
|
||||||
|
"\n",
|
||||||
|
"There are additional space limitations. For example, the venue has 100 stalls,\n",
|
||||||
|
"4 arenas (where competitions can be scheduled), and 6 paddocks (where riders\n",
|
||||||
|
"warm up before their turn). It is also ideal that paddocks are not overloaded by\n",
|
||||||
|
"riders from multiple competitions.\n",
|
||||||
|
"\n",
|
||||||
|
"The organizer's goal is find a schedule in which competitions don't overlap, and\n",
|
||||||
|
"the times at which they happen are scattered throughout the day (and hopefully\n",
|
||||||
|
"not that early in the morning). The starting times of the competitions should be\n",
|
||||||
|
"at the hour or 30 minutes past the hour (e.g. 9:30, 10:00, 10:30, etc.).\n",
|
||||||
|
"Competitions can only be scheduled while there is daylight, except for\n",
|
||||||
|
"competitions scheduled in the Main Stage arena, which is covered and has proper\n",
|
||||||
|
"lighting. Also, beginner competitions (1.10m or less) are scheduled on the first\n",
|
||||||
|
"day, and advanced competitions (1.50m or more) are scheduled on the last day.\n",
|
||||||
|
"\n",
|
||||||
|
"The information for next winter's show is as follows:\n",
|
||||||
|
"Available stalls: 100\n",
|
||||||
|
"Number of riders: 100\n",
|
||||||
|
"Number of horses: 130\n",
|
||||||
|
"Number of requested Entries: 200\n",
|
||||||
|
"Number of competitions: 15\n",
|
||||||
|
"\n",
|
||||||
|
"Venue:\n",
|
||||||
|
"- Main Stage arena: Covered (9AM-11PM)\n",
|
||||||
|
"- Highlands arena: Daylight Only (9AM-5PM)\n",
|
||||||
|
"- Sawdust arena: Daylight Only (9AM-5PM)\n",
|
||||||
|
"- Paddock1 has capacity for 10 riders and serves Main Stage\n",
|
||||||
|
"- Paddock2 has capacity for 6 riders and serves Main Stage\n",
|
||||||
|
"- Paddock3 has capacity for 8 riders and serves Main Stage, Highlands\n",
|
||||||
|
"- Paddock4 has capacity for 8 riders and serves Highlands, Sawdust\n",
|
||||||
|
"- Paddock5 has capacity for 9 riders and serves Sawdust\n",
|
||||||
|
"- Paddock6 has capacity for 7 riders and serves Sawdust\n",
|
||||||
|
"\n",
|
||||||
|
"competitions:\n",
|
||||||
|
"- C_5_1.10m_Year_Olds 1.10m - 60 minutes\n",
|
||||||
|
"- C_6_1.25m_Year_Olds 1.25m - 90 minutes\n",
|
||||||
|
"- C_7_1.35m_Year_Olds 1.35m - 120 minutes\n",
|
||||||
|
"- C_0.8m_Jumpers 0.80m - 240 minutes\n",
|
||||||
|
"- C_1.0m_Jumpers 1.00m - 180 minutes\n",
|
||||||
|
"- C_1.10m_Jumpers 1.10m - 180 minutes\n",
|
||||||
|
"- C_1.20m_Jumpers 1.20m - 120 minutes\n",
|
||||||
|
"- C_1.30m_Jumpers 1.30m - 120 minutes\n",
|
||||||
|
"- C_1.40m_Jumpers 1.40m - 120 minutes\n",
|
||||||
|
"- C_1.20m_Derby 1.20m - 180 minutes\n",
|
||||||
|
"- C_1.35m_Derby 1.35m - 180 minutes\n",
|
||||||
|
"- C_1.45m_Derby 1.45m - 180 minutes\n",
|
||||||
|
"- C_1.40m_Open 1.40m - 120 minutes\n",
|
||||||
|
"- C_1.50m_Open 1.50m - 180 minutes\n",
|
||||||
|
"- C_1.60m_Grand_Prix 1.60m - 240 minutes\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import dataclasses\n",
|
||||||
|
"import numpy as np\n",
|
||||||
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class Arena:\n",
|
||||||
|
" \"\"\"Data for an arena.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" id: str\n",
|
||||||
|
" hours: str\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class Competition:\n",
|
||||||
|
" \"\"\"Data for a competition.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" id: str\n",
|
||||||
|
" height: float\n",
|
||||||
|
" duration: int\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class HorseJumpingShowData:\n",
|
||||||
|
" \"\"\"Horse Jumping Show Data.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" num_days: int\n",
|
||||||
|
" competitions: list[Competition]\n",
|
||||||
|
" arenas: list[Arena]\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class ScheduledCompetition:\n",
|
||||||
|
" \"\"\"Horse Jumping Show Schedule.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" completion: str\n",
|
||||||
|
" day: int\n",
|
||||||
|
" arena: str\n",
|
||||||
|
" start_time: str\n",
|
||||||
|
" end_time: str\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def generate_horse_jumping_show_data() -> HorseJumpingShowData:\n",
|
||||||
|
" \"\"\"Generates the horse jumping show data.\"\"\"\n",
|
||||||
|
" arenas = [\n",
|
||||||
|
" Arena(id=\"Main Stage\", hours=\"9AM-9PM\"),\n",
|
||||||
|
" Arena(id=\"Highlands\", hours=\"9AM-5PM\"),\n",
|
||||||
|
" Arena(id=\"Sawdust\", hours=\"9AM-5PM\"),\n",
|
||||||
|
" ]\n",
|
||||||
|
" competitions = [\n",
|
||||||
|
" Competition(id=\"C_5_1.10m_Year_Olds\", height=1.1, duration=60),\n",
|
||||||
|
" Competition(id=\"C_6_1.25m_Year_Olds\", height=1.25, duration=90),\n",
|
||||||
|
" Competition(id=\"C_7_1.35m_Year_Olds\", height=1.35, duration=120),\n",
|
||||||
|
" Competition(id=\"C_0.8m_Jumpers\", height=0.8, duration=240),\n",
|
||||||
|
" Competition(id=\"C_1.0m_Jumpers\", height=1.0, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.10m_Jumpers\", height=1.10, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.20m_Jumpers\", height=1.20, duration=120),\n",
|
||||||
|
" Competition(id=\"C_1.30m_Jumpers\", height=1.30, duration=120),\n",
|
||||||
|
" Competition(id=\"C_1.40m_Jumpers\", height=1.40, duration=120),\n",
|
||||||
|
" Competition(id=\"C_1.20m_Derby\", height=1.20, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.35m_Derby\", height=1.35, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.45m_Derby\", height=1.45, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.40m_Open\", height=1.40, duration=120),\n",
|
||||||
|
" Competition(id=\"C_1.50m_Open\", height=1.50, duration=180),\n",
|
||||||
|
" Competition(id=\"C_1.60m_Grand_Prix\", height=1.60, duration=240),\n",
|
||||||
|
" ]\n",
|
||||||
|
" return HorseJumpingShowData(num_days=3, competitions=competitions, arenas=arenas)\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def solve() -> list[ScheduledCompetition]:\n",
|
||||||
|
" \"\"\"Solves the horse jumping show problem.\"\"\"\n",
|
||||||
|
" data = generate_horse_jumping_show_data()\n",
|
||||||
|
" num_days = data.num_days\n",
|
||||||
|
" competitions = data.competitions\n",
|
||||||
|
" arenas = data.arenas\n",
|
||||||
|
" day_index = list(range(num_days))\n",
|
||||||
|
"\n",
|
||||||
|
" # Time parser.\n",
|
||||||
|
" def parse_time(t_str):\n",
|
||||||
|
" hour = int(t_str[:-2])\n",
|
||||||
|
" if \"PM\" in t_str and hour != 12:\n",
|
||||||
|
" hour += 12\n",
|
||||||
|
" if \"AM\" in t_str and hour == 12:\n",
|
||||||
|
" hour = 0\n",
|
||||||
|
" return hour * 60\n",
|
||||||
|
"\n",
|
||||||
|
" # Schedule time intervals for each arena.\n",
|
||||||
|
" schedule_interval_by_arena = {}\n",
|
||||||
|
" for arena in arenas:\n",
|
||||||
|
" start_h_str, end_h_str = arena.hours.split(\"-\")\n",
|
||||||
|
" start_time = parse_time(start_h_str)\n",
|
||||||
|
" end_time = parse_time(end_h_str)\n",
|
||||||
|
" schedule_interval_by_arena[arena.id] = (start_time, end_time)\n",
|
||||||
|
"\n",
|
||||||
|
" # Map time to 30-minute intervals and back.\n",
|
||||||
|
" time_slot_size = 30\n",
|
||||||
|
"\n",
|
||||||
|
" def time_to_slot(time_in_minutes: int):\n",
|
||||||
|
" return time_in_minutes // time_slot_size\n",
|
||||||
|
"\n",
|
||||||
|
" def slot_to_time(slot_index: int):\n",
|
||||||
|
" return slot_index * time_slot_size\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Model Creation ---\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Variables ---\n",
|
||||||
|
" # Competition scheduling variables per arena and day.\n",
|
||||||
|
" competition_assignments = np.empty(\n",
|
||||||
|
" (len(competitions), len(arenas), num_days), dtype=object\n",
|
||||||
|
" )\n",
|
||||||
|
" for c, comp in enumerate(competitions):\n",
|
||||||
|
" for a, arena in enumerate(arenas):\n",
|
||||||
|
" for d in day_index:\n",
|
||||||
|
" competition_assignments[c, a, d] = model.new_bool_var(\n",
|
||||||
|
" f\"competition_scheduled_{comp.id}_{arena.id}_{d}\"\n",
|
||||||
|
" )\n",
|
||||||
|
" # Time intervals and start times for each competition. We model time steps\n",
|
||||||
|
" # 0,1,2,... to represent the start times in 30 minutes intervals, as opposed\n",
|
||||||
|
" # to represent the start times in minutes.\n",
|
||||||
|
" competition_start_times = np.empty(\n",
|
||||||
|
" (len(competitions), len(arenas), num_days), dtype=object\n",
|
||||||
|
" )\n",
|
||||||
|
" competition_intervals = np.empty(\n",
|
||||||
|
" (len(competitions), len(arenas), num_days), dtype=object\n",
|
||||||
|
" )\n",
|
||||||
|
" for c, comp in enumerate(competitions):\n",
|
||||||
|
" for a, arena in enumerate(arenas):\n",
|
||||||
|
" earliest_start_time, latest_end_time = schedule_interval_by_arena[arena.id]\n",
|
||||||
|
" latest_start_time = latest_end_time - comp.duration\n",
|
||||||
|
" for d in day_index:\n",
|
||||||
|
" competition_start_times[c, a, d] = model.new_int_var(\n",
|
||||||
|
" time_to_slot(earliest_start_time),\n",
|
||||||
|
" time_to_slot(latest_start_time),\n",
|
||||||
|
" f\"start_time_{comp.id}_{arena.id}_{d}\",\n",
|
||||||
|
" )\n",
|
||||||
|
" competition_intervals[c, a, d] = (\n",
|
||||||
|
" model.new_optional_fixed_size_interval_var(\n",
|
||||||
|
" competition_start_times[c, a, d],\n",
|
||||||
|
" time_to_slot(comp.duration),\n",
|
||||||
|
" competition_assignments[c, a, d],\n",
|
||||||
|
" f\"task_{comp.id}_{arena.id}_{d}\",\n",
|
||||||
|
" )\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Constraints ---\n",
|
||||||
|
" # Every competition must be scheduled, enforcing that beginner competitions\n",
|
||||||
|
" # are on day 1, and advanced competitions are on day 3.\n",
|
||||||
|
" for c, comp in enumerate(competitions):\n",
|
||||||
|
" model.add(np.sum(competition_assignments[c, :, :]) == 1)\n",
|
||||||
|
" # Beginner competitions are on the first day.\n",
|
||||||
|
" if comp.height <= 1.10:\n",
|
||||||
|
" beginners_day = 0\n",
|
||||||
|
" model.add(np.sum(competition_assignments[c, :, beginners_day]) == 1)\n",
|
||||||
|
" # Advanced competitions are on the last day.\n",
|
||||||
|
" if comp.height >= 1.50:\n",
|
||||||
|
" advanced_day = num_days - 1\n",
|
||||||
|
" model.add(np.sum(competition_assignments[c, :, advanced_day]) == 1)\n",
|
||||||
|
"\n",
|
||||||
|
" # Competitions scheduled on the same arena and on the same day can't overlap.\n",
|
||||||
|
" for a, _ in enumerate(arenas):\n",
|
||||||
|
" for day in range(num_days):\n",
|
||||||
|
" model.add_no_overlap(competition_intervals[:, a, day])\n",
|
||||||
|
"\n",
|
||||||
|
" # Start times should be scattered across the day.\n",
|
||||||
|
" for a, _ in enumerate(arenas):\n",
|
||||||
|
" for day in day_index:\n",
|
||||||
|
" model.add_all_different(competition_start_times[:, a, day])\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Objective ---\n",
|
||||||
|
" model.maximize(np.sum(competition_start_times))\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Solve ---\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" solver.parameters.max_time_in_seconds = 30.0\n",
|
||||||
|
" solver.parameters.log_search_progress = True\n",
|
||||||
|
" solver.parameters.num_workers = 16\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # --- Print Solution ---\n",
|
||||||
|
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
|
||||||
|
" schedule = []\n",
|
||||||
|
" for day in range(num_days):\n",
|
||||||
|
" for c, comp in enumerate(competitions):\n",
|
||||||
|
" for a, arena in enumerate(arenas):\n",
|
||||||
|
" if solver.value(competition_assignments[c, a, day]):\n",
|
||||||
|
" start_time_minutes = slot_to_time(\n",
|
||||||
|
" solver.value(competition_start_times[c, a, day])\n",
|
||||||
|
" )\n",
|
||||||
|
" start_h, start_m = divmod(start_time_minutes, 60)\n",
|
||||||
|
" end_h, end_m = divmod(start_time_minutes + comp.duration, 60)\n",
|
||||||
|
" schedule.append(\n",
|
||||||
|
" ScheduledCompetition(\n",
|
||||||
|
" completion=comp.id,\n",
|
||||||
|
" day=day + 1,\n",
|
||||||
|
" arena=arena.id,\n",
|
||||||
|
" start_time=f\"{start_h:02d}:{start_m:02d}\",\n",
|
||||||
|
" end_time=f\"{end_h:02d}:{end_m:02d}\",\n",
|
||||||
|
" )\n",
|
||||||
|
" )\n",
|
||||||
|
" # Sort and print schedule for readability.\n",
|
||||||
|
" schedule.sort(key=lambda x: (x.day, x.start_time))\n",
|
||||||
|
" print(\"Schedule:\")\n",
|
||||||
|
" for item in schedule:\n",
|
||||||
|
" print(\n",
|
||||||
|
" f\"Day {item.day}: {item.completion} in {item.arena} from\"\n",
|
||||||
|
" f\" {item.start_time} to {item.end_time}.\"\n",
|
||||||
|
" )\n",
|
||||||
|
" return schedule\n",
|
||||||
|
" elif status == cp_model.INFEASIBLE:\n",
|
||||||
|
" print(\"Problem is infeasible.\")\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(\"No solution found.\")\n",
|
||||||
|
" # Return an empty schedule if no solution is found.\n",
|
||||||
|
" return []\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def main(_):\n",
|
||||||
|
" solve()\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"main()\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -93,8 +93,6 @@
|
|||||||
"import numpy as np\n",
|
"import numpy as np\n",
|
||||||
"import pandas as pd\n",
|
"import pandas as pd\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -227,7 +225,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -329,7 +327,7 @@
|
|||||||
" # solve model.\n",
|
" # solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -450,7 +448,7 @@
|
|||||||
" # solve model.\n",
|
" # solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -101,8 +101,6 @@
|
|||||||
"from typing import Dict, Sequence\n",
|
"from typing import Dict, Sequence\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\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",
|
||||||
@@ -340,7 +338,7 @@
|
|||||||
" # solve model.\n",
|
" # solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" solver.solve(model)\n",
|
" solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -407,7 +405,7 @@
|
|||||||
" # solve model.\n",
|
" # solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" solver.solve(model)\n",
|
" solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -75,9 +75,9 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"Test linear sum assignment on a 4x4 matrix.\n",
|
"Test linear sum assignment on a 4x4 matrix.\n",
|
||||||
"\n",
|
"\n",
|
||||||
" Example taken from:\n",
|
"Example taken from:\n",
|
||||||
" http://www.ee.oulu.fi/~mpa/matreng/eem1_2-1.htm with kCost[0][1]\n",
|
"http://www.ee.oulu.fi/~mpa/matreng/eem1_2-1.htm with kCost[0][1]\n",
|
||||||
" modified so the optimum solution is unique.\n",
|
"modified so the optimum solution is unique.\n",
|
||||||
"\n"
|
"\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -96,7 +96,12 @@
|
|||||||
" \"\"\"Test linear sum assignment on a 4x4 matrix.\"\"\"\n",
|
" \"\"\"Test linear sum assignment on a 4x4 matrix.\"\"\"\n",
|
||||||
" num_sources = 4\n",
|
" num_sources = 4\n",
|
||||||
" num_targets = 4\n",
|
" num_targets = 4\n",
|
||||||
" cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n",
|
" cost = [\n",
|
||||||
|
" [90, 76, 75, 80],\n",
|
||||||
|
" [35, 85, 55, 65],\n",
|
||||||
|
" [125, 95, 90, 105],\n",
|
||||||
|
" [45, 110, 95, 115],\n",
|
||||||
|
" ]\n",
|
||||||
" expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]\n",
|
" expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
|
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from typing import Sequence\n",
|
"from typing import Sequence\n",
|
||||||
|
"\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -79,7 +79,8 @@
|
|||||||
"visit all boxes in order, and walk on each block in a 4x4x4 map exactly once.\n",
|
"visit all boxes in order, and walk on each block in a 4x4x4 map exactly once.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Admissible moves are one step in one of the 6 directions:\n",
|
"Admissible moves are one step in one of the 6 directions:\n",
|
||||||
" x+, x-, y+, y-, z+(up), z-(down)\n"
|
" x+, x-, y+, y-, z+(up), z-(down)\n",
|
||||||
|
"\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -92,7 +93,6 @@
|
|||||||
"from typing import Dict, Sequence, Tuple\n",
|
"from typing import Dict, Sequence, Tuple\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_OUTPUT_PROTO = flags.define_string(\n",
|
"_OUTPUT_PROTO = flags.define_string(\n",
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" result = solver.solve(model)\n",
|
" result = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -87,7 +87,6 @@
|
|||||||
"from typing import List\n",
|
"from typing import List\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -139,7 +138,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
" print(solver.response_stats())\n",
|
" print(solver.response_stats())\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -225,7 +224,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
" print(solver.response_stats())\n",
|
" print(solver.response_stats())\n",
|
||||||
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
|
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
|
||||||
|
|||||||
388
examples/notebook/examples/music_playlist_sat.ipynb
Normal file
388
examples/notebook/examples/music_playlist_sat.ipynb
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"# music_playlist_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/music_playlist_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/music_playlist_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",
|
||||||
|
"Create a balanced music playlist.\n",
|
||||||
|
"\n",
|
||||||
|
"Create a music playlist by selecting tunes from a list of tunes.\n",
|
||||||
|
"\n",
|
||||||
|
"Each tune has a duration in seconds and a music genre (e.g. Rock, Disco, Techno,\n",
|
||||||
|
"etc).\n",
|
||||||
|
"\n",
|
||||||
|
"The total playlist duration must be as close as possible to a given total\n",
|
||||||
|
"duration. Each tune can appear at most once in the playlist. All existing\n",
|
||||||
|
"genres must appear at least once in the playlist. Two consecutive tunes must be\n",
|
||||||
|
"of different genres. There is a positive cost to go from a genre to another, and\n",
|
||||||
|
"the playlist must minimize this cost overall.\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from collections.abc import Sequence\n",
|
||||||
|
"\n",
|
||||||
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def Solve():\n",
|
||||||
|
" \"\"\"Solves the music playlist problem.\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 1. Data\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" tunes = [\n",
|
||||||
|
" (\"Song 01\", 202, \"Pop\"),\n",
|
||||||
|
" (\"Song 02\", 233, \"Techno\"),\n",
|
||||||
|
" (\"Song 03\", 108, \"Disco\"),\n",
|
||||||
|
" (\"Song 04\", 281, \"Disco\"),\n",
|
||||||
|
" (\"Song 05\", 129, \"Techno\"),\n",
|
||||||
|
" (\"Song 06\", 122, \"Techno\"),\n",
|
||||||
|
" (\"Song 07\", 244, \"Pop\"),\n",
|
||||||
|
" (\"Song 08\", 178, \"Techno\"),\n",
|
||||||
|
" (\"Song 09\", 213, \"Techno\"),\n",
|
||||||
|
" (\"Song 10\", 124, \"Rock\"),\n",
|
||||||
|
" (\"Song 11\", 120, \"Disco\"),\n",
|
||||||
|
" (\"Song 12\", 196, \"Rock\"),\n",
|
||||||
|
" (\"Song 13\", 249, \"Disco\"),\n",
|
||||||
|
" (\"Song 14\", 294, \"Disco\"),\n",
|
||||||
|
" (\"Song 15\", 103, \"Techno\"),\n",
|
||||||
|
" (\"Song 16\", 179, \"Disco\"),\n",
|
||||||
|
" (\"Song 17\", 146, \"Disco\"),\n",
|
||||||
|
" (\"Song 18\", 126, \"Techno\"),\n",
|
||||||
|
" (\"Song 19\", 100, \"Pop\"),\n",
|
||||||
|
" (\"Song 20\", 122, \"Disco\"),\n",
|
||||||
|
" (\"Song 21\", 190, \"Disco\"),\n",
|
||||||
|
" (\"Song 22\", 181, \"Techno\"),\n",
|
||||||
|
" (\"Song 23\", 273, \"Pop\"),\n",
|
||||||
|
" (\"Song 24\", 121, \"Disco\"),\n",
|
||||||
|
" (\"Song 25\", 159, \"Pop\"),\n",
|
||||||
|
" (\"Song 26\", 234, \"Rock\"),\n",
|
||||||
|
" (\"Song 27\", 169, \"Rock\"),\n",
|
||||||
|
" (\"Song 28\", 151, \"Rock\"),\n",
|
||||||
|
" (\"Song 29\", 142, \"Techno\"),\n",
|
||||||
|
" (\"Song 30\", 245, \"Pop\"),\n",
|
||||||
|
" (\"Song 31\", 281, \"Techno\"),\n",
|
||||||
|
" (\"Song 32\", 154, \"Rock\"),\n",
|
||||||
|
" (\"Song 33\", 148, \"Disco\"),\n",
|
||||||
|
" (\"Song 34\", 120, \"Pop\"),\n",
|
||||||
|
" (\"Song 35\", 163, \"Disco\"),\n",
|
||||||
|
" (\"Song 36\", 158, \"Pop\"),\n",
|
||||||
|
" (\"Song 37\", 235, \"Rock\"),\n",
|
||||||
|
" (\"Song 38\", 106, \"Techno\"),\n",
|
||||||
|
" (\"Song 39\", 117, \"Disco\"),\n",
|
||||||
|
" (\"Song 40\", 110, \"Pop\"),\n",
|
||||||
|
" (\"Song 41\", 144, \"Rock\"),\n",
|
||||||
|
" (\"Song 42\", 156, \"Disco\"),\n",
|
||||||
|
" (\"Song 43\", 204, \"Rock\"),\n",
|
||||||
|
" (\"Song 44\", 108, \"Pop\"),\n",
|
||||||
|
" (\"Song 45\", 255, \"Pop\"),\n",
|
||||||
|
" (\"Song 46\", 165, \"Rock\"),\n",
|
||||||
|
" (\"Song 47\", 290, \"Disco\"),\n",
|
||||||
|
" (\"Song 48\", 242, \"Pop\"),\n",
|
||||||
|
" (\"Song 49\", 272, \"Rock\"),\n",
|
||||||
|
" (\"Song 50\", 212, \"Pop\"),\n",
|
||||||
|
" ]\n",
|
||||||
|
"\n",
|
||||||
|
" # Genre transition costs. A higher cost means a less desirable transition.\n",
|
||||||
|
" genre_transition_costs = {\n",
|
||||||
|
" \"Rock\": {\"Pop\": 3, \"Disco\": 5, \"Techno\": 7},\n",
|
||||||
|
" \"Pop\": {\"Rock\": 3, \"Disco\": 6, \"Techno\": 8},\n",
|
||||||
|
" \"Disco\": {\"Rock\": 5, \"Pop\": 6, \"Techno\": 9},\n",
|
||||||
|
" \"Techno\": {\"Rock\": 7, \"Pop\": 8, \"Disco\": 9},\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" num_tunes = len(tunes)\n",
|
||||||
|
" all_tunes = range(num_tunes)\n",
|
||||||
|
"\n",
|
||||||
|
" # Playlist target duration in seconds.\n",
|
||||||
|
" target_duration = 60 * 60 # 1 hour\n",
|
||||||
|
"\n",
|
||||||
|
" # We use a circuit constraint to model the playlist. In the circuit constraint\n",
|
||||||
|
" # graph, each node is a tune, and each arc represents a pair of consecutive\n",
|
||||||
|
" # tunes in the playlist. We introduce a dummy node to represent the start and\n",
|
||||||
|
" # the end of the playlist.\n",
|
||||||
|
" #\n",
|
||||||
|
" # The constraint that two consecutive tunes must be of different genres is\n",
|
||||||
|
" # encoded by not creating an arc between two tunes that are of the same genre.\n",
|
||||||
|
" # This is crucial in the modelisation of this problem: it reduces the number\n",
|
||||||
|
" # of variables in the model, and it avoids additional constraints to ensure\n",
|
||||||
|
" # two consecutive tunes are of different genres.\n",
|
||||||
|
"\n",
|
||||||
|
" # Dummy node representing the start and end of the playlist.\n",
|
||||||
|
" dummy_node = num_tunes\n",
|
||||||
|
"\n",
|
||||||
|
" # `possible_successors[i]` contains the list of nodes that can be reached\n",
|
||||||
|
" # after node `i`.\n",
|
||||||
|
" possible_successors = {}\n",
|
||||||
|
" possible_successors[dummy_node] = [dummy_node]\n",
|
||||||
|
" for i in all_tunes:\n",
|
||||||
|
" # Any node can be the first tune in the playlist.\n",
|
||||||
|
" possible_successors[dummy_node].append(i)\n",
|
||||||
|
" # Any node can be the last tune in the playlist.\n",
|
||||||
|
" possible_successors[i] = [dummy_node]\n",
|
||||||
|
" genre_i = tunes[i][2]\n",
|
||||||
|
" for j in all_tunes:\n",
|
||||||
|
" genre_j = tunes[j][2]\n",
|
||||||
|
" # If `i` and `j` are of different genres, we can go from `i` to `j`.\n",
|
||||||
|
" if genre_i != genre_j:\n",
|
||||||
|
" possible_successors[i].append(j)\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 2. Model\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 3. Decision Variables\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # `literals[i][j]` is true if tune `j` follows tune `i` in the playlist.\n",
|
||||||
|
" literals = {}\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 4. Constraints\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
"\n",
|
||||||
|
" # 4.1 Two consecutive tunes must be of different genres.\n",
|
||||||
|
" # This is encoded in possible_successors, which doesn't contain any arcs\n",
|
||||||
|
" # between two tunes that are of the same genre. Now we just have to add a\n",
|
||||||
|
" # circuit constraint.\n",
|
||||||
|
"\n",
|
||||||
|
" # `arcs` contains the list of possible arcs in the circuit graph, each arc\n",
|
||||||
|
" # is a tuple (i, j, literals[i][j]).\n",
|
||||||
|
" arcs = []\n",
|
||||||
|
"\n",
|
||||||
|
" def AddArc(i, j):\n",
|
||||||
|
" literals[(i, j)] = model.new_bool_var(f\"lit_{i}_{j}\")\n",
|
||||||
|
" arcs.append((i, j, literals[(i, j)]))\n",
|
||||||
|
"\n",
|
||||||
|
" # Add all possible arcs between different nodes.\n",
|
||||||
|
" for i, successors in possible_successors.items():\n",
|
||||||
|
" for j in successors:\n",
|
||||||
|
" AddArc(i, j)\n",
|
||||||
|
"\n",
|
||||||
|
" # Add self-arcs to let tunes not be in the playlist.\n",
|
||||||
|
" for i in all_tunes:\n",
|
||||||
|
" AddArc(i, i)\n",
|
||||||
|
"\n",
|
||||||
|
" # Add a circuit constraint with the arcs.\n",
|
||||||
|
" model.add_circuit(arcs)\n",
|
||||||
|
"\n",
|
||||||
|
" # 4.2 All genres must appear at least once.\n",
|
||||||
|
" # This is encoded by adding a constraint that the sum of all literals for a\n",
|
||||||
|
" # given genre is at least 1.\n",
|
||||||
|
"\n",
|
||||||
|
" # `is_active[i]` is true iff tune `i` is in the playlist, i.e. if its self-arc\n",
|
||||||
|
" # is not active in the circuit.\n",
|
||||||
|
" is_active = {}\n",
|
||||||
|
" for i in all_tunes:\n",
|
||||||
|
" is_active[i] = literals[(i, i)].Not()\n",
|
||||||
|
"\n",
|
||||||
|
" # `genre_tunes[genre]` contains the list of tunes that are of genre `genre`.\n",
|
||||||
|
" genre_tunes = {}\n",
|
||||||
|
" for genre in genre_transition_costs:\n",
|
||||||
|
" genre_tunes[genre] = []\n",
|
||||||
|
" for i in all_tunes:\n",
|
||||||
|
" genre_tunes[tunes[i][2]].append(i)\n",
|
||||||
|
"\n",
|
||||||
|
" # For each genre, at least one tune must be active: the sum of all literals\n",
|
||||||
|
" # for this genre is at least 1.\n",
|
||||||
|
" for t in genre_tunes.values():\n",
|
||||||
|
" model.add(sum(is_active[i] for i in t) >= 1)\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 5. Objective\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
"\n",
|
||||||
|
" # 5.1. Minimize genre transition costs.\n",
|
||||||
|
"\n",
|
||||||
|
" # Add a total_transition_cost variable representing the sum of all transition\n",
|
||||||
|
" # costs in the playlist.\n",
|
||||||
|
" max_transition_cost = 0\n",
|
||||||
|
" for genre_costs in genre_transition_costs.values():\n",
|
||||||
|
" for cost in genre_costs.values():\n",
|
||||||
|
" max_transition_cost = max(cost, max_transition_cost)\n",
|
||||||
|
" total_transition_cost_upper_bound = (num_tunes - 1) * max_transition_cost\n",
|
||||||
|
" total_transition_cost = model.new_int_var(\n",
|
||||||
|
" 0, total_transition_cost_upper_bound, \"total_transition_cost\"\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" transition_cost_terms = []\n",
|
||||||
|
" for i, successors in possible_successors.items():\n",
|
||||||
|
" if i == dummy_node:\n",
|
||||||
|
" continue\n",
|
||||||
|
" genre_i = tunes[i][2]\n",
|
||||||
|
" for j in successors:\n",
|
||||||
|
" if j == dummy_node:\n",
|
||||||
|
" continue\n",
|
||||||
|
" genre_j = tunes[j][2]\n",
|
||||||
|
" cost = genre_transition_costs[genre_i][genre_j]\n",
|
||||||
|
" transition_cost_terms.append(cost * literals[(i, j)])\n",
|
||||||
|
" model.add(total_transition_cost == sum(transition_cost_terms))\n",
|
||||||
|
"\n",
|
||||||
|
" # 5.2. Minimize the deviation between the target duration and the actual total\n",
|
||||||
|
" # duration.\n",
|
||||||
|
"\n",
|
||||||
|
" # Add a total_duration variable representing the duration of all active tunes.\n",
|
||||||
|
" total_duration_upper_bound = sum([t[1] for t in tunes])\n",
|
||||||
|
" total_duration = model.new_int_var(0, total_duration_upper_bound, \"total_duration\")\n",
|
||||||
|
" model.add(total_duration == sum(tunes[i][1] * is_active[i] for i in all_tunes))\n",
|
||||||
|
"\n",
|
||||||
|
" # Minimize the absolute difference from the target duration.\n",
|
||||||
|
" deviation = model.new_int_var(0, target_duration, \"deviation\")\n",
|
||||||
|
" model.add_abs_equality(deviation, total_duration - target_duration)\n",
|
||||||
|
"\n",
|
||||||
|
" # 5.3 Combine the objectives.\n",
|
||||||
|
" #\n",
|
||||||
|
" # You can add a weight to prioritize one over the other.\n",
|
||||||
|
" # For example, `model.minimize(10 * total_transition_cost + deviation)`\n",
|
||||||
|
" model.minimize(total_transition_cost + deviation)\n",
|
||||||
|
"\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" # 6. Solve\n",
|
||||||
|
" # --------------------\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" # Set a time limit for the solver\n",
|
||||||
|
" solver.parameters.max_time_in_seconds = 30.0\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # -----------------------\n",
|
||||||
|
" # 7. Print the solution\n",
|
||||||
|
" # -----------------------\n",
|
||||||
|
" if status == cp_model.OPTIMAL:\n",
|
||||||
|
" print(\"Found Optimal Playlist:\")\n",
|
||||||
|
" elif status == cp_model.FEASIBLE:\n",
|
||||||
|
" print(\"Found Feasible Playlist:\")\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(\"No solution found.\")\n",
|
||||||
|
" return\n",
|
||||||
|
"\n",
|
||||||
|
" print(f\" Total Transition Cost: {solver.value(total_transition_cost)}\")\n",
|
||||||
|
" print(\n",
|
||||||
|
" f\" Playlist Duration: {solver.value(total_duration)} seconds \"\n",
|
||||||
|
" f\"({solver.value(total_duration) / 60:.2f} minutes)\"\n",
|
||||||
|
" )\n",
|
||||||
|
" print(\n",
|
||||||
|
" f\" Deviation from target duration ({target_duration}):\"\n",
|
||||||
|
" f\" {solver.value(deviation)} seconds\"\n",
|
||||||
|
" )\n",
|
||||||
|
" print(\"-\" * 30)\n",
|
||||||
|
"\n",
|
||||||
|
" # Reconstruct the playlist sequence by starting from the dummy node.\n",
|
||||||
|
" playlist = []\n",
|
||||||
|
" current_node = dummy_node\n",
|
||||||
|
" while True:\n",
|
||||||
|
" # Find the successor of the current node.\n",
|
||||||
|
" next_node = dummy_node\n",
|
||||||
|
" for next_node in possible_successors[current_node]:\n",
|
||||||
|
" if solver.value(literals[(current_node, next_node)]):\n",
|
||||||
|
" break\n",
|
||||||
|
"\n",
|
||||||
|
" if next_node == dummy_node:\n",
|
||||||
|
" break # We've completed the loop back to the start.\n",
|
||||||
|
"\n",
|
||||||
|
" playlist.append(next_node)\n",
|
||||||
|
" current_node = next_node\n",
|
||||||
|
"\n",
|
||||||
|
" if not playlist:\n",
|
||||||
|
" print(\"Empty playlist.\")\n",
|
||||||
|
" else:\n",
|
||||||
|
" for i in playlist:\n",
|
||||||
|
" (name, duration, genre) = tunes[i]\n",
|
||||||
|
" print(f\"{i+1}. {name} ({genre}) - {duration}s\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def main(argv: Sequence[str]) -> None:\n",
|
||||||
|
" if len(argv) > 1:\n",
|
||||||
|
" raise app.UsageError(\"Too many command-line arguments.\")\n",
|
||||||
|
" Solve()\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"main()\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -93,7 +93,6 @@
|
|||||||
"from typing import List, Sequence, Tuple\n",
|
"from typing import List, Sequence, Tuple\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_PARAMS = flags.define_string(\n",
|
"_PARAMS = flags.define_string(\n",
|
||||||
@@ -354,7 +353,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -98,7 +98,6 @@
|
|||||||
"from typing import Dict, List\n",
|
"from typing import Dict, List\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -211,7 +210,7 @@
|
|||||||
" # Solve the model.\n",
|
" # Solve the model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" print(\n",
|
" print(\n",
|
||||||
|
|||||||
@@ -151,6 +151,8 @@
|
|||||||
" total_distance = 0\n",
|
" total_distance = 0\n",
|
||||||
" total_value_collected = 0\n",
|
" total_value_collected = 0\n",
|
||||||
" for v in range(manager.GetNumberOfVehicles()):\n",
|
" for v in range(manager.GetNumberOfVehicles()):\n",
|
||||||
|
" if not routing.IsVehicleUsed(assignment, v):\n",
|
||||||
|
" continue\n",
|
||||||
" index = routing.Start(v)\n",
|
" index = routing.Start(v)\n",
|
||||||
" plan_output = f'Route for vehicle {v}:\\n'\n",
|
" plan_output = f'Route for vehicle {v}:\\n'\n",
|
||||||
" route_distance = 0\n",
|
" route_distance = 0\n",
|
||||||
|
|||||||
@@ -120,7 +120,12 @@
|
|||||||
" print(\"MinCostFlow on 4x4 matrix.\")\n",
|
" print(\"MinCostFlow on 4x4 matrix.\")\n",
|
||||||
" num_sources = 4\n",
|
" num_sources = 4\n",
|
||||||
" num_targets = 4\n",
|
" num_targets = 4\n",
|
||||||
" costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]\n",
|
" costs = [\n",
|
||||||
|
" [90, 75, 75, 80],\n",
|
||||||
|
" [35, 85, 55, 65],\n",
|
||||||
|
" [125, 95, 90, 105],\n",
|
||||||
|
" [45, 110, 95, 115],\n",
|
||||||
|
" ]\n",
|
||||||
" expected_cost = 275\n",
|
" expected_cost = 275\n",
|
||||||
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
|
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
|
||||||
" for source in range(0, num_sources):\n",
|
" for source in range(0, num_sources):\n",
|
||||||
|
|||||||
@@ -93,7 +93,6 @@
|
|||||||
"import collections\n",
|
"import collections\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"from ortools.scheduling import rcpsp_pb2\n",
|
"from ortools.scheduling import rcpsp_pb2\n",
|
||||||
"from ortools.scheduling.python import rcpsp\n",
|
"from ortools.scheduling.python import rcpsp\n",
|
||||||
@@ -428,7 +427,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" # Parse user specified parameters.\n",
|
" # Parse user specified parameters.\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Favor objective_shaving over objective_lb_search.\n",
|
" # Favor objective_shaving over objective_lb_search.\n",
|
||||||
" if solver.parameters.num_workers >= 16 and solver.parameters.num_workers < 24:\n",
|
" if solver.parameters.num_workers >= 16 and solver.parameters.num_workers < 24:\n",
|
||||||
|
|||||||
@@ -84,7 +84,6 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_OUTPUT_PROTO = flags.define_string(\n",
|
"_OUTPUT_PROTO = flags.define_string(\n",
|
||||||
@@ -477,7 +476,7 @@
|
|||||||
" # Solve the model.\n",
|
" # Solve the model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
||||||
" status = solver.solve(model, solution_printer)\n",
|
" status = solver.solve(model, solution_printer)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -85,7 +85,6 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"from typing import Sequence\n",
|
"from typing import Sequence\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# ----------------------------------------------------------------------------\n",
|
"# ----------------------------------------------------------------------------\n",
|
||||||
@@ -566,7 +565,7 @@
|
|||||||
" # Solve.\n",
|
" # Solve.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if parameters:\n",
|
" if parameters:\n",
|
||||||
" text_format.Parse(parameters, solver.parameters)\n",
|
" solver.parameters.parse_text_format(parameters)\n",
|
||||||
" solution_printer = SolutionPrinter()\n",
|
" solution_printer = SolutionPrinter()\n",
|
||||||
" solver.best_bound_callback = lambda a: print(f\"New objective lower bound: {a}\")\n",
|
" solver.best_bound_callback = lambda a: print(f\"New objective lower bound: {a}\")\n",
|
||||||
" solver.solve(model, solution_printer)\n",
|
" solver.solve(model, solution_printer)\n",
|
||||||
|
|||||||
439
examples/notebook/examples/spillover_sat.ipynb
Normal file
439
examples/notebook/examples/spillover_sat.ipynb
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"# spillover_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/spillover_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/spillover_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 problem of buying physical machines to meet VM demand.\n",
|
||||||
|
"\n",
|
||||||
|
"The Spillover problem is defined as follows:\n",
|
||||||
|
"\n",
|
||||||
|
"You have M types of physical machines and V types of Virtual Machines (VMs). You\n",
|
||||||
|
"can use a physical machine of type m to get n_mv copies of VM v. Each physical\n",
|
||||||
|
"machine m has a cost of c_m. Each VM has a demand of d_v. VMs are assigned to\n",
|
||||||
|
"physical machines by the following rule. The demand for each VM type arrives\n",
|
||||||
|
"equally spaced out over the interval [0, 1]. For each VM type, there is a\n",
|
||||||
|
"priority order over the physical machine types that you must follow. When a\n",
|
||||||
|
"demand arrives, if there are any machines of the highest priority type\n",
|
||||||
|
"available, you use them first, then you move on to the second priority machine\n",
|
||||||
|
"type, and so on. Each VM type has a list of compatible physical machine types,\n",
|
||||||
|
"and when the list is exhausted, the remaining demand is not met. Your goal is\n",
|
||||||
|
"to pick quantities of the physical machines to buy (minimizing cost) so that at\n",
|
||||||
|
"least some target service level (e.g. 95%) of the total demand of all VM is met.\n",
|
||||||
|
"\n",
|
||||||
|
"The number of machines bought of each type and the number of VMs demanded of\n",
|
||||||
|
"each type is large enough that you can solve an approximate problem instead,\n",
|
||||||
|
"where the number of machines purchased and the assignment of machines to VMs is\n",
|
||||||
|
"fractional, if it is helpful to do so.\n",
|
||||||
|
"\n",
|
||||||
|
"The problem is not particularly interesting in isolation, it is more interesting\n",
|
||||||
|
"to embed this LP inside a larger optimization problem (e.g. consider a two stage\n",
|
||||||
|
"problem where in stage one, you buy machines, then in stage two, you realize VM\n",
|
||||||
|
"demand).\n",
|
||||||
|
"\n",
|
||||||
|
"The continuous approximation of this problem can be solved by LP (see the\n",
|
||||||
|
"MathOpt python examples). Doing this, instead of using MIP, is nontrivial.\n",
|
||||||
|
"Below, we show that continuous relaxation can be approximately solved by CP-SAT\n",
|
||||||
|
"as well, despite not having continuous variables. If you were solving the\n",
|
||||||
|
"problem in isolation, you should just use an LP solver, but if you were to add\n",
|
||||||
|
"side constraints or embed this within a more complex model, using CP-SAT could\n",
|
||||||
|
"be appropriate.\n",
|
||||||
|
"\n",
|
||||||
|
"If for each VM type, the physical machines that are most cost effective are the\n",
|
||||||
|
"highest priority, AND the target service level is 100%, then the problem has a\n",
|
||||||
|
"trivial optimal solution:\n",
|
||||||
|
" 1. Rank the VMs by lowest cost to meet a unit of demand with the #1 preferred\n",
|
||||||
|
" machine type.\n",
|
||||||
|
" 2. For each VM type in the order above, buy machines from #1 preferred machine\n",
|
||||||
|
" type, until either you have met all demand for the VM type.\n",
|
||||||
|
"\n",
|
||||||
|
"MOE:begin_strip\n",
|
||||||
|
"This example is motivated by the Cloudy problem, see go/fluid-model.\n",
|
||||||
|
"MOE:end_strip\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from collections.abc import Sequence\n",
|
||||||
|
"import dataclasses\n",
|
||||||
|
"import math\n",
|
||||||
|
"import random\n",
|
||||||
|
"\n",
|
||||||
|
"from ortools.sat.colab import flags\n",
|
||||||
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"\n",
|
||||||
|
"_MACHINE_TYPES = flags.define_integer(\n",
|
||||||
|
" \"machine_types\",\n",
|
||||||
|
" 100,\n",
|
||||||
|
" \"How many types of machines we can fulfill demand with.\",\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"_VM_TYPES = flags.define_integer(\n",
|
||||||
|
" \"vm_types\", 500, \"How many types of VMs we need to supply.\"\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"_FUNGIBILITY = flags.define_integer(\n",
|
||||||
|
" \"fungibility\",\n",
|
||||||
|
" 10,\n",
|
||||||
|
" \"Each VM type can be satisfied with this many machine types, selected\"\n",
|
||||||
|
" \" uniformly at random.\",\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"_MAX_DEMAND = flags.define_integer(\n",
|
||||||
|
" \"max_demand\",\n",
|
||||||
|
" 100,\n",
|
||||||
|
" \"Demand for each VM type is in [max_demand//2, max_demand], uniformly at\"\n",
|
||||||
|
" \" random.\",\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"_TEST_DATA = flags.define_bool(\n",
|
||||||
|
" \"test_data\", False, \"Use small test instance instead of random data.\"\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"_SEED = flags.define_integer(\"seed\", 13, \"RNG seed for instance creation.\")\n",
|
||||||
|
"\n",
|
||||||
|
"_TIME_STEPS = flags.define_integer(\"time_steps\", 100, \"How much to discretize time.\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class MachineUse:\n",
|
||||||
|
" machine_type: int\n",
|
||||||
|
" vms_per_machine: int\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class VmDemand:\n",
|
||||||
|
" compatible_machines: tuple[MachineUse, ...]\n",
|
||||||
|
" vm_quantity: int\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"@dataclasses.dataclass(frozen=True)\n",
|
||||||
|
"class SpilloverProblem:\n",
|
||||||
|
" machine_cost: tuple[float, ...]\n",
|
||||||
|
" machine_limit: tuple[int, ...]\n",
|
||||||
|
" vm_demands: tuple[VmDemand, ...]\n",
|
||||||
|
" service_level: float\n",
|
||||||
|
" time_horizon: int\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def _random_spillover_problem(\n",
|
||||||
|
" num_machines: int,\n",
|
||||||
|
" num_vms: int,\n",
|
||||||
|
" fungibility: int,\n",
|
||||||
|
" max_vm_demand: int,\n",
|
||||||
|
" horizon: int,\n",
|
||||||
|
") -> SpilloverProblem:\n",
|
||||||
|
" \"\"\"Generates a random SpilloverProblem.\"\"\"\n",
|
||||||
|
" machine_costs = tuple(random.random() for _ in range(num_machines))\n",
|
||||||
|
" vm_demands = []\n",
|
||||||
|
" all_machines = list(range(num_machines))\n",
|
||||||
|
" min_vm_demand = max_vm_demand // 2\n",
|
||||||
|
" for _ in range(num_vms):\n",
|
||||||
|
" vm_use = []\n",
|
||||||
|
" for machine in random.sample(all_machines, fungibility):\n",
|
||||||
|
" vm_use.append(\n",
|
||||||
|
" MachineUse(machine_type=machine, vms_per_machine=random.randint(1, 10))\n",
|
||||||
|
" )\n",
|
||||||
|
" vm_demands.append(\n",
|
||||||
|
" VmDemand(\n",
|
||||||
|
" compatible_machines=tuple(vm_use),\n",
|
||||||
|
" vm_quantity=random.randint(min_vm_demand, max_vm_demand),\n",
|
||||||
|
" )\n",
|
||||||
|
" )\n",
|
||||||
|
" machine_need_ub = num_vms * max_vm_demand\n",
|
||||||
|
" machine_limit = (machine_need_ub,) * num_machines\n",
|
||||||
|
" return SpilloverProblem(\n",
|
||||||
|
" machine_cost=machine_costs,\n",
|
||||||
|
" machine_limit=machine_limit,\n",
|
||||||
|
" vm_demands=tuple(vm_demands),\n",
|
||||||
|
" service_level=0.95,\n",
|
||||||
|
" time_horizon=horizon,\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def _test_problem() -> SpilloverProblem:\n",
|
||||||
|
" \"\"\"Creates a small SpilloverProblem with optimal objective of 360.\"\"\"\n",
|
||||||
|
" # To avoid machine type 2, ensure we buy enough of 1 to not stock out, cost\n",
|
||||||
|
" # 20\n",
|
||||||
|
" vm_a = VmDemand(\n",
|
||||||
|
" vm_quantity=10,\n",
|
||||||
|
" compatible_machines=(\n",
|
||||||
|
" MachineUse(machine_type=1, vms_per_machine=1),\n",
|
||||||
|
" MachineUse(machine_type=2, vms_per_machine=1),\n",
|
||||||
|
" ),\n",
|
||||||
|
" )\n",
|
||||||
|
" # machine type 0 is cheaper, but we don't want to stock out of machine type 1,\n",
|
||||||
|
" # so use all machine type 1, cost 40.\n",
|
||||||
|
" vm_b = VmDemand(\n",
|
||||||
|
" vm_quantity=20,\n",
|
||||||
|
" compatible_machines=(\n",
|
||||||
|
" MachineUse(machine_type=1, vms_per_machine=1),\n",
|
||||||
|
" MachineUse(machine_type=0, vms_per_machine=1),\n",
|
||||||
|
" ),\n",
|
||||||
|
" )\n",
|
||||||
|
" # Will use 3 copies of machine type 2, cost 300\n",
|
||||||
|
" vm_c = VmDemand(\n",
|
||||||
|
" vm_quantity=30,\n",
|
||||||
|
" compatible_machines=(MachineUse(machine_type=2, vms_per_machine=10),),\n",
|
||||||
|
" )\n",
|
||||||
|
" return SpilloverProblem(\n",
|
||||||
|
" machine_cost=(1.0, 2.0, 100.0),\n",
|
||||||
|
" machine_limit=(60, 60, 60),\n",
|
||||||
|
" vm_demands=(vm_a, vm_b, vm_c),\n",
|
||||||
|
" service_level=1.0,\n",
|
||||||
|
" time_horizon=100,\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"# Indices:\n",
|
||||||
|
"# * i in I, the VM demands\n",
|
||||||
|
"# * j in J, the machines supplied\n",
|
||||||
|
"#\n",
|
||||||
|
"# Data:\n",
|
||||||
|
"# * c_j: cost of a machine of type j\n",
|
||||||
|
"# * l_j: a limit of how many machines of type j you can buy.\n",
|
||||||
|
"# * n_ij: how many VMs of type i you get from a machine of type j\n",
|
||||||
|
"# * d_i: the total demand for VMs of type i\n",
|
||||||
|
"# * service_level: the target fraction of demand that is met.\n",
|
||||||
|
"# * P_i subset J: the compatible machine types for VM demand i.\n",
|
||||||
|
"# * UP_i(j) subset P_i, for j in P_i: for VM demand type i, the machines of\n",
|
||||||
|
"# priority higher than j\n",
|
||||||
|
"# * T: the number of integer time steps.\n",
|
||||||
|
"#\n",
|
||||||
|
"# Note: when d_i/n_ij is not integer, some approximation error is introduced in\n",
|
||||||
|
"# constraint 6 below.\n",
|
||||||
|
"#\n",
|
||||||
|
"# Decision variables:\n",
|
||||||
|
"# * s_j: the supply of machine type j\n",
|
||||||
|
"# * w_j: the time we run out of machine j, or 1 if we never run out\n",
|
||||||
|
"# * v_ij: when we start using supply j to meet demand i, or w_j if we never use\n",
|
||||||
|
"# this machine type for this demand.\n",
|
||||||
|
"# * o_i: the time we start failing to meet vm demand i\n",
|
||||||
|
"# * m_i: the total demand met for vm type i.\n",
|
||||||
|
"#\n",
|
||||||
|
"# Model the problem:\n",
|
||||||
|
"# min sum_{j in J} c_j s_j\n",
|
||||||
|
"# s.t.\n",
|
||||||
|
"# 1: sum_i m_i >= service_level * sum_{i in I} d_i\n",
|
||||||
|
"# 2: T * m_i <= o_i * d_i for all i in I\n",
|
||||||
|
"# 3: v_ij >= w_r for all i in I, j in C_i, r in UP_i(j)\n",
|
||||||
|
"# 4: v_ij <= w_j for all i in I, j in C_i\n",
|
||||||
|
"# 5: o_i = sum_{j in P_i} (w_j - v_ij) for all i in I\n",
|
||||||
|
"# 6: sum_{i in I: j in P_i}ceil(d_i/n_ij)(w_j - v_ij)<=T*s_j for all j in J\n",
|
||||||
|
"# o_i, w_j, v_ij in [0, T]\n",
|
||||||
|
"# 0 <= m_i <= d_i\n",
|
||||||
|
"# 0 <= s_j <= l_j\n",
|
||||||
|
"#\n",
|
||||||
|
"# The constraints say:\n",
|
||||||
|
"# 1. The amount of demand served must be at least 95% of total demand.\n",
|
||||||
|
"# 2. The demand served for VM type i is linear in the time we fail to keep\n",
|
||||||
|
"# serving demand.\n",
|
||||||
|
"# 3. Don't start using machine type j for demand i until all higher priority\n",
|
||||||
|
"# machine types r are used up.\n",
|
||||||
|
"# 4. The time we run out of machine type j must be after we start using it for\n",
|
||||||
|
"# VM demand type i.\n",
|
||||||
|
"# 5. The time we are unable to serve further VM demand i is the sum of the\n",
|
||||||
|
"# time spent serving the demand with each eligible machine type.\n",
|
||||||
|
"# 6. The total use of machine type j to serve demand does not exceed the\n",
|
||||||
|
"# supply. The ceil function above introduces some approximation error when\n",
|
||||||
|
"# d_i/n_ij is not integer.\n",
|
||||||
|
"def _solve_spillover_problem(problem: SpilloverProblem) -> None:\n",
|
||||||
|
" \"\"\"Solves the spillover problem and prints the optimal objective.\"\"\"\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
" num_machines = len(problem.machine_cost)\n",
|
||||||
|
" num_vms = len(problem.vm_demands)\n",
|
||||||
|
" horizon = problem.time_horizon\n",
|
||||||
|
" s = [\n",
|
||||||
|
" model.new_int_var(lb=0, ub=problem.machine_limit[j], name=f\"s_{j}\")\n",
|
||||||
|
" for j in range(num_machines)\n",
|
||||||
|
" ]\n",
|
||||||
|
" w = [\n",
|
||||||
|
" model.new_int_var(lb=0, ub=horizon, name=f\"w_{i}\") for i in range(num_machines)\n",
|
||||||
|
" ]\n",
|
||||||
|
" o = [model.new_int_var(lb=0, ub=horizon, name=f\"o_{j}\") for j in range(num_vms)]\n",
|
||||||
|
" m = [\n",
|
||||||
|
" model.new_int_var(lb=0, ub=problem.vm_demands[j].vm_quantity, name=f\"m_{j}\")\n",
|
||||||
|
" for j in range(num_vms)\n",
|
||||||
|
" ]\n",
|
||||||
|
" v = [\n",
|
||||||
|
" {\n",
|
||||||
|
" compat.machine_type: model.new_int_var(\n",
|
||||||
|
" lb=0, ub=horizon, name=f\"v_{i}_{compat.machine_type}\"\n",
|
||||||
|
" )\n",
|
||||||
|
" for compat in vm_demand.compatible_machines\n",
|
||||||
|
" }\n",
|
||||||
|
" for i, vm_demand in enumerate(problem.vm_demands)\n",
|
||||||
|
" ]\n",
|
||||||
|
"\n",
|
||||||
|
" obj = 0\n",
|
||||||
|
" for j in range(num_machines):\n",
|
||||||
|
" obj += s[j] * problem.machine_cost[j]\n",
|
||||||
|
" model.minimize(obj)\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 1: demand served is at least service_level fraction of total.\n",
|
||||||
|
" total_vm_demand = sum(vm_demand.vm_quantity for vm_demand in problem.vm_demands)\n",
|
||||||
|
" model.add(sum(m) >= int(math.ceil(problem.service_level * total_vm_demand)))\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 2: demand served is linear in time we stop serving.\n",
|
||||||
|
" for i in range(num_vms):\n",
|
||||||
|
" model.add(\n",
|
||||||
|
" problem.time_horizon * m[i] <= o[i] * problem.vm_demands[i].vm_quantity\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 3: use machine type j for demand i after all higher priority\n",
|
||||||
|
" # machine types r are used up.\n",
|
||||||
|
" for i in range(num_vms):\n",
|
||||||
|
" for k, meet_demand in enumerate(problem.vm_demands[i].compatible_machines):\n",
|
||||||
|
" j = meet_demand.machine_type\n",
|
||||||
|
" for l in range(k):\n",
|
||||||
|
" r = problem.vm_demands[i].compatible_machines[l].machine_type\n",
|
||||||
|
" model.add(v[i][j] >= w[r])\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 4: outage time of machine j is after start time for using j to\n",
|
||||||
|
" # meet VM demand i.\n",
|
||||||
|
" for i in range(num_vms):\n",
|
||||||
|
" for meet_demand in problem.vm_demands[i].compatible_machines:\n",
|
||||||
|
" j = meet_demand.machine_type\n",
|
||||||
|
" model.add(v[i][j] <= w[j])\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 5: For VM demand i, time service ends is the sum of the time\n",
|
||||||
|
" # spent serving with each eligible machine type.\n",
|
||||||
|
" for i in range(num_vms):\n",
|
||||||
|
" sum_serving = 0\n",
|
||||||
|
" for meet_demand in problem.vm_demands[i].compatible_machines:\n",
|
||||||
|
" j = meet_demand.machine_type\n",
|
||||||
|
" sum_serving += w[j] - v[i][j]\n",
|
||||||
|
" model.add(o[i] == sum_serving)\n",
|
||||||
|
"\n",
|
||||||
|
" # Constraint 6: Total use of machine type j is at most the supply.\n",
|
||||||
|
" #\n",
|
||||||
|
" # We build the constraints in bulk because our data is transposed.\n",
|
||||||
|
" total_machine_use = [0 for _ in range(num_machines)]\n",
|
||||||
|
" for i in range(num_vms):\n",
|
||||||
|
" for meet_demand in problem.vm_demands[i].compatible_machines:\n",
|
||||||
|
" j = meet_demand.machine_type\n",
|
||||||
|
" nij = meet_demand.vms_per_machine\n",
|
||||||
|
" vm_quantity = problem.vm_demands[i].vm_quantity\n",
|
||||||
|
" # Want vm_quantity/nij, over estimate with ceil(vm_quantity/nij) to use\n",
|
||||||
|
" # integer coefficients.\n",
|
||||||
|
" rate = (vm_quantity + nij - 1) // nij\n",
|
||||||
|
" total_machine_use[j] += rate * (w[j] - v[i][j])\n",
|
||||||
|
" for j in range(num_machines):\n",
|
||||||
|
" model.add(total_machine_use[j] <= horizon * s[j])\n",
|
||||||
|
"\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" solver.parameters.num_workers = 16\n",
|
||||||
|
" solver.parameters.log_search_progress = True\n",
|
||||||
|
" solver.max_time_in_seconds = 30.0\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
" if status != cp_model.OPTIMAL:\n",
|
||||||
|
" raise RuntimeError(f\"expected optimal, found: {status}\")\n",
|
||||||
|
" print(f\"objective: {solver.objective_value}\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def main(argv: Sequence[str]) -> None:\n",
|
||||||
|
" del argv # Unused.\n",
|
||||||
|
" random.seed(_SEED.value)\n",
|
||||||
|
" if _TEST_DATA.value:\n",
|
||||||
|
" problem = _test_problem()\n",
|
||||||
|
" else:\n",
|
||||||
|
" problem = _random_spillover_problem(\n",
|
||||||
|
" _MACHINE_TYPES.value,\n",
|
||||||
|
" _VM_TYPES.value,\n",
|
||||||
|
" _FUNGIBILITY.value,\n",
|
||||||
|
" _MAX_DEMAND.value,\n",
|
||||||
|
" _TIME_STEPS.value,\n",
|
||||||
|
" )\n",
|
||||||
|
" print(problem)\n",
|
||||||
|
"\n",
|
||||||
|
" _solve_spillover_problem(problem)\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"main()\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
"import math\n",
|
"import math\n",
|
||||||
"from typing import Sequence\n",
|
"from typing import Sequence\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_NUM_ROBOTS = flags.define_integer(\"num_robots\", 8, \"Number of robots to place.\")\n",
|
"_NUM_ROBOTS = flags.define_integer(\"num_robots\", 8, \"Number of robots to place.\")\n",
|
||||||
@@ -161,7 +160,7 @@
|
|||||||
" # Creates a solver and solves the model.\n",
|
" # Creates a solver and solves the model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if params:\n",
|
" if params:\n",
|
||||||
" text_format.Parse(params, solver.parameters)\n",
|
" solver.parameters.parse_text_format(params)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -88,7 +88,6 @@
|
|||||||
"import time\n",
|
"import time\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -360,7 +359,7 @@
|
|||||||
" ### Solve model.\n",
|
" ### Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" objective_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
" objective_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
||||||
" status = solver.solve(model, objective_printer)\n",
|
" status = solver.solve(model, objective_printer)\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -544,7 +543,7 @@
|
|||||||
" ### Solve model.\n",
|
" ### Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\n",
|
" solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)\n",
|
||||||
" status = solver.solve(model, solution_printer)\n",
|
" status = solver.solve(model, solution_printer)\n",
|
||||||
@@ -614,7 +613,7 @@
|
|||||||
" ### Solve model.\n",
|
" ### Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
|
||||||
" status = solver.solve(model, solution_printer)\n",
|
" status = solver.solve(model, solution_printer)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -101,7 +101,6 @@
|
|||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"import pandas as pd\n",
|
"import pandas as pd\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -209,7 +208,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" status = solver.solve(model)\n",
|
" status = solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Report solution.\n",
|
" # Report solution.\n",
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from ortools.constraint_solver import pywrapcp\n",
|
"from ortools.constraint_solver import pywrapcp\n",
|
||||||
"from ortools.constraint_solver import routing_enums_pb2\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"###########################\n",
|
"###########################\n",
|
||||||
|
|||||||
@@ -85,8 +85,8 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"import random\n",
|
"import random\n",
|
||||||
"from typing import Sequence\n",
|
"from typing import Sequence\n",
|
||||||
|
"\n",
|
||||||
"from ortools.sat.colab import flags\n",
|
"from ortools.sat.colab import flags\n",
|
||||||
"from google.protobuf import text_format\n",
|
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
"_NUM_NODES = flags.define_integer(\"num_nodes\", 12, \"Number of nodes to visit.\")\n",
|
"_NUM_NODES = flags.define_integer(\"num_nodes\", 12, \"Number of nodes to visit.\")\n",
|
||||||
@@ -94,7 +94,9 @@
|
|||||||
"_PROFIT_RANGE = flags.define_integer(\"profit_range\", 50, \"Range of profit.\")\n",
|
"_PROFIT_RANGE = flags.define_integer(\"profit_range\", 50, \"Range of profit.\")\n",
|
||||||
"_SEED = flags.define_integer(\"seed\", 0, \"Random seed.\")\n",
|
"_SEED = flags.define_integer(\"seed\", 0, \"Random seed.\")\n",
|
||||||
"_PARAMS = flags.define_string(\n",
|
"_PARAMS = flags.define_string(\n",
|
||||||
" \"params\", \"num_search_workers:16, max_time_in_seconds:5\", \"Sat solver parameters.\"\n",
|
" \"params\",\n",
|
||||||
|
" \"num_search_workers:16, max_time_in_seconds:5\",\n",
|
||||||
|
" \"Sat solver parameters.\",\n",
|
||||||
")\n",
|
")\n",
|
||||||
"_PROTO_FILE = flags.define_string(\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",
|
||||||
@@ -163,7 +165,7 @@
|
|||||||
" # Solve model.\n",
|
" # Solve model.\n",
|
||||||
" solver = cp_model.CpSolver()\n",
|
" solver = cp_model.CpSolver()\n",
|
||||||
" if _PARAMS.value:\n",
|
" if _PARAMS.value:\n",
|
||||||
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
|
" solver.parameters.parse_text_format(_PARAMS.value)\n",
|
||||||
" solver.parameters.log_search_progress = True\n",
|
" solver.parameters.log_search_progress = True\n",
|
||||||
" solver.solve(model)\n",
|
" solver.solve(model)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.graph.python import linear_sum_assignment\n",
|
"from ortools.graph.python import linear_sum_assignment\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"Linear Sum Assignment example.\"\"\"\n",
|
" \"\"\"Linear Sum Assignment example.\"\"\"\n",
|
||||||
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
|
" assignment = linear_sum_assignment.SimpleLinearSumAssignment()\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.graph.python import min_cost_flow\n",
|
"from ortools.graph.python import min_cost_flow\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"Solving an Assignment Problem with MinCostFlow.\"\"\"\n",
|
" \"\"\"Solving an Assignment Problem with MinCostFlow.\"\"\"\n",
|
||||||
" # Instantiate a SimpleMinCostFlow solver.\n",
|
" # Instantiate a SimpleMinCostFlow solver.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.graph.python import min_cost_flow\n",
|
"from ortools.graph.python import min_cost_flow\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"Solving an Assignment with teams of worker.\"\"\"\n",
|
" \"\"\"Solving an Assignment with teams of worker.\"\"\"\n",
|
||||||
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
|
" smcf = min_cost_flow.SimpleMinCostFlow()\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.graph.python import max_flow\n",
|
"from ortools.graph.python import max_flow\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"MaxFlow simple interface example.\"\"\"\n",
|
" \"\"\"MaxFlow simple interface example.\"\"\"\n",
|
||||||
" # Instantiate a SimpleMaxFlow solver.\n",
|
" # Instantiate a SimpleMaxFlow solver.\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.graph.python import min_cost_flow\n",
|
"from ortools.graph.python import min_cost_flow\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"MinCostFlow simple interface example.\"\"\"\n",
|
" \"\"\"MinCostFlow simple interface example.\"\"\"\n",
|
||||||
" # Instantiate a SimpleMinCostFlow solver.\n",
|
" # Instantiate a SimpleMinCostFlow solver.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Data\n",
|
" # Data\n",
|
||||||
" costs = [\n",
|
" costs = [\n",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"from ortools.linear_solver.python import model_builder\n",
|
"from ortools.linear_solver.python import model_builder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Data\n",
|
" # Data\n",
|
||||||
" data_str = \"\"\"\n",
|
" data_str = \"\"\"\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Data\n",
|
" # Data\n",
|
||||||
" costs = [\n",
|
" costs = [\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Data\n",
|
" # Data\n",
|
||||||
" costs = [\n",
|
" costs = [\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Data\n",
|
" # Data\n",
|
||||||
" costs = [\n",
|
" costs = [\n",
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" print(\"Google OR-Tools version:\", init.OrToolsVersion.version_string())\n",
|
" print(\"Google OR-Tools version:\", init.OrToolsVersion.version_string())\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"from ortools.linear_solver.python import model_builder\n",
|
"from ortools.linear_solver.python import model_builder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:\n",
|
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:\n",
|
||||||
" \"\"\"Create the data for the example.\"\"\"\n",
|
" \"\"\"Create the data for the example.\"\"\"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def create_data_model():\n",
|
"def create_data_model():\n",
|
||||||
" \"\"\"Create the data for the example.\"\"\"\n",
|
" \"\"\"Create the data for the example.\"\"\"\n",
|
||||||
" data = {}\n",
|
" data = {}\n",
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" data = create_data_model()\n",
|
" data = create_data_model()\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.linear_solver.python import model_builder\n",
|
"from ortools.linear_solver.python import model_builder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the model.\n",
|
" # Create the model.\n",
|
||||||
" model = model_builder.Model()\n",
|
" model = model_builder.Model()\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def IntegerProgrammingExample():\n",
|
"def IntegerProgrammingExample():\n",
|
||||||
" \"\"\"Integer programming sample.\"\"\"\n",
|
" \"\"\"Integer programming sample.\"\"\"\n",
|
||||||
" # Create the mip solver with the SCIP backend.\n",
|
" # Create the mip solver with the SCIP backend.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def LinearProgrammingExample():\n",
|
"def LinearProgrammingExample():\n",
|
||||||
" \"\"\"Linear programming sample.\"\"\"\n",
|
" \"\"\"Linear programming sample.\"\"\"\n",
|
||||||
" # Instantiate a Glop solver, naming it LinearExample.\n",
|
" # Instantiate a Glop solver, naming it LinearExample.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def create_data_model():\n",
|
"def create_data_model():\n",
|
||||||
" \"\"\"Stores the data for the problem.\"\"\"\n",
|
" \"\"\"Stores the data for the problem.\"\"\"\n",
|
||||||
" data = {}\n",
|
" data = {}\n",
|
||||||
@@ -103,6 +104,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" data = create_data_model()\n",
|
" data = create_data_model()\n",
|
||||||
" # Create the mip solver with the SCIP backend.\n",
|
" # Create the mip solver with the SCIP backend.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" data = {}\n",
|
" data = {}\n",
|
||||||
" data[\"weights\"] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n",
|
" data[\"weights\"] = [48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36]\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the linear solver with the GLOP backend.\n",
|
" # Create the linear solver with the GLOP backend.\n",
|
||||||
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
|
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.linear_solver.python import model_builder\n",
|
"from ortools.linear_solver.python import model_builder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the model.\n",
|
" # Create the model.\n",
|
||||||
" model = model_builder.Model()\n",
|
" model = model_builder.Model()\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the mip solver with the CP-SAT backend.\n",
|
" # Create the mip solver with the CP-SAT backend.\n",
|
||||||
" solver = pywraplp.Solver.CreateSolver(\"SAT\")\n",
|
" solver = pywraplp.Solver.CreateSolver(\"SAT\")\n",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"from ortools.linear_solver.python import model_builder\n",
|
"from ortools.linear_solver.python import model_builder\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" # Create the model.\n",
|
" # Create the model.\n",
|
||||||
" model = model_builder.Model()\n",
|
" model = model_builder.Model()\n",
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
"from ortools.linear_solver import pywraplp\n",
|
"from ortools.linear_solver import pywraplp\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" \"\"\"Entry point of the program.\"\"\"\n",
|
" \"\"\"Entry point of the program.\"\"\"\n",
|
||||||
" # Instantiate the data problem.\n",
|
" # Instantiate the data problem.\n",
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"\n",
|
"\n",
|
||||||
"Code sample to demonstrates how to rank intervals using a circuit.\n"
|
"Code sample to demonstrates how to rank intervals using a circuit.\n",
|
||||||
|
"\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -83,8 +84,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from typing import List, Sequence\n",
|
"from collections.abc import Sequence\n",
|
||||||
"\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
" num_tasks = len(starts)\n",
|
" num_tasks = len(starts)\n",
|
||||||
" all_tasks = range(num_tasks)\n",
|
" all_tasks = range(num_tasks)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" arcs: List[cp_model.ArcT] = []\n",
|
" arcs: list[cp_model.ArcT] = []\n",
|
||||||
" for i in all_tasks:\n",
|
" for i in all_tasks:\n",
|
||||||
" # if node i is first.\n",
|
" # if node i is first.\n",
|
||||||
" start_lit = model.new_bool_var(f\"start_{i}\")\n",
|
" start_lit = model.new_bool_var(f\"start_{i}\")\n",
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from typing import Dict, List, Sequence, Tuple\n",
|
"from collections.abc import Sequence\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -95,9 +95,9 @@
|
|||||||
" task_types: Sequence[str],\n",
|
" task_types: Sequence[str],\n",
|
||||||
" lengths: Sequence[cp_model.IntVar],\n",
|
" lengths: Sequence[cp_model.IntVar],\n",
|
||||||
" cumuls: Sequence[cp_model.IntVar],\n",
|
" cumuls: Sequence[cp_model.IntVar],\n",
|
||||||
" sequence_length_constraints: Dict[str, Tuple[int, int]],\n",
|
" sequence_length_constraints: dict[str, tuple[int, int]],\n",
|
||||||
" sequence_cumul_constraints: Dict[str, Tuple[int, int, int]],\n",
|
" sequence_cumul_constraints: dict[str, tuple[int, int, int]],\n",
|
||||||
") -> Sequence[Tuple[cp_model.IntVar, int]]:\n",
|
") -> Sequence[tuple[cp_model.IntVar, int]]:\n",
|
||||||
" \"\"\"This method enforces constraints on sequences of tasks of the same type.\n",
|
" \"\"\"This method enforces constraints on sequences of tasks of the same type.\n",
|
||||||
"\n",
|
"\n",
|
||||||
" This method assumes that all durations are strictly positive.\n",
|
" This method assumes that all durations are strictly positive.\n",
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
" num_tasks = len(starts)\n",
|
" num_tasks = len(starts)\n",
|
||||||
" all_tasks = range(num_tasks)\n",
|
" all_tasks = range(num_tasks)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" arcs: List[cp_model.ArcT] = []\n",
|
" arcs: list[cp_model.ArcT] = []\n",
|
||||||
" for i in all_tasks:\n",
|
" for i in all_tasks:\n",
|
||||||
" # if node i is first.\n",
|
" # if node i is first.\n",
|
||||||
" start_lit = model.new_bool_var(f\"start_{i}\")\n",
|
" start_lit = model.new_bool_var(f\"start_{i}\")\n",
|
||||||
|
|||||||
249
examples/notebook/sat/soft_constraints_sat.ipynb
Normal file
249
examples/notebook/sat/soft_constraints_sat.ipynb
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
{
|
||||||
|
"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": [
|
||||||
|
"# soft_constraints_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/soft_constraints_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/soft_constraints_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",
|
||||||
|
"The sample shows multiple ways to model soft constraints in CP-SAT.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "code",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from ortools.sat.python import cp_model\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def infeasible_model() -> None:\n",
|
||||||
|
" \"\"\"Base model that is infeasible.\"\"\"\n",
|
||||||
|
" # Creates the model.\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the variables.\n",
|
||||||
|
" x = model.new_int_var(0, 10, \"x\")\n",
|
||||||
|
" y = model.new_int_var(0, 10, \"y\")\n",
|
||||||
|
" z = model.new_int_var(0, 10, \"z\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the constraints.\n",
|
||||||
|
" model.add(x > y)\n",
|
||||||
|
" model.add(y > z)\n",
|
||||||
|
" model.add(z > x)\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates a solver and solves.\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # Print solution.\n",
|
||||||
|
" print(f\" Status = {solver.status_name(status)}\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def model_with_enforcement_literals() -> None:\n",
|
||||||
|
" \"\"\"Adds fixed costs to violated constraints.\"\"\"\n",
|
||||||
|
" # Creates the model.\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the variables.\n",
|
||||||
|
" x = model.new_int_var(0, 10, \"x\")\n",
|
||||||
|
" y = model.new_int_var(0, 10, \"y\")\n",
|
||||||
|
" z = model.new_int_var(0, 10, \"z\")\n",
|
||||||
|
" a = model.new_bool_var(\"a\")\n",
|
||||||
|
" b = model.new_bool_var(\"b\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the constraints. Adds enforcement literals to the first two\n",
|
||||||
|
" # constraints, we assume the third constraint is always enforced.\n",
|
||||||
|
" model.add(x > y).only_enforce_if(a)\n",
|
||||||
|
" model.add(y > z).only_enforce_if(b)\n",
|
||||||
|
" model.add(z > x)\n",
|
||||||
|
"\n",
|
||||||
|
" # Adds an objective to maximize the number of enforced constraints.\n",
|
||||||
|
" model.maximize(a + 2 * b)\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates a solver and solves.\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # Print solution.\n",
|
||||||
|
" print(f\" Status = {solver.status_name(status)}\")\n",
|
||||||
|
" if status == cp_model.OPTIMAL:\n",
|
||||||
|
" print(f\" Objective value = {solver.objective_value}\")\n",
|
||||||
|
" print(f\" Value of x = {solver.value(x)}\")\n",
|
||||||
|
" print(f\" Value of y = {solver.value(y)}\")\n",
|
||||||
|
" print(f\" Value of z = {solver.value(z)}\")\n",
|
||||||
|
" print(f\" Value of a = {solver.boolean_value(a)}\")\n",
|
||||||
|
" print(f\" Value of b = {solver.boolean_value(b)}\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def model_with_linear_violations() -> None:\n",
|
||||||
|
" \"\"\"Adds fixed costs to violated constraints.\"\"\"\n",
|
||||||
|
" # Creates the model.\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the variables.\n",
|
||||||
|
" x = model.new_int_var(0, 10, \"x\")\n",
|
||||||
|
" y = model.new_int_var(0, 10, \"y\")\n",
|
||||||
|
" z = model.new_int_var(0, 10, \"z\")\n",
|
||||||
|
" a = model.new_int_var(0, 10, \"a\")\n",
|
||||||
|
" b = model.new_int_var(0, 10, \"b\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the constraints. Adds enforcement literals to the first two\n",
|
||||||
|
" # constraints, we assume the third constraint is always enforced.\n",
|
||||||
|
" model.add(x > y - a)\n",
|
||||||
|
" model.add(y > z - b)\n",
|
||||||
|
" model.add(z > x)\n",
|
||||||
|
"\n",
|
||||||
|
" # Adds an objective to minimize the added slacks.\n",
|
||||||
|
" model.minimize(a + 2 * b)\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates a solver and solves.\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # Print solution.\n",
|
||||||
|
" print(f\" Status = {solver.status_name(status)}\")\n",
|
||||||
|
" if status == cp_model.OPTIMAL:\n",
|
||||||
|
" print(f\" Objective value = {solver.objective_value}\")\n",
|
||||||
|
" print(f\" Value of x = {solver.value(x)}\")\n",
|
||||||
|
" print(f\" Value of y = {solver.value(y)}\")\n",
|
||||||
|
" print(f\" Value of z = {solver.value(z)}\")\n",
|
||||||
|
" print(f\" Value of a = {solver.value(a)}\")\n",
|
||||||
|
" print(f\" Value of b = {solver.value(b)}\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def model_with_quadratic_violations() -> None:\n",
|
||||||
|
" \"\"\"Adds fixed costs to violated constraints.\"\"\"\n",
|
||||||
|
" # Creates the model.\n",
|
||||||
|
" model = cp_model.CpModel()\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the variables.\n",
|
||||||
|
" x = model.new_int_var(0, 10, \"x\")\n",
|
||||||
|
" y = model.new_int_var(0, 10, \"y\")\n",
|
||||||
|
" z = model.new_int_var(0, 10, \"z\")\n",
|
||||||
|
" a = model.new_int_var(0, 10, \"a\")\n",
|
||||||
|
" b = model.new_int_var(0, 10, \"b\")\n",
|
||||||
|
" square_a = model.new_int_var(0, 100, \"square_a\")\n",
|
||||||
|
" square_b = model.new_int_var(0, 100, \"square_b\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates the constraints. Adds enforcement literals to the first two\n",
|
||||||
|
" # constraints, we assume the third constraint is always enforced.\n",
|
||||||
|
" model.add(x > y - a)\n",
|
||||||
|
" model.add(y > z - b)\n",
|
||||||
|
" model.add(z > x)\n",
|
||||||
|
"\n",
|
||||||
|
" model.add_multiplication_equality(square_a, a, a)\n",
|
||||||
|
" model.add_multiplication_equality(square_b, b, b)\n",
|
||||||
|
"\n",
|
||||||
|
" # Adds an objective to minimize the added slacks.\n",
|
||||||
|
" model.minimize(square_a + 2 * square_b)\n",
|
||||||
|
"\n",
|
||||||
|
" # Creates a solver and solves.\n",
|
||||||
|
" solver = cp_model.CpSolver()\n",
|
||||||
|
" status = solver.solve(model)\n",
|
||||||
|
"\n",
|
||||||
|
" # Print solution.\n",
|
||||||
|
" print(f\" Status = {solver.status_name(status)}\")\n",
|
||||||
|
" if status == cp_model.OPTIMAL:\n",
|
||||||
|
" print(f\" Objective value = {solver.objective_value}\")\n",
|
||||||
|
" print(f\" Value of x = {solver.value(x)}\")\n",
|
||||||
|
" print(f\" Value of y = {solver.value(y)}\")\n",
|
||||||
|
" print(f\" Value of z = {solver.value(z)}\")\n",
|
||||||
|
" print(f\" Value of a = {solver.value(a)}\")\n",
|
||||||
|
" print(f\" Value of b = {solver.value(b)}\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"def main() -> None:\n",
|
||||||
|
" print(\"Infeasible model:\")\n",
|
||||||
|
" infeasible_model()\n",
|
||||||
|
" print(\"Model with enforcement literals:\")\n",
|
||||||
|
" model_with_enforcement_literals()\n",
|
||||||
|
" print(\"Model with linear violations:\")\n",
|
||||||
|
" model_with_linear_violations()\n",
|
||||||
|
" print(\"Model with quadratic violations:\")\n",
|
||||||
|
" model_with_quadratic_violations()\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"main()\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
@@ -83,7 +83,8 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from typing import Dict, List, Sequence, Tuple, Union\n",
|
"from collections.abc import Sequence\n",
|
||||||
|
"from typing import Union\n",
|
||||||
"\n",
|
"\n",
|
||||||
"from ortools.sat.python import cp_model\n",
|
"from ortools.sat.python import cp_model\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -93,9 +94,9 @@
|
|||||||
" starts: Sequence[cp_model.IntVar],\n",
|
" starts: Sequence[cp_model.IntVar],\n",
|
||||||
" durations: Sequence[int],\n",
|
" durations: Sequence[int],\n",
|
||||||
" presences: Sequence[Union[cp_model.IntVar, bool]],\n",
|
" presences: Sequence[Union[cp_model.IntVar, bool]],\n",
|
||||||
" penalties: Dict[Tuple[int, int], int],\n",
|
" penalties: dict[tuple[int, int], int],\n",
|
||||||
" delays: Dict[Tuple[int, int], int],\n",
|
" delays: dict[tuple[int, int], int],\n",
|
||||||
") -> Sequence[Tuple[cp_model.IntVar, int]]:\n",
|
") -> Sequence[tuple[cp_model.IntVar, int]]:\n",
|
||||||
" \"\"\"This method uses a circuit constraint to rank tasks.\n",
|
" \"\"\"This method uses a circuit constraint to rank tasks.\n",
|
||||||
"\n",
|
"\n",
|
||||||
" This method assumes that all starts are disjoint, meaning that all tasks have\n",
|
" This method assumes that all starts are disjoint, meaning that all tasks have\n",
|
||||||
@@ -132,7 +133,7 @@
|
|||||||
" num_tasks = len(starts)\n",
|
" num_tasks = len(starts)\n",
|
||||||
" all_tasks = range(num_tasks)\n",
|
" all_tasks = range(num_tasks)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" arcs: List[cp_model.ArcT] = []\n",
|
" arcs: list[cp_model.ArcT] = []\n",
|
||||||
" penalty_terms = []\n",
|
" penalty_terms = []\n",
|
||||||
" for i in all_tasks:\n",
|
" for i in all_tasks:\n",
|
||||||
" # if node i is first.\n",
|
" # if node i is first.\n",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"from ortools.set_cover.python import set_cover\n",
|
"from ortools.set_cover.python import set_cover\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"\n",
|
||||||
"def main():\n",
|
"def main():\n",
|
||||||
" model = set_cover.SetCoverModel()\n",
|
" model = set_cover.SetCoverModel()\n",
|
||||||
" model.add_empty_subset(2.0)\n",
|
" model.add_empty_subset(2.0)\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user