diff --git a/examples/notebook/constraint_solver/cvrp_reload.ipynb b/examples/notebook/constraint_solver/cvrp_reload.ipynb
index 82b8a28766..dce7f7058d 100644
--- a/examples/notebook/constraint_solver/cvrp_reload.ipynb
+++ b/examples/notebook/constraint_solver/cvrp_reload.ipynb
@@ -144,12 +144,12 @@
" -_capacity,\n",
" -_capacity,\n",
" -_capacity,\n",
- " 1, 1, # 1, 2\n",
- " 2, 4, # 3, 4\n",
- " 2, 4, # 5, 6\n",
+ " 3, 3, # 1, 2\n",
+ " 3, 4, # 3, 4\n",
+ " 3, 4, # 5, 6\n",
" 8, 8, # 7, 8\n",
- " 1, 2, # 9,10\n",
- " 1, 2, # 11,12\n",
+ " 3, 3, # 9,10\n",
+ " 3, 3, # 11,12\n",
" 4, 4, # 13, 14\n",
" 8, 8] # 15, 16\n",
" data['time_per_demand_unit'] = 5 # 5 minutes/unit\n",
@@ -160,16 +160,18 @@
" (0, 1000),\n",
" (0, 1000),\n",
" (0, 1000),\n",
- " (75, 8500), (75, 8500), # 1, 2\n",
- " (60, 7000), (45, 5500), # 3, 4\n",
- " (0, 8000), (50, 6000), # 5, 6\n",
- " (0, 1000), (10, 2000), # 7, 8\n",
- " (0, 1000), (75, 8500), # 9, 10\n",
- " (85, 9500), (5, 1500), # 11, 12\n",
- " (15, 2500), (10, 2000), # 13, 14\n",
- " (45, 5500), (30, 4000)] # 15, 16\n",
+ " (75, 850), (75, 850), # 1, 2\n",
+ " (60, 700), (45, 550), # 3, 4\n",
+ " (0, 800), (50, 600), # 5, 6\n",
+ " (0, 1000), (10, 200), # 7, 8\n",
+ " (0, 1000), (75, 850), # 9, 10\n",
+ " (85, 950), (5, 150), # 11, 12\n",
+ " (15, 250), (10, 200), # 13, 14\n",
+ " (45, 550), (30, 400)] # 15, 16\n",
" data['num_vehicles'] = 3\n",
" data['vehicle_capacity'] = _capacity\n",
+ " data['vehicle_max_distance'] = 10_000\n",
+ " data['vehicle_max_time'] = 1_500\n",
" data[\n",
" 'vehicle_speed'] = 5 * 60 / 3.6 # Travel speed: 5km/h to convert in m/min\n",
" data['depot'] = 0\n",
@@ -194,6 +196,9 @@
" for to_node in range(data['num_locations']):\n",
" if from_node == to_node:\n",
" _distances[from_node][to_node] = 0\n",
+ " # Forbid start/end/reload node to be consecutive.\n",
+ " elif from_node in range(6) and to_node in range(6):\n",
+ " _distances[from_node][to_node] = data['vehicle_max_distance']\n",
" else:\n",
" _distances[from_node][to_node] = (manhattan_distance(\n",
" data['locations'][from_node], data['locations'][to_node]))\n",
@@ -206,13 +211,13 @@
" return distance_evaluator\n",
"\n",
"\n",
- "def add_distance_dimension(routing, distance_evaluator_index):\n",
+ "def add_distance_dimension(routing, manager, data, distance_evaluator_index):\n",
" \"\"\"Add Global Span constraint\"\"\"\n",
" distance = 'Distance'\n",
" routing.AddDimension(\n",
" distance_evaluator_index,\n",
" 0, # null slack\n",
- " 10000, # maximum distance per vehicle\n",
+ " data['vehicle_max_distance'], # maximum distance per vehicle\n",
" True, # start cumul to zero\n",
" distance)\n",
" distance_dimension = routing.GetDimensionOrDie(distance)\n",
@@ -238,7 +243,7 @@
" capacity = 'Capacity'\n",
" routing.AddDimension(\n",
" demand_evaluator_index,\n",
- " 0, # Null slack\n",
+ " vehicle_capacity,\n",
" vehicle_capacity,\n",
" True, # start cumul to zero\n",
" capacity)\n",
@@ -247,11 +252,17 @@
" # e.g. vehicle with load 10/15 arrives at node 1 (depot unload)\n",
" # so we have CumulVar = 10(current load) + -15(unload) + 5(slack) = 0.\n",
" capacity_dimension = routing.GetDimensionOrDie(capacity)\n",
+ " # Allow to drop reloading nodes with zero cost.\n",
" for node_index in [1, 2, 3, 4, 5]:\n",
" index = manager.NodeToIndex(node_index)\n",
- " capacity_dimension.SlackVar(index).SetRange(0, vehicle_capacity)\n",
" routing.AddDisjunction([node_index], 0)\n",
"\n",
+ " # Allow to drop regular node with a cost.\n",
+ " for node_index in range(6, len(data['demands'])):\n",
+ " index = manager.NodeToIndex(node_index)\n",
+ " capacity_dimension.SlackVar(index).SetValue(0)\n",
+ " routing.AddDisjunction([node_index], 100_000)\n",
+ "\n",
"\n",
"def create_time_evaluator(data):\n",
" \"\"\"Creates callback to get total times between locations.\"\"\"\n",
@@ -265,8 +276,8 @@
" if from_node == to_node:\n",
" travel_time = 0\n",
" else:\n",
- " travel_time = manhattan_distance(data['locations'][\n",
- " from_node], data['locations'][to_node]) / data['vehicle_speed']\n",
+ " travel_time = manhattan_distance(\n",
+ " data['locations'][from_node], data['locations'][to_node]) / data['vehicle_speed']\n",
" return travel_time\n",
"\n",
" _total_time = {}\n",
@@ -292,11 +303,11 @@
"def add_time_window_constraints(routing, manager, data, time_evaluator):\n",
" \"\"\"Add Time windows constraint\"\"\"\n",
" time = 'Time'\n",
- " horizon = 1500\n",
+ " max_time = data['vehicle_max_time']\n",
" routing.AddDimension(\n",
" time_evaluator,\n",
- " horizon, # allow waiting time\n",
- " horizon, # maximum time per vehicle\n",
+ " max_time, # allow waiting time\n",
+ " max_time, # maximum time per vehicle\n",
" False, # don't force start cumul to zero since we are giving TW to start nodes\n",
" time)\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
@@ -324,22 +335,27 @@
"###########\n",
"def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n",
" \"\"\"Prints assignment on console\"\"\"\n",
- " print('Objective: {}'.format(assignment.ObjectiveValue()))\n",
+ " print(f'Objective: {assignment.ObjectiveValue()}')\n",
" total_distance = 0\n",
" total_load = 0\n",
" total_time = 0\n",
" capacity_dimension = routing.GetDimensionOrDie('Capacity')\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" dropped = []\n",
- " for order in range(0, routing.nodes()):\n",
+ " for order in range(6, routing.nodes()):\n",
" index = manager.NodeToIndex(order)\n",
" if assignment.Value(routing.NextVar(index)) == index:\n",
" dropped.append(order)\n",
- " print('dropped orders: {}'.format(dropped))\n",
+ " print(f'dropped orders: {dropped}')\n",
+ " for reload in range(1, 6):\n",
+ " index = manager.NodeToIndex(reload)\n",
+ " if assignment.Value(routing.NextVar(index)) == index:\n",
+ " dropped.append(reload)\n",
+ " print(f'dropped reload stations: {dropped}')\n",
"\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" index = routing.Start(vehicle_id)\n",
- " plan_output = 'Route for vehicle {}:\\n'.format(vehicle_id)\n",
+ " plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" distance = 0\n",
" while not routing.IsEnd(index):\n",
" load_var = capacity_dimension.CumulVar(index)\n",
@@ -358,11 +374,9 @@
" manager.IndexToNode(index),\n",
" assignment.Value(load_var),\n",
" assignment.Min(time_var), assignment.Max(time_var))\n",
- " plan_output += 'Distance of the route: {}m\\n'.format(distance)\n",
- " plan_output += 'Load of the route: {}\\n'.format(\n",
- " assignment.Value(load_var))\n",
- " plan_output += 'Time of the route: {}min\\n'.format(\n",
- " assignment.Value(time_var))\n",
+ " plan_output += f'Distance of the route: {distance}m\\n'\n",
+ " plan_output += f'Load of the route: {assignment.Value(load_var)}\\n'\n",
+ " plan_output += f'Time of the route: {assignment.Value(time_var)}min\\n'\n",
" print(plan_output)\n",
" total_distance += distance\n",
" total_load += assignment.Value(load_var)\n",
@@ -392,7 +406,7 @@
"routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n",
"\n",
"# Add Distance constraint to minimize the longuest route\n",
- "add_distance_dimension(routing, distance_evaluator_index)\n",
+ "add_distance_dimension(routing, manager, data, distance_evaluator_index)\n",
"\n",
"# Add Capacity constraint\n",
"demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n",
@@ -408,9 +422,16 @@
"search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
"search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n",
+ "search_parameters.local_search_metaheuristic = (\n",
+ " routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
+ "search_parameters.time_limit.FromSeconds(3)\n",
+ "\n",
"# Solve the problem.\n",
- "assignment = routing.SolveWithParameters(search_parameters)\n",
- "print_solution(data, manager, routing, assignment)\n",
+ "solution = routing.SolveWithParameters(search_parameters)\n",
+ "if solution:\n",
+ " print_solution(data, manager, routing, solution)\n",
+ "else:\n",
+ " print(\"No solution found !\")\n",
"\n"
]
}
diff --git a/examples/notebook/constraint_solver/tsp_cities.ipynb b/examples/notebook/constraint_solver/tsp_cities.ipynb
index 0959509a43..c8a2e25270 100644
--- a/examples/notebook/constraint_solver/tsp_cities.ipynb
+++ b/examples/notebook/constraint_solver/tsp_cities.ipynb
@@ -85,7 +85,6 @@
"# [START import]\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
diff --git a/examples/notebook/constraint_solver/vrp_global_span.ipynb b/examples/notebook/constraint_solver/vrp_global_span.ipynb
index 38e296fa7b..b771473c7e 100644
--- a/examples/notebook/constraint_solver/vrp_global_span.ipynb
+++ b/examples/notebook/constraint_solver/vrp_global_span.ipynb
@@ -188,7 +188,6 @@
" max_route_distance = max(route_distance, max_route_distance)\n",
" print('Maximum of the route distances: {}m'.format(max_route_distance))\n",
"\n",
- "\n",
"# [END solution_printer]\n",
"\n",
"\n",
diff --git a/examples/notebook/constraint_solver/vrp_initial_routes.ipynb b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb
index 3af95990a6..d5506dc6ed 100644
--- a/examples/notebook/constraint_solver/vrp_initial_routes.ipynb
+++ b/examples/notebook/constraint_solver/vrp_initial_routes.ipynb
@@ -84,7 +84,6 @@
"\n",
"# [START import]\n",
"from ortools.constraint_solver import pywrapcp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -196,7 +195,6 @@
" max_route_distance = max(route_distance, max_route_distance)\n",
" print('Maximum of the route distances: {}m'.format(max_route_distance))\n",
"\n",
- "\n",
"# [END solution_printer]\n",
"\n",
"\n",
diff --git a/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb b/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb
new file mode 100644
index 0000000000..89a8ab0076
--- /dev/null
+++ b/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb
@@ -0,0 +1,301 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "##### Copyright 2020 Google LLC."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "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",
+ "metadata": {},
+ "source": [
+ "# vrptw_store_solution_data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install ortools"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Copyright 2010-2018 Google LLC\n",
+ "# 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",
+ "# [START program]\n",
+ "\"\"\"VRPTW example that stores routes and cumulative data in an array.\"\"\"\n",
+ "\n",
+ "# [START import]\n",
+ "from ortools.constraint_solver import routing_enums_pb2\n",
+ "from ortools.constraint_solver import pywrapcp\n",
+ "# [END import]\n",
+ "\n",
+ "\n",
+ "# [START program_part1]\n",
+ "# [START data_model]\n",
+ "def create_data_model():\n",
+ " \"\"\"Stores the data for the problem.\"\"\"\n",
+ " data = {}\n",
+ " data['time_matrix'] = [\n",
+ " [0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],\n",
+ " [6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],\n",
+ " [9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],\n",
+ " [8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],\n",
+ " [7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],\n",
+ " [3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],\n",
+ " [6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],\n",
+ " [2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],\n",
+ " [3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],\n",
+ " [2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],\n",
+ " [6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],\n",
+ " [6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],\n",
+ " [4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],\n",
+ " [4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],\n",
+ " [5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],\n",
+ " [9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],\n",
+ " [7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],\n",
+ " ]\n",
+ " data['time_windows'] = [\n",
+ " (0, 5), # depot\n",
+ " (7, 12), # 1\n",
+ " (10, 15), # 2\n",
+ " (16, 18), # 3\n",
+ " (10, 13), # 4\n",
+ " (0, 5), # 5\n",
+ " (5, 10), # 6\n",
+ " (0, 4), # 7\n",
+ " (5, 10), # 8\n",
+ " (0, 3), # 9\n",
+ " (10, 16), # 10\n",
+ " (10, 15), # 11\n",
+ " (0, 5), # 12\n",
+ " (5, 10), # 13\n",
+ " (7, 8), # 14\n",
+ " (10, 15), # 15\n",
+ " (11, 15), # 16\n",
+ " ]\n",
+ " data['num_vehicles'] = 4\n",
+ " data['depot'] = 0\n",
+ " return data\n",
+ "\n",
+ "# [END data_model]\n",
+ "\n",
+ "\n",
+ "# [START solution_printer]\n",
+ "def print_solution(routes, cumul_data):\n",
+ " \"\"\"Print the solution.\"\"\"\n",
+ " total_time = 0\n",
+ " route_str = ''\n",
+ " for i, route in enumerate(routes):\n",
+ " route_str += 'Route ' + str(i) + ':\\n'\n",
+ " start_time = cumul_data[i][0][0]\n",
+ " end_time = cumul_data[i][0][1]\n",
+ " route_str += ' ' + str(route[0]) + \\\n",
+ " ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n",
+ " for j in range(1, len(route)):\n",
+ " start_time = cumul_data[i][j][0]\n",
+ " end_time = cumul_data[i][j][1]\n",
+ " route_str += ' -> ' + str(route[j]) + \\\n",
+ " ' Time(' + str(start_time) + ', ' + str(end_time) + ')'\n",
+ " route_str += '\\n Route time: {} min\\n\\n'.format(start_time)\n",
+ " total_time += cumul_data[i][len(route) - 1][0]\n",
+ " route_str += 'Total time: {} min'.format(total_time)\n",
+ " print(route_str)\n",
+ "\n",
+ "# [END solution_printer]\n",
+ "\n",
+ "\n",
+ "# [START get_routes]\n",
+ "def get_routes(solution, routing, manager):\n",
+ " \"\"\"Get vehicle routes from a solution and store them in an array.\"\"\"\n",
+ " # Get vehicle routes and store them in a two dimensional array whose\n",
+ " # i,j entry is the jth location visited by vehicle i along its route.\n",
+ " routes = []\n",
+ " for route_nbr in range(routing.vehicles()):\n",
+ " index = routing.Start(route_nbr)\n",
+ " route = [manager.IndexToNode(index)]\n",
+ " while not routing.IsEnd(index):\n",
+ " index = solution.Value(routing.NextVar(index))\n",
+ " route.append(manager.IndexToNode(index))\n",
+ " routes.append(route)\n",
+ " return routes\n",
+ "\n",
+ "# [END get_routes]\n",
+ "\n",
+ "\n",
+ "# [START get_cumulative_data]\n",
+ "def get_cumul_data(solution, routing, dimension):\n",
+ " \"\"\"Get cumulative data from a dimension and store it in an array.\"\"\"\n",
+ " # Returns an array cumul_data whose i,j entry contains the minimum and\n",
+ " # maximum of CumulVar for the dimension at the jth node on route :\n",
+ " # - cumul_data[i][j][0] is the minimum.\n",
+ " # - cumul_data[i][j][1] is the maximum.\n",
+ "\n",
+ " cumul_data = []\n",
+ " for route_nbr in range(routing.vehicles()):\n",
+ " route_data = []\n",
+ " index = routing.Start(route_nbr)\n",
+ " dim_var = dimension.CumulVar(index)\n",
+ " route_data.append([solution.Min(dim_var), solution.Max(dim_var)])\n",
+ " while not routing.IsEnd(index):\n",
+ " index = solution.Value(routing.NextVar(index))\n",
+ " dim_var = dimension.CumulVar(index)\n",
+ " route_data.append([solution.Min(dim_var), solution.Max(dim_var)])\n",
+ " cumul_data.append(route_data)\n",
+ " return cumul_data\n",
+ "\n",
+ "# [END get_cumulative_data]\n",
+ "\n",
+ "\n",
+ "\"\"\"Solve the VRP with time windows.\"\"\"\n",
+ "# Instantiate the data problem.\n",
+ "# [START data]\n",
+ "data = create_data_model()\n",
+ "# [END data]\n",
+ "\n",
+ "# Create the routing index manager.\n",
+ "# [START index_manager]\n",
+ "manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
+ " data['num_vehicles'], data['depot'])\n",
+ "# [END index_manager]\n",
+ "\n",
+ "# Create Routing Model.\n",
+ "# [START routing_model]\n",
+ "routing = pywrapcp.RoutingModel(manager)\n",
+ "\n",
+ "# [END routing_model]\n",
+ "\n",
+ "# Create and register a transit callback.\n",
+ "# [START transit_callback]\n",
+ "def time_callback(from_index, to_index):\n",
+ " \"\"\"Returns the travel time between the two nodes.\"\"\"\n",
+ " # Convert from routing variable Index to time matrix NodeIndex.\n",
+ " from_node = manager.IndexToNode(from_index)\n",
+ " to_node = manager.IndexToNode(to_index)\n",
+ " return data['time_matrix'][from_node][to_node]\n",
+ "\n",
+ "transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
+ "# [END transit_callback]\n",
+ "\n",
+ "# Define cost of each arc.\n",
+ "# [START arc_cost]\n",
+ "routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
+ "# [END arc_cost]\n",
+ "\n",
+ "# Add Time Windows constraint.\n",
+ "# [START time_windows_constraint]\n",
+ "time = 'Time'\n",
+ "\n",
+ "routing.AddDimension(\n",
+ " transit_callback_index,\n",
+ " 30, # allow waiting time\n",
+ " 30, # maximum time per vehicle\n",
+ " False, # Don't force cumulative time to be 0 at start of routes.\n",
+ " time)\n",
+ "time_dimension = routing.GetDimensionOrDie(time)\n",
+ "# Add time window constraints for each location except depot.\n",
+ "for location_idx, time_window in enumerate(data['time_windows']):\n",
+ " if location_idx == 0:\n",
+ " continue\n",
+ " index = manager.NodeToIndex(location_idx)\n",
+ " time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])\n",
+ "# Add time window constraints for each vehicle start node.\n",
+ "for vehicle_id in range(data['num_vehicles']):\n",
+ " index = routing.Start(vehicle_id)\n",
+ " time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],\n",
+ " data['time_windows'][0][1])\n",
+ "# [END time_windows_constraint]\n",
+ "\n",
+ "# Instantiate route start and end times to produce feasible times.\n",
+ "# [START depot_start_end_times]\n",
+ "for i in range(data['num_vehicles']):\n",
+ " routing.AddVariableMinimizedByFinalizer(\n",
+ " time_dimension.CumulVar(routing.Start(i)))\n",
+ " routing.AddVariableMinimizedByFinalizer(\n",
+ " time_dimension.CumulVar(routing.End(i)))\n",
+ "# [END depot_start_end_times]\n",
+ "\n",
+ "# Setting first solution heuristic.\n",
+ "# [START parameters]\n",
+ "search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
+ "search_parameters.first_solution_strategy = (\n",
+ " routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
+ "# [END parameters]\n",
+ "\n",
+ "# Solve the problem.\n",
+ "# [START solve]\n",
+ "solution = routing.SolveWithParameters(search_parameters)\n",
+ "# [END solve]\n",
+ "\n",
+ "# Print solution.\n",
+ "# [START print_solution]\n",
+ "if solution:\n",
+ " routes = get_routes(solution, routing, manager)\n",
+ " cumul_data = get_cumul_data(solution, routing, time_dimension)\n",
+ " print_solution(routes, cumul_data)\n",
+ "# [END print_solution]\n",
+ "\n"
+ ]
+ }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/examples/notebook/examples/appointments.ipynb b/examples/notebook/examples/appointments.ipynb
index 8648309fbc..4d446e0536 100644
--- a/examples/notebook/examples/appointments.ipynb
+++ b/examples/notebook/examples/appointments.ipynb
@@ -79,22 +79,26 @@
"# 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",
- "\"\"\"Generates possible daily schedules for workers.\"\"\"\n",
+ "\"\"\"Appointment selection.\n",
"\n",
+ "This module maximizes the number of appointments that can\n",
+ "be fulfilled by a crew of installers while staying close to ideal\n",
+ "ratio of appointment types.\n",
+ "\"\"\"\n",
"\n",
- "import argparse\n",
- "from ortools.sat.python import cp_model\n",
+ "# overloaded sum() clashes with pytype.\n",
+ "# pytype: disable=wrong-arg-types\n",
+ "\n",
+ "from absl import app\n",
+ "from absl import flags\n",
"from ortools.linear_solver import pywraplp\n",
+ "from ortools.sat.python import cp_model\n",
"\n",
- "PARSER = argparse.ArgumentParser()\n",
- "PARSER.add_argument(\n",
- " '--load_min', default=480, type=int, help='Minimum load in minutes')\n",
- "PARSER.add_argument(\n",
- " '--load_max', default=540, type=int, help='Maximum load in minutes')\n",
- "PARSER.add_argument(\n",
- " '--commute_time', default=30, type=int, help='Commute time in minutes')\n",
- "PARSER.add_argument(\n",
- " '--num_workers', default=98, type=int, help='Maximum number of workers.')\n",
+ "FLAGS = flags.FLAGS\n",
+ "flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.')\n",
+ "flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.')\n",
+ "flags.DEFINE_integer('commute_time', 30, 'Commute time in minutes.')\n",
+ "flags.DEFINE_integer('num_workers', 98, 'Maximum number of workers.')\n",
"\n",
"\n",
"class AllSolutionCollector(cp_model.CpSolverSolutionCallback):\n",
@@ -115,29 +119,26 @@
" return self.__collect\n",
"\n",
"\n",
- "def find_combinations(durations, load_min, load_max, commute_time):\n",
- " \"\"\"This methods find all valid combinations of appointments.\n",
+ "def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,\n",
+ " total_size_max):\n",
+ " \"\"\"Enumerate all possible knapsacks with total size in the given range.\n",
"\n",
- " This methods find all combinations of appointments such that the sum of\n",
- " durations + commute times is between load_min and load_max.\n",
+ " Args:\n",
+ " item_sizes: a list of integers. item_sizes[i] is the size of item #i.\n",
+ " total_size_min: an integer, the minimum total size.\n",
+ " total_size_max: an integer, the maximum total size.\n",
"\n",
- " Args:\n",
- " durations: The durations of all appointments.\n",
- " load_min: The min number of worked minutes for a valid selection.\n",
- " load_max: The max number of worked minutes for a valid selection.\n",
- " commute_time: The commute time between two appointments in minutes.\n",
- "\n",
- " Returns:\n",
- " A matrix where each line is a valid combinations of appointments.\n",
- " \"\"\"\n",
+ " Returns:\n",
+ " The list of all the knapsacks whose total size is in the given inclusive\n",
+ " range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an\n",
+ " nonnegative integer: the number of times we put item #K in the knapsack.\n",
+ " \"\"\"\n",
" model = cp_model.CpModel()\n",
" variables = [\n",
- " model.NewIntVar(0, load_max // (duration + commute_time), '')\n",
- " for duration in durations\n",
+ " model.NewIntVar(0, total_size_max // size, '') for size in item_sizes\n",
" ]\n",
- " terms = sum(variables[i] * (duration + commute_time)\n",
- " for i, duration in enumerate(durations))\n",
- " model.AddLinearConstraint(terms, load_min, load_max)\n",
+ " load = sum(variables[i] * size for i, size in enumerate(item_sizes))\n",
+ " model.AddLinearConstraint(load, total_size_min, total_size_max)\n",
"\n",
" solver = cp_model.CpSolver()\n",
" solution_collector = AllSolutionCollector(variables)\n",
@@ -145,86 +146,162 @@
" return solution_collector.combinations()\n",
"\n",
"\n",
- "def select(combinations, loads, max_number_of_workers):\n",
- " \"\"\"This method selects the optimal combination of appointments.\n",
+ "def AggregateItemCollectionsOptimally(item_collections, max_num_collections,\n",
+ " ideal_item_ratios):\n",
+ " \"\"\"Selects a set (with repetition) of combination of items optimally.\n",
"\n",
- " This method uses Mixed Integer Programming to select the optimal mix of\n",
- " appointments.\n",
+ " Given a set of collections of N possible items (in each collection, an item\n",
+ " may appear multiple times), a given \"ideal breakdown of items\", and a\n",
+ " maximum number of collections, this method finds the optimal way to\n",
+ " aggregate the collections in order to:\n",
+ " - maximize the overall number of items\n",
+ " - while keeping the ratio of each item, among the overall selection, as close\n",
+ " as possible to a given input ratio (which depends on the item).\n",
+ " Each collection may be selected more than one time.\n",
+ "\n",
+ " Args:\n",
+ " item_collections: a list of item collections. Each item collection is a\n",
+ " list of integers [#item0, ..., #itemN-1], where #itemK is the number\n",
+ " of times item #K appears in the collection, and N is the number of\n",
+ " distinct items.\n",
+ " max_num_collections: an integer, the maximum number of item collections\n",
+ " that may be selected (counting repetitions of the same collection).\n",
+ " ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is\n",
+ " the ideal ratio of item #K in the whole aggregated selection.\n",
+ "\n",
+ " Returns:\n",
+ " A pair (objective value, list of pairs (item collection, num_selections)),\n",
+ " where:\n",
+ " - \"objective value\" is the value of the internal objective function used\n",
+ " by the MIP Solver\n",
+ " - Each \"item collection\" is an element of the input item_collections\n",
+ " - and its associated \"num_selections\" is the number of times it was\n",
+ " selected.\n",
" \"\"\"\n",
" solver = pywraplp.Solver('Select',\n",
- " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n",
- " num_vars = len(loads)\n",
- " num_combinations = len(combinations)\n",
- " variables = [\n",
- " solver.IntVar(0, max_number_of_workers, 's[%d]' % i)\n",
- " for i in range(num_combinations)\n",
- " ]\n",
- " achieved = [\n",
- " solver.IntVar(0, 1000, 'achieved[%d]' % i) for i in range(num_vars)\n",
- " ]\n",
- " transposed = [[\n",
- " combinations[type][index] for type in range(num_combinations)\n",
- " ] for index in range(num_vars)]\n",
+ " pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)\n",
+ " n = len(ideal_item_ratios)\n",
+ " num_distinct_collections = len(item_collections)\n",
+ " max_num_items_per_collection = 0\n",
+ " for template in item_collections:\n",
+ " max_num_items_per_collection = max(max_num_items_per_collection,\n",
+ " sum(template))\n",
+ " upper_bound = max_num_items_per_collection * max_num_collections\n",
"\n",
- " # Maintain the achieved variables.\n",
- " for i, coefs in enumerate(transposed):\n",
+ " # num_selections_of_collection[i] is an IntVar that represents the number\n",
+ " # of times that we will use collection #i in our global selection.\n",
+ " num_selections_of_collection = [\n",
+ " solver.IntVar(0, max_num_collections, 's[%d]' % i)\n",
+ " for i in range(num_distinct_collections)\n",
+ " ]\n",
+ "\n",
+ " # num_overall_item[i] is an IntVar that represents the total count of item #i,\n",
+ " # aggregated over all selected collections. This is enforced with dedicated\n",
+ " # constraints that bind them with the num_selections_of_collection vars.\n",
+ " num_overall_item = [\n",
+ " solver.IntVar(0, upper_bound, 'num_overall_item[%d]' % i)\n",
+ " for i in range(n)\n",
+ " ]\n",
+ " for i in range(n):\n",
" ct = solver.Constraint(0.0, 0.0)\n",
- " ct.SetCoefficient(achieved[i], -1)\n",
- " for j, coef in enumerate(coefs):\n",
- " ct.SetCoefficient(variables[j], coef)\n",
+ " ct.SetCoefficient(num_overall_item[i], -1)\n",
+ " for j in range(num_distinct_collections):\n",
+ " ct.SetCoefficient(num_selections_of_collection[j],\n",
+ " item_collections[j][i])\n",
"\n",
- " # Simple bound.\n",
- " solver.Add(solver.Sum(variables) <= max_number_of_workers)\n",
+ " # Maintain the num_all_item variable as the sum of all num_overall_item\n",
+ " # variables.\n",
+ " num_all_items = solver.IntVar(0, upper_bound, 'num_all_items')\n",
+ " solver.Add(solver.Sum(num_overall_item) == num_all_items)\n",
"\n",
- " obj_vars = [\n",
- " solver.IntVar(0, 1000, 'obj_vars[%d]' % i) for i in range(num_vars)\n",
+ " # Sets the total number of workers.\n",
+ " solver.Add(solver.Sum(num_selections_of_collection) == max_num_collections)\n",
+ "\n",
+ " # Objective variables.\n",
+ " deviation_vars = [\n",
+ " solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i)\n",
+ " for i in range(n)\n",
" ]\n",
- " for i in range(num_vars):\n",
- " solver.Add(obj_vars[i] >= achieved[i] - loads[i])\n",
- " solver.Add(obj_vars[i] >= loads[i] - achieved[i])\n",
+ " for i in range(n):\n",
+ " deviation = deviation_vars[i]\n",
+ " solver.Add(deviation >= num_overall_item[i] -\n",
+ " ideal_item_ratios[i] * num_all_items)\n",
+ " solver.Add(deviation >= ideal_item_ratios[i] * num_all_items -\n",
+ " num_overall_item[i])\n",
"\n",
- " solver.Minimize(solver.Sum(obj_vars))\n",
+ " solver.Maximize(num_all_items - solver.Sum(deviation_vars))\n",
"\n",
" result_status = solver.Solve()\n",
"\n",
- " # The problem has an optimal solution.\n",
" if result_status == pywraplp.Solver.OPTIMAL:\n",
- " print('Problem solved in %f milliseconds' % solver.WallTime())\n",
- " return solver.Objective().Value(), [\n",
- " int(v.SolutionValue()) for v in variables\n",
- " ]\n",
- " return -1, []\n",
+ " # The problem has an optimal solution.\n",
+ " return [int(v.solution_value()) for v in num_selections_of_collection]\n",
+ " return []\n",
"\n",
"\n",
- "def get_optimal_schedule(demand, args):\n",
- " \"\"\"Computes the optimal schedule for the appointment selection problem.\"\"\"\n",
- " combinations = find_combinations([a[2] for a in demand], args.load_min,\n",
- " args.load_max, args.commute_time)\n",
- " print('found %d possible combinations of appointements' % len(combinations))\n",
+ "def GetOptimalSchedule(demand):\n",
+ " \"\"\"Computes the optimal schedule for the installation input.\n",
"\n",
- " cost, selection = select(combinations, [a[0]\n",
- " for a in demand], args.num_workers)\n",
- " output = [(selection[i], [(combinations[i][t], demand[t][1])\n",
- " for t in range(len(demand))\n",
- " if combinations[i][t] != 0])\n",
- " for i in range(len(selection)) if selection[i] != 0]\n",
- " return cost, output\n",
+ " Args:\n",
+ " demand: a list of \"appointment types\". Each \"appointment type\" is\n",
+ " a triple (ideal_ratio_pct, name, duration_minutes), where\n",
+ " ideal_ratio_pct is the ideal percentage (in [0..100.0]) of that\n",
+ " type of appointment among all appointments scheduled.\n",
+ "\n",
+ " Returns:\n",
+ " The same output type as EnumerateAllKnapsacksWithRepetition.\n",
+ " \"\"\"\n",
+ " combinations = EnumerateAllKnapsacksWithRepetition(\n",
+ " [a[2] + FLAGS.commute_time for a in demand], FLAGS.load_min,\n",
+ " FLAGS.load_max)\n",
+ " print(('Found %d possible day schedules ' % len(combinations) +\n",
+ " '(i.e. combination of appointments filling up one worker\\'s day)'))\n",
+ "\n",
+ " selection = AggregateItemCollectionsOptimally(\n",
+ " combinations, FLAGS.num_workers, [a[0] / 100.0 for a in demand])\n",
+ " output = []\n",
+ " for i in range(len(selection)):\n",
+ " if selection[i] != 0:\n",
+ " output.append((selection[i], [(combinations[i][t], demand[t][1])\n",
+ " for t in range(len(demand))\n",
+ " if combinations[i][t] != 0]))\n",
+ "\n",
+ " return output\n",
"\n",
"\n",
- "\"\"\"Solve the assignment problem.\"\"\"\n",
- "demand = [(40, 'A1', 90), (30, 'A2', 120), (25, 'A3', 180)]\n",
- "print('appointments: ')\n",
+ "demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)]\n",
+ "print('*** input problem ***')\n",
+ "print('Appointments: ')\n",
"for a in demand:\n",
- " print(' %d * %s : %d min' % (a[0], a[1], a[2]))\n",
- "print('commute time = %d' % args.commute_time)\n",
- "print('accepted total duration = [%d..%d]' % (args.load_min, args.load_max))\n",
- "print('%d workers' % args.num_workers)\n",
- "cost, selection = get_optimal_schedule(demand, args)\n",
- "print('Optimal solution as a cost of %d' % cost)\n",
+ " print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2]))\n",
+ "print('Commute time = %d' % FLAGS.commute_time)\n",
+ "print('Acceptable duration of a work day = [%d..%d]' %\n",
+ " (FLAGS.load_min, FLAGS.load_max))\n",
+ "print('%d workers' % FLAGS.num_workers)\n",
+ "selection = GetOptimalSchedule(demand)\n",
+ "print()\n",
+ "installed = 0\n",
+ "installed_per_type = {}\n",
+ "for a in demand:\n",
+ " installed_per_type[a[1]] = 0\n",
+ "\n",
+ "print('*** output solution ***')\n",
"for template in selection:\n",
- " print('%d schedules with ' % template[0])\n",
+ " num_instances = template[0]\n",
+ " print('%d schedules with ' % num_instances)\n",
" for t in template[1]:\n",
- " print(' %d installation of type %s' % (t[0], t[1]))\n",
+ " mult = t[0]\n",
+ " print(' %d installation of type %s' % (mult, t[1]))\n",
+ " installed += num_instances * mult\n",
+ " installed_per_type[t[1]] += num_instances * mult\n",
+ "\n",
+ "print()\n",
+ "print('%d installations planned' % installed)\n",
+ "for a in demand:\n",
+ " name = a[1]\n",
+ " per_type = installed_per_type[name]\n",
+ " print((' %d (%.2f%%) installations of type %s planned' %\n",
+ " (per_type, per_type * 100.0 / installed, name)))\n",
"\n"
]
}
diff --git a/examples/notebook/examples/flexible_job_shop_sat.ipynb b/examples/notebook/examples/flexible_job_shop_sat.ipynb
index e9a042b0d4..8484b8e648 100644
--- a/examples/notebook/examples/flexible_job_shop_sat.ipynb
+++ b/examples/notebook/examples/flexible_job_shop_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Solves a flexible jobshop problems with the CP-SAT solver.\"\"\"\n",
"\n",
- "\n",
"import collections\n",
"from ortools.sat.python import cp_model\n",
"\n",
@@ -103,10 +102,23 @@
"def flexible_jobshop():\n",
" \"\"\"Solve a small flexible jobshop problem.\"\"\"\n",
" # Data part.\n",
- " jobs = [[[(3, 0), (1, 1), (5, 2)], [(2, 0), (4, 1), (6, 2)], [(2, 0), (3, 1), (1, 2)]],\n",
- " [[(2, 0), (3, 1), (4, 2)], [(1, 0), (5, 1), (4, 2)], [(2, 0), (1, 1), (4, 2)]],\n",
- " [[(2, 0), (1, 1), (4, 2)], [(2, 0), (3, 1), (4, 2)], [(3, 0), (1, 1), (5, 2)]]\n",
- " ] # yapf:disable\n",
+ " jobs = [ # task = (processing_time, machine_id)\n",
+ " [ # Job 0\n",
+ " [(3, 0), (1, 1), (5, 2)], # alternative 0\n",
+ " [(2, 0), (4, 1), (6, 2)], # alternative 1\n",
+ " [(2, 0), (3, 1), (1, 2)], # alternative 2\n",
+ " ],\n",
+ " [ # Job 1\n",
+ " [(2, 0), (3, 1), (4, 2)],\n",
+ " [(1, 0), (5, 1), (4, 2)],\n",
+ " [(2, 0), (1, 1), (4, 2)],\n",
+ " ],\n",
+ " [ # Job 2\n",
+ " [(2, 0), (1, 1), (4, 2)],\n",
+ " [(2, 0), (3, 1), (4, 2)],\n",
+ " [(3, 0), (1, 1), (5, 2)],\n",
+ " ],\n",
+ " ]\n",
"\n",
" num_jobs = len(jobs)\n",
" all_jobs = range(num_jobs)\n",
diff --git a/examples/notebook/examples/gate_scheduling_sat.ipynb b/examples/notebook/examples/gate_scheduling_sat.ipynb
index a26e126b82..74ccbbe26d 100644
--- a/examples/notebook/examples/gate_scheduling_sat.ipynb
+++ b/examples/notebook/examples/gate_scheduling_sat.ipynb
@@ -85,22 +85,39 @@
"We have two parallel machines that can perform this job.\n",
"One machine can only perform one job at a time.\n",
"At any point in time, the sum of the width of the two active jobs does not\n",
- "exceed a max_length.\n",
+ "exceed a max_width.\n",
"\n",
"The objective is to minimize the max end time of all jobs.\n",
"\"\"\"\n",
"\n",
- "from ortools.sat.python import cp_model\n",
+ "from absl import app\n",
+ "\n",
"from ortools.sat.python import visualization\n",
+ "from ortools.sat.python import cp_model\n",
"\n",
"\n",
"\"\"\"Solves the gate scheduling problem.\"\"\"\n",
"model = cp_model.CpModel()\n",
"\n",
- "jobs = [[3, 3], [2, 5], [1, 3], [3, 7], [7, 3], [2, 2], [2, 2], [5, 5],\n",
- " [10, 2], [4, 3], [2, 6], [1, 2], [6, 8], [4, 5], [3, 7]]\n",
+ "jobs = [\n",
+ " [3, 3], # [duration, width]\n",
+ " [2, 5],\n",
+ " [1, 3],\n",
+ " [3, 7],\n",
+ " [7, 3],\n",
+ " [2, 2],\n",
+ " [2, 2],\n",
+ " [5, 5],\n",
+ " [10, 2],\n",
+ " [4, 3],\n",
+ " [2, 6],\n",
+ " [1, 2],\n",
+ " [6, 8],\n",
+ " [4, 5],\n",
+ " [3, 7]\n",
+ "]\n",
"\n",
- "max_length = 10\n",
+ "max_width = 10\n",
"\n",
"horizon = sum(t[0] for t in jobs)\n",
"num_jobs = len(jobs)\n",
@@ -125,14 +142,14 @@
" ends.append(end)\n",
" demands.append(jobs[i][1])\n",
"\n",
+ " # Create an optional copy of interval to be executed on machine 0.\n",
" performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)\n",
" performed.append(performed_on_m0)\n",
- "\n",
- " # Create an optional copy of interval to be executed on machine 0.\n",
" start0 = model.NewIntVar(0, horizon, 'start_%i_on_m0' % i)\n",
" end0 = model.NewIntVar(0, horizon, 'end_%i_on_m0' % i)\n",
- " interval0 = model.NewOptionalIntervalVar(\n",
- " start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)\n",
+ " interval0 = model.NewOptionalIntervalVar(start0, duration, end0,\n",
+ " performed_on_m0,\n",
+ " 'interval_%i_on_m0' % i)\n",
" intervals0.append(interval0)\n",
"\n",
" # Create an optional copy of interval to be executed on machine 1.\n",
@@ -147,8 +164,8 @@
" model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)\n",
" model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())\n",
"\n",
- "# Max Length constraint (modeled as a cumulative)\n",
- "model.AddCumulative(intervals, demands, max_length)\n",
+ "# Width constraint (modeled as a cumulative)\n",
+ "model.AddCumulative(intervals, demands, max_width)\n",
"\n",
"# Choose which machine to perform the jobs on.\n",
"model.AddNoOverlap(intervals0)\n",
@@ -168,7 +185,7 @@
"\n",
"# Output solution.\n",
"if visualization.RunFromIPython():\n",
- " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_length,\n",
+ " output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width,\n",
" 40.0)\n",
" output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n",
" color_manager = visualization.ColorManager()\n",
@@ -179,7 +196,7 @@
" start = solver.Value(starts[i])\n",
" d_x = jobs[i][0]\n",
" d_y = jobs[i][1]\n",
- " s_y = performed_machine * (max_length - d_y)\n",
+ " s_y = performed_machine * (max_width - d_y)\n",
" output.AddRectangle(start, s_y, d_x, d_y,\n",
" color_manager.RandomColor(), 'black', 'j%i' % i)\n",
"\n",
@@ -192,8 +209,8 @@
" for i in all_jobs:\n",
" performed_machine = 1 - solver.Value(performed[i])\n",
" start = solver.Value(starts[i])\n",
- " print(' - Job %i starts at %i on machine %i' % (i, start,\n",
- " performed_machine))\n",
+ " print(' - Job %i starts at %i on machine %i' %\n",
+ " (i, start, performed_machine))\n",
" print('Statistics')\n",
" print(' - conflicts : %i' % solver.NumConflicts())\n",
" print(' - branches : %i' % solver.NumBranches())\n",
diff --git a/examples/notebook/examples/golomb8.ipynb b/examples/notebook/examples/golomb8.ipynb
index 22161c065d..f9cc123c83 100644
--- a/examples/notebook/examples/golomb8.ipynb
+++ b/examples/notebook/examples/golomb8.ipynb
@@ -115,13 +115,13 @@
"\n",
"# We expand the creation of the diff array to avoid a pylint warning.\n",
"diffs = []\n",
- "for i in range(0, size - 1):\n",
+ "for i in range(size - 1):\n",
" for j in range(i + 1, size):\n",
" diffs.append(marks[j] - marks[i])\n",
"solver.Add(solver.AllDifferent(diffs))\n",
"\n",
"solver.Add(marks[size - 1] - marks[size - 2] > marks[1] - marks[0])\n",
- "for i in range(0, size - 2):\n",
+ "for i in range(size - 2):\n",
" solver.Add(marks[i + 1] > marks[i])\n",
"\n",
"solution = solver.Assignment()\n",
diff --git a/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb
index de7dd696c1..60ce238e99 100644
--- a/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb
+++ b/examples/notebook/examples/jobshop_with_maintenance_sat.ipynb
@@ -81,12 +81,24 @@
"# limitations under the License.\n",
"\"\"\"Jobshop with maintenance tasks using the CP-SAT solver.\"\"\"\n",
"\n",
- "\n",
"import collections\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
+ "class SolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
+ " \"\"\"Print intermediate solutions.\"\"\"\n",
+ "\n",
+ " def __init__(self):\n",
+ " cp_model.CpSolverSolutionCallback.__init__(self)\n",
+ " self.__solution_count = 0\n",
+ "\n",
+ " def on_solution_callback(self):\n",
+ " \"\"\"Called at each new solution.\"\"\"\n",
+ " print('Solution %i, time = %f s, objective = %i' %\n",
+ " (self.__solution_count, self.WallTime(), self.ObjectiveValue()))\n",
+ " self.__solution_count += 1\n",
+ "\n",
+ "\n",
"def jobshop_with_maintenance():\n",
" \"\"\"Solves a jobshop with maintenance on one machine.\"\"\"\n",
" # Create the model.\n",
@@ -95,7 +107,7 @@
" jobs_data = [ # task = (machine_id, processing_time).\n",
" [(0, 3), (1, 2), (2, 2)], # Job0\n",
" [(0, 2), (2, 1), (1, 4)], # Job1\n",
- " [(1, 4), (2, 3)] # Job2\n",
+ " [(1, 4), (2, 3)], # Job2\n",
" ]\n",
"\n",
" machines_count = 1 + max(task[0] for job in jobs_data for task in job)\n",
@@ -123,8 +135,9 @@
" end_var = model.NewIntVar(0, horizon, 'end' + suffix)\n",
" interval_var = model.NewIntervalVar(start_var, duration, end_var,\n",
" 'interval' + suffix)\n",
- " all_tasks[job_id, task_id] = task_type(\n",
- " start=start_var, end=end_var, interval=interval_var)\n",
+ " all_tasks[job_id, task_id] = task_type(start=start_var,\n",
+ " end=end_var,\n",
+ " interval=interval_var)\n",
" machine_to_intervals[machine].append(interval_var)\n",
"\n",
" # Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}).\n",
@@ -150,7 +163,9 @@
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
- " status = solver.Solve(model)\n",
+ " solution_printer = SolutionPrinter()\n",
+ " status = solver.SolveWithSolutionCallback(model, solution_printer)\n",
+ " #status = solver.Solve(model)\n",
"\n",
" # Output solution.\n",
" if status == cp_model.OPTIMAL:\n",
@@ -160,11 +175,11 @@
" for task_id, task in enumerate(job):\n",
" machine = task[0]\n",
" assigned_jobs[machine].append(\n",
- " assigned_task_type(\n",
- " start=solver.Value(all_tasks[job_id, task_id].start),\n",
- " job=job_id,\n",
- " index=task_id,\n",
- " duration=task[1]))\n",
+ " assigned_task_type(start=solver.Value(\n",
+ " all_tasks[job_id, task_id].start),\n",
+ " job=job_id,\n",
+ " index=task_id,\n",
+ " duration=task[1]))\n",
"\n",
" # Create per machine output lines.\n",
" output = ''\n",
@@ -193,6 +208,10 @@
" # Finally print the solution found.\n",
" print('Optimal Schedule Length: %i' % solver.ObjectiveValue())\n",
" print(output)\n",
+ " print('Statistics')\n",
+ " print(' - conflicts : %i' % solver.NumConflicts())\n",
+ " print(' - branches : %i' % solver.NumBranches())\n",
+ " print(' - wall time : %f s' % solver.WallTime())\n",
"\n",
"\n",
"jobshop_with_maintenance()\n",
diff --git a/examples/notebook/examples/shift_scheduling_sat.ipynb b/examples/notebook/examples/shift_scheduling_sat.ipynb
index f2f9f3d4d3..eee2b70dbb 100644
--- a/examples/notebook/examples/shift_scheduling_sat.ipynb
+++ b/examples/notebook/examples/shift_scheduling_sat.ipynb
@@ -81,17 +81,18 @@
"# limitations under the License.\n",
"\"\"\"Creates a shift scheduling problem and solves it.\"\"\"\n",
"\n",
- "from ortools.sat.python import cp_model\n",
- "\n",
- "from google.protobuf import text_format\n",
"from absl import app\n",
"from absl import flags\n",
"\n",
+ "from ortools.sat.python import cp_model\n",
+ "from google.protobuf import text_format\n",
+ "\n",
"FLAGS = flags.FLAGS\n",
"\n",
"flags.DEFINE_string('output_proto', '',\n",
" 'Output file to write the cp_model proto to.')\n",
- "flags.DEFINE_string('params', '', 'Sat solver parameters.')\n",
+ "flags.DEFINE_string('params', 'max_time_in_seconds:10.0',\n",
+ " 'Sat solver parameters.')\n",
"\n",
"\n",
"def negated_bounded_span(works, start, length):\n",
diff --git a/examples/notebook/examples/steel_mill_slab_sat.ipynb b/examples/notebook/examples/steel_mill_slab_sat.ipynb
index fda185f811..8b9ac1102a 100644
--- a/examples/notebook/examples/steel_mill_slab_sat.ipynb
+++ b/examples/notebook/examples/steel_mill_slab_sat.ipynb
@@ -81,31 +81,25 @@
"# limitations under the License.\n",
"\"\"\"Solves the Stell Mill Slab problem with 4 different techniques.\"\"\"\n",
"\n",
+ "# overloaded sum() clashes with pytype.\n",
+ "# pytype: disable=wrong-arg-types\n",
"\n",
- "import argparse\n",
"import collections\n",
"import time\n",
"\n",
- "from ortools.sat.python import cp_model\n",
+ "from absl import app\n",
+ "from absl import flags\n",
"from ortools.linear_solver import pywraplp\n",
+ "from ortools.sat.python import cp_model\n",
"\n",
- "PARSER = argparse.ArgumentParser()\n",
+ "FLAGS = flags.FLAGS\n",
"\n",
- "PARSER.add_argument(\n",
- " '--problem', default=2, type=int, help='Problem id to solve.')\n",
- "PARSER.add_argument(\n",
- " '--break_symmetries',\n",
- " default=True,\n",
- " type=bool,\n",
- " help='Break symmetries between equivalent orders.')\n",
- "PARSER.add_argument(\n",
- " '--solver',\n",
- " default='sat_table',\n",
- " help='Method used to solve: sat, sat_table, sat_column, mip_column.')\n",
- "PARSER.add_argument(\n",
- " '--output_proto',\n",
- " default='',\n",
- " help='Output file to write the cp_model proto to.')\n",
+ "flags.DEFINE_integer('problem', 2, 'Problem id to solve.')\n",
+ "flags.DEFINE_boolean('break_symmetries', True,\n",
+ " 'Break symmetries between equivalent orders.')\n",
+ "flags.DEFINE_string(\n",
+ " 'solver', 'mip_column', 'Method used to solve: sat, sat_table, sat_column, '\n",
+ " 'mip_column.')\n",
"\n",
"\n",
"def build_problem(problem_id):\n",
@@ -364,7 +358,7 @@
" print(line)\n",
"\n",
"\n",
- "def steel_mill_slab(problem, break_symmetries, output_proto):\n",
+ "def steel_mill_slab(problem, break_symmetries):\n",
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
" ### Load problem.\n",
" (num_slabs, capacities, num_colors, orders) = build_problem(problem)\n",
@@ -377,17 +371,18 @@
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
"\n",
- " # Compute auxilliary data.\n",
+ " # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
- " min(x for x in capacities if x >= c) - c\n",
- " for c in range(max_capacity + 1)\n",
+ " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
+ " 1)\n",
" ]\n",
" max_loss = max(loss_array)\n",
- " orders_per_color = [[o for o in all_orders if colors[o] == c + 1]\n",
- " for c in all_colors]\n",
+ " orders_per_color = [\n",
+ " [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n",
+ " ]\n",
" unique_color_orders = [\n",
" o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1\n",
" ]\n",
@@ -404,14 +399,12 @@
" for s in all_slabs\n",
" ]\n",
" color_is_in_slab = [[\n",
- " model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s))\n",
- " for c in all_colors\n",
+ " model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s)) for c in all_colors\n",
" ] for s in all_slabs]\n",
"\n",
" # Compute load of all slabs.\n",
" for s in all_slabs:\n",
- " model.Add(\n",
- " sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n",
+ " model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])\n",
"\n",
" # Orders are assigned to one slab.\n",
" for o in all_orders:\n",
@@ -503,8 +496,9 @@
"\n",
" ### Output the solution.\n",
" if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n",
- " print('Loss = %i, time = %f s, %i conflicts' % (\n",
- " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
+ " print(\n",
+ " 'Loss = %i, time = %f s, %i conflicts' %\n",
+ " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
" else:\n",
" print('No solution')\n",
"\n",
@@ -527,19 +521,19 @@
" if assignment.load + new_width > max_capacity:\n",
" continue\n",
" new_colors = list(assignment.colors)\n",
- " if not new_color in new_colors:\n",
+ " if new_color not in new_colors:\n",
" new_colors.append(new_color)\n",
" if len(new_colors) > 2:\n",
" continue\n",
- " new_assignment = valid_assignment(\n",
- " orders=assignment.orders + [order_id],\n",
- " load=assignment.load + new_width,\n",
- " colors=new_colors)\n",
+ " new_assignment = valid_assignment(orders=assignment.orders +\n",
+ " [order_id],\n",
+ " load=assignment.load + new_width,\n",
+ " colors=new_colors)\n",
" new_assignments.append(new_assignment)\n",
" all_valid_assignments.extend(new_assignments)\n",
"\n",
- " print('%i assignments created in %.2f s' % (len(all_valid_assignments),\n",
- " time.time() - start_time))\n",
+ " print('%i assignments created in %.2f s' %\n",
+ " (len(all_valid_assignments), time.time() - start_time))\n",
" tuples = []\n",
" for assignment in all_valid_assignments:\n",
" solution = [0 for _ in range(len(colors))]\n",
@@ -552,7 +546,7 @@
" return tuples\n",
"\n",
"\n",
- "def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):\n",
+ "def steel_mill_slab_with_valid_slabs(problem, break_symmetries):\n",
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
" ### Load problem.\n",
" (num_slabs, capacities, num_colors, orders) = build_problem(problem)\n",
@@ -565,13 +559,13 @@
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
"\n",
- " # Compute auxilliary data.\n",
+ " # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
- " min(x for x in capacities if x >= c) - c\n",
- " for c in range(max_capacity + 1)\n",
+ " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
+ " 1)\n",
" ]\n",
" max_loss = max(loss_array)\n",
"\n",
@@ -582,21 +576,18 @@
" assign = [[\n",
" model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs\n",
" ] for o in all_orders]\n",
- " loads = [\n",
- " model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs\n",
- " ]\n",
+ " loads = [model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs]\n",
" losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]\n",
"\n",
" unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n",
" loss_array)\n",
" # Sort slab by descending load/loss. Remove duplicates.\n",
- " valid_slabs = sorted(\n",
- " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
+ " valid_slabs = sorted(unsorted_valid_slabs,\n",
+ " key=lambda c: 1000 * c[-1] + c[-2])\n",
"\n",
" for s in all_slabs:\n",
- " model.AddAllowedAssignments(\n",
- " [assign[o][s] for o in all_orders] + [losses[s], loads[s]],\n",
- " valid_slabs)\n",
+ " model.AddAllowedAssignments([assign[o][s] for o in all_orders] +\n",
+ " [losses[s], loads[s]], valid_slabs)\n",
"\n",
" # Orders are assigned to one slab.\n",
" for o in all_orders:\n",
@@ -614,8 +605,9 @@
" print('Breaking symmetries')\n",
" width_to_unique_color_order = {}\n",
" ordered_equivalent_orders = []\n",
- " orders_per_color = [[o for o in all_orders if colors[o] == c + 1]\n",
- " for c in all_colors]\n",
+ " orders_per_color = [\n",
+ " [o for o in all_orders if colors[o] == c + 1] for c in all_colors\n",
+ " ]\n",
" for c in all_colors:\n",
" colored_orders = orders_per_color[c]\n",
" if not colored_orders:\n",
@@ -637,8 +629,7 @@
" for w, os in local_width_to_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
- " ordered_equivalent_orders.append((os[p],\n",
- " os[p + 1]))\n",
+ " ordered_equivalent_orders.append((os[p], os[p + 1]))\n",
" for w, os in width_to_unique_color_order.items():\n",
" if len(os) > 1:\n",
" for p in range(len(os) - 1):\n",
@@ -666,12 +657,6 @@
"\n",
" print('Model created')\n",
"\n",
- " # Output model proto to file.\n",
- " if output_proto:\n",
- " output_file = open(output_proto, 'w')\n",
- " output_file.write(str(model.Proto()))\n",
- " output_file.close()\n",
- "\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.num_search_workers = 8\n",
@@ -681,13 +666,14 @@
"\n",
" ### Output the solution.\n",
" if status == cp_model.OPTIMAL:\n",
- " print('Loss = %i, time = %.2f s, %i conflicts' % (\n",
- " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
+ " print(\n",
+ " 'Loss = %i, time = %.2f s, %i conflicts' %\n",
+ " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
" else:\n",
" print('No solution')\n",
"\n",
"\n",
- "def steel_mill_slab_with_column_generation(problem, output_proto):\n",
+ "def steel_mill_slab_with_column_generation(problem):\n",
" \"\"\"Solves the Steel Mill Slab Problem.\"\"\"\n",
" ### Load problem.\n",
" (num_slabs, capacities, _, orders) = build_problem(problem)\n",
@@ -698,13 +684,13 @@
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
"\n",
- " # Compute auxilliary data.\n",
+ " # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
- " min(x for x in capacities if x >= c) - c\n",
- " for c in range(max_capacity + 1)\n",
+ " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
+ " 1)\n",
" ]\n",
"\n",
" ### Model problem.\n",
@@ -714,8 +700,8 @@
" loss_array)\n",
"\n",
" # Sort slab by descending load/loss. Remove duplicates.\n",
- " valid_slabs = sorted(\n",
- " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
+ " valid_slabs = sorted(unsorted_valid_slabs,\n",
+ " key=lambda c: 1000 * c[-1] + c[-2])\n",
" all_valid_slabs = range(len(valid_slabs))\n",
"\n",
" # create model and decision variables.\n",
@@ -724,7 +710,8 @@
"\n",
" for order_id in all_orders:\n",
" model.Add(\n",
- " sum(selected[i] for i, slab in enumerate(valid_slabs)\n",
+ " sum(selected[i]\n",
+ " for i, slab in enumerate(valid_slabs)\n",
" if slab[order_id]) == 1)\n",
"\n",
" # Redundant constraint (sum of loads == sum of widths).\n",
@@ -738,22 +725,18 @@
"\n",
" print('Model created')\n",
"\n",
- " # Output model proto to file.\n",
- " if output_proto:\n",
- " output_file = open(output_proto, 'w')\n",
- " output_file.write(str(model.Proto()))\n",
- " output_file.close()\n",
- "\n",
" ### Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.num_search_workers = 8\n",
+ " solver.parameters.log_search_progress = True\n",
" solution_printer = cp_model.ObjectiveSolutionPrinter()\n",
" status = solver.SolveWithSolutionCallback(model, solution_printer)\n",
"\n",
" ### Output the solution.\n",
" if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):\n",
- " print('Loss = %i, time = %.2f s, %i conflicts' % (\n",
- " solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
+ " print(\n",
+ " 'Loss = %i, time = %.2f s, %i conflicts' %\n",
+ " (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))\n",
" else:\n",
" print('No solution')\n",
"\n",
@@ -769,13 +752,13 @@
" print('Solving steel mill with %i orders, %i slabs, and %i capacities' %\n",
" (num_orders, num_slabs, num_capacities - 1))\n",
"\n",
- " # Compute auxilliary data.\n",
+ " # Compute auxiliary data.\n",
" widths = [x[0] for x in orders]\n",
" colors = [x[1] for x in orders]\n",
" max_capacity = max(capacities)\n",
" loss_array = [\n",
- " min(x for x in capacities if x >= c) - c\n",
- " for c in range(max_capacity + 1)\n",
+ " min(x for x in capacities if x >= c) - c for c in range(max_capacity +\n",
+ " 1)\n",
" ]\n",
"\n",
" ### Model problem.\n",
@@ -784,21 +767,22 @@
" unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,\n",
" loss_array)\n",
" # Sort slab by descending load/loss. Remove duplicates.\n",
- " valid_slabs = sorted(\n",
- " unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])\n",
+ " valid_slabs = sorted(unsorted_valid_slabs,\n",
+ " key=lambda c: 1000 * c[-1] + c[-2])\n",
" all_valid_slabs = range(len(valid_slabs))\n",
"\n",
" # create model and decision variables.\n",
" start_time = time.time()\n",
" solver = pywraplp.Solver('Steel',\n",
- " pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)\n",
+ " pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)\n",
" selected = [\n",
" solver.IntVar(0.0, 1.0, 'selected_%i' % i) for i in all_valid_slabs\n",
" ]\n",
"\n",
" for order in all_orders:\n",
" solver.Add(\n",
- " sum(selected[i] for i in all_valid_slabs\n",
+ " sum(selected[i]\n",
+ " for i in all_valid_slabs\n",
" if valid_slabs[i][order]) == 1)\n",
"\n",
" # Redundant constraint (sum of loads == sum of widths).\n",
@@ -820,16 +804,14 @@
" print('No solution')\n",
"\n",
"\n",
- "'''Main function'''\n",
- "if args.solver == 'sat':\n",
- " steel_mill_slab(args.problem, args.break_symmetries, args.output_proto)\n",
- "elif args.solver == 'sat_table':\n",
- " steel_mill_slab_with_valid_slabs(args.problem, args.break_symmetries,\n",
- " args.output_proto)\n",
- "elif args.solver == 'sat_column':\n",
- " steel_mill_slab_with_column_generation(args.problem, args.output_proto)\n",
+ "if FLAGS.solver == 'sat':\n",
+ " steel_mill_slab(FLAGS.problem, FLAGS.break_symmetries)\n",
+ "elif FLAGS.solver == 'sat_table':\n",
+ " steel_mill_slab_with_valid_slabs(FLAGS.problem, FLAGS.break_symmetries)\n",
+ "elif FLAGS.solver == 'sat_column':\n",
+ " steel_mill_slab_with_column_generation(FLAGS.problem)\n",
"else: # 'mip_column'\n",
- " steel_mill_slab_with_mip_column_generation(args.problem)\n",
+ " steel_mill_slab_with_mip_column_generation(FLAGS.problem)\n",
"\n"
]
}
diff --git a/examples/notebook/linear_solver/bin_packing_mip.ipynb b/examples/notebook/linear_solver/bin_packing_mip.ipynb
index 227b6f3059..2d0db0e172 100644
--- a/examples/notebook/linear_solver/bin_packing_mip.ipynb
+++ b/examples/notebook/linear_solver/bin_packing_mip.ipynb
@@ -83,7 +83,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.linear_solver import pywraplp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -99,7 +98,6 @@
" data['bin_capacity'] = 100\n",
" return data\n",
"\n",
- "\n",
"# [END data_model]\n",
"\n",
"\n",
diff --git a/examples/notebook/linear_solver/linear_programming_example.ipynb b/examples/notebook/linear_solver/linear_programming_example.ipynb
index 85e5c39e85..8020f8ea6d 100644
--- a/examples/notebook/linear_solver/linear_programming_example.ipynb
+++ b/examples/notebook/linear_solver/linear_programming_example.ipynb
@@ -83,7 +83,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.linear_solver import pywraplp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
diff --git a/examples/notebook/linear_solver/mip_var_array.ipynb b/examples/notebook/linear_solver/mip_var_array.ipynb
index 25aaf19556..272e65611d 100644
--- a/examples/notebook/linear_solver/mip_var_array.ipynb
+++ b/examples/notebook/linear_solver/mip_var_array.ipynb
@@ -83,7 +83,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.linear_solver import pywraplp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -104,7 +103,6 @@
" data['num_constraints'] = 4\n",
" return data\n",
"\n",
- "\n",
"# [END data_model]\n",
"\n",
"\n",
diff --git a/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb b/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb
index a6e0a0f258..6c1dd67a91 100644
--- a/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb
+++ b/examples/notebook/linear_solver/multiple_knapsack_mip.ipynb
@@ -83,7 +83,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.linear_solver import pywraplp\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -103,7 +102,6 @@
" data['bin_capacities'] = [100, 100, 100, 100, 100]\n",
" return data\n",
"\n",
- "\n",
"# [END data_model]\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/binpacking_problem_sat.ipynb b/examples/notebook/sat/binpacking_problem_sat.ipynb
index 912fa5901f..edbb6c239a 100644
--- a/examples/notebook/sat/binpacking_problem_sat.ipynb
+++ b/examples/notebook/sat/binpacking_problem_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Solves a binpacking problem using the CP-SAT solver.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/bool_or_sample_sat.ipynb b/examples/notebook/sat/bool_or_sample_sat.ipynb
index 570e8b1353..62772cda82 100644
--- a/examples/notebook/sat/bool_or_sample_sat.ipynb
+++ b/examples/notebook/sat/bool_or_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrates a simple Boolean constraint.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/boolean_product_sample_sat.ipynb b/examples/notebook/sat/boolean_product_sample_sat.ipynb
index 89dae1846e..444a648990 100644
--- a/examples/notebook/sat/boolean_product_sample_sat.ipynb
+++ b/examples/notebook/sat/boolean_product_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample that encodes the product of two Boolean variables.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/channeling_sample_sat.ipynb b/examples/notebook/sat/channeling_sample_sat.ipynb
index f62cfbc803..5f17927669 100644
--- a/examples/notebook/sat/channeling_sample_sat.ipynb
+++ b/examples/notebook/sat/channeling_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Link integer constraints together.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb
index 7fd069a85e..12a0bf2744 100644
--- a/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb
+++ b/examples/notebook/sat/earliness_tardiness_cost_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Encodes an convex piecewise linear function.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/interval_sample_sat.ipynb b/examples/notebook/sat/interval_sample_sat.ipynb
index ce0dbc64ac..00e77f5d01 100644
--- a/examples/notebook/sat/interval_sample_sat.ipynb
+++ b/examples/notebook/sat/interval_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrates how to build an interval.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/literal_sample_sat.ipynb b/examples/notebook/sat/literal_sample_sat.ipynb
index 1deff00c52..8ee48b1bab 100644
--- a/examples/notebook/sat/literal_sample_sat.ipynb
+++ b/examples/notebook/sat/literal_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrate Boolean variable and literals.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/minimal_jobshop_sat.ipynb b/examples/notebook/sat/minimal_jobshop_sat.ipynb
index 5bf3b64b46..d627a86109 100644
--- a/examples/notebook/sat/minimal_jobshop_sat.ipynb
+++ b/examples/notebook/sat/minimal_jobshop_sat.ipynb
@@ -82,7 +82,6 @@
"\"\"\"Minimal jobshop example.\"\"\"\n",
"\n",
"# [START program]\n",
- "\n",
"import collections\n",
"\n",
"# [START model]\n",
diff --git a/examples/notebook/sat/multiple_knapsack_sat.ipynb b/examples/notebook/sat/multiple_knapsack_sat.ipynb
index 7ad99528b6..b54452eeec 100644
--- a/examples/notebook/sat/multiple_knapsack_sat.ipynb
+++ b/examples/notebook/sat/multiple_knapsack_sat.ipynb
@@ -83,9 +83,7 @@
"\"\"\"Solves a multiple knapsack problem using the CP-SAT solver.\"\"\"\n",
"\n",
"# [START import]\n",
- "\n",
"from ortools.sat.python import cp_model\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -104,7 +102,6 @@
" data['all_bins'] = range(data['num_bins'])\n",
" return data\n",
"\n",
- "\n",
"# [END data_model]\n",
"\n",
"\n",
@@ -130,7 +127,6 @@
" print('Total packed weight:', total_weight)\n",
" print('Total packed value:', total_value)\n",
"\n",
- "\n",
"# [END solution_printer]\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/no_overlap_sample_sat.ipynb b/examples/notebook/sat/no_overlap_sample_sat.ipynb
index 7ed4669ac2..3bf88bc104 100644
--- a/examples/notebook/sat/no_overlap_sample_sat.ipynb
+++ b/examples/notebook/sat/no_overlap_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrate how to build a NoOverlap constraint.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/nurses_sat.ipynb b/examples/notebook/sat/nurses_sat.ipynb
index 598d83f792..008b33c231 100644
--- a/examples/notebook/sat/nurses_sat.ipynb
+++ b/examples/notebook/sat/nurses_sat.ipynb
@@ -84,7 +84,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.sat.python import cp_model\n",
- "\n",
"# [END import]\n",
"\n",
"\n",
@@ -120,7 +119,6 @@
" def solution_count(self):\n",
" return self._solution_count\n",
"\n",
- "\n",
"# [END solution_printer]\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/optional_interval_sample_sat.ipynb b/examples/notebook/sat/optional_interval_sample_sat.ipynb
index 993fb7f4ef..956ea3060b 100644
--- a/examples/notebook/sat/optional_interval_sample_sat.ipynb
+++ b/examples/notebook/sat/optional_interval_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrates how to build an optional interval.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb
index 50636a369d..a7ceec3414 100644
--- a/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb
+++ b/examples/notebook/sat/rabbits_and_pheasants_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Rabbits and Pheasants quizz.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/ranking_sample_sat.ipynb b/examples/notebook/sat/ranking_sample_sat.ipynb
index 58aa77799e..985b64e638 100644
--- a/examples/notebook/sat/ranking_sample_sat.ipynb
+++ b/examples/notebook/sat/ranking_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrates how to rank intervals.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/reified_sample_sat.ipynb b/examples/notebook/sat/reified_sample_sat.ipynb
index 21bb868774..9e6cbd4988 100644
--- a/examples/notebook/sat/reified_sample_sat.ipynb
+++ b/examples/notebook/sat/reified_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Simple model with a reified constraint.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb
index 57eb4ba96b..57f8330777 100644
--- a/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb
+++ b/examples/notebook/sat/scheduling_with_calendar_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample to demonstrate how an interval can span across a break.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb
index 72304dfde3..dba2330b1e 100644
--- a/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb
+++ b/examples/notebook/sat/search_for_all_solutions_sample_sat.ipynb
@@ -82,7 +82,6 @@
"\"\"\"Code sample that solves a model and displays all solutions.\"\"\"\n",
"\n",
"# [START program]\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/simple_sat_program.ipynb b/examples/notebook/sat/simple_sat_program.ipynb
index dd01be64f8..176bf92c68 100644
--- a/examples/notebook/sat/simple_sat_program.ipynb
+++ b/examples/notebook/sat/simple_sat_program.ipynb
@@ -82,7 +82,6 @@
"\"\"\"Simple solve.\"\"\"\n",
"\n",
"# [START program]\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/solution_hinting_sample_sat.ipynb b/examples/notebook/sat/solution_hinting_sample_sat.ipynb
index 9c630caa9d..896f5ebaea 100644
--- a/examples/notebook/sat/solution_hinting_sample_sat.ipynb
+++ b/examples/notebook/sat/solution_hinting_sample_sat.ipynb
@@ -82,7 +82,6 @@
"\"\"\"Code sample that solves a model using solution hinting.\"\"\"\n",
"\n",
"# [START program]\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb
index a35d8da88b..bc149f7b2e 100644
--- a/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb
+++ b/examples/notebook/sat/solve_and_print_intermediate_solutions_sample_sat.ipynb
@@ -82,7 +82,6 @@
"\"\"\"Solves an optimization problem and displays all intermediate solutions.\"\"\"\n",
"\n",
"# [START program]\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb
index 3a0e022001..52838f980b 100644
--- a/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb
+++ b/examples/notebook/sat/solve_with_time_limit_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Solves a problem with a time limit.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/step_function_sample_sat.ipynb b/examples/notebook/sat/step_function_sample_sat.ipynb
index df9ebe1693..96e8fe1a31 100644
--- a/examples/notebook/sat/step_function_sample_sat.ipynb
+++ b/examples/notebook/sat/step_function_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Implements a step function.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
diff --git a/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb
index 161185d552..8e702eb2b8 100644
--- a/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb
+++ b/examples/notebook/sat/stop_after_n_solutions_sample_sat.ipynb
@@ -81,7 +81,6 @@
"# limitations under the License.\n",
"\"\"\"Code sample that solves a model and displays a small number of solutions.\"\"\"\n",
"\n",
- "\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",