Update Notebooks

This commit is contained in:
Mizux Seiha
2020-12-07 09:51:28 +01:00
parent 051a08b307
commit 6c95bc794a
39 changed files with 682 additions and 290 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/master/examples/notebook/constraint_solver/vrptw_store_solution_data.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/master/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/master/ortools/constraint_solver/samples/vrptw_store_solution_data.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/master/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,7 +83,6 @@
"# [START program]\n",
"# [START import]\n",
"from ortools.linear_solver import pywraplp\n",
"\n",
"# [END import]\n",
"\n",
"\n",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,6 @@
"\"\"\"Minimal jobshop example.\"\"\"\n",
"\n",
"# [START program]\n",
"\n",
"import collections\n",
"\n",
"# [START model]\n",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,6 @@
"\"\"\"Simple solve.\"\"\"\n",
"\n",
"# [START program]\n",
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",

View File

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

View File

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

View File

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

View File

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

View File

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