examples: regenerate notebook

This commit is contained in:
Mizux Seiha
2025-12-19 15:01:21 +01:00
parent 2f299d5d51
commit c3164316fb
63 changed files with 1903 additions and 909 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
] ]
}, },
{ {

View File

@@ -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",

View 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
}

View File

@@ -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"
] ]
}, },
{ {

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View 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
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View 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
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View 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
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View 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
}

View File

@@ -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",

View File

@@ -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",