update notebooks

This commit is contained in:
Mizux Seiha
2024-04-30 21:30:04 +02:00
parent b5917aab18
commit 32d962c0aa
47 changed files with 1859 additions and 563 deletions

View File

@@ -74,31 +74,31 @@
"source": [
"Capacitated Vehicle Routing Problem (CVRP).\n",
"\n",
" This is a sample using the routing library python wrapper to solve a CVRP\n",
" problem while allowing multiple trips, i.e., vehicles can return to a depot\n",
" to reset their load (\"reload\").\n",
"This is a sample using the routing library python wrapper to solve a CVRP\n",
"problem while allowing multiple trips, i.e., vehicles can return to a depot\n",
"to reset their load (\"reload\").\n",
"\n",
" A description of the CVRP problem can be found here:\n",
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"A description of the CVRP problem can be found here:\n",
"http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"\n",
" Distances are in meters.\n",
"Distances are in meters.\n",
"\n",
" In order to implement multiple trips, new nodes are introduced at the same\n",
" locations of the original depots. These additional nodes can be dropped\n",
" from the schedule at 0 cost.\n",
"In order to implement multiple trips, new nodes are introduced at the same\n",
"locations of the original depots. These additional nodes can be dropped\n",
"from the schedule at 0 cost.\n",
"\n",
" The max_slack parameter associated to the capacity constraints of all nodes\n",
" can be set to be the maximum of the vehicles' capacities, rather than 0 like\n",
" in a traditional CVRP. Slack is required since before a solution is found,\n",
" it is not known how much capacity will be transferred at the new nodes. For\n",
" all the other (original) nodes, the slack is then re-set to 0.\n",
"The max_slack parameter associated to the capacity constraints of all nodes\n",
"can be set to be the maximum of the vehicles' capacities, rather than 0 like\n",
"in a traditional CVRP. Slack is required since before a solution is found,\n",
"it is not known how much capacity will be transferred at the new nodes. For\n",
"all the other (original) nodes, the slack is then re-set to 0.\n",
"\n",
" The above two considerations are implemented in `add_capacity_constraints()`.\n",
"The above two considerations are implemented in `add_capacity_constraints()`.\n",
"\n",
" Last, it is useful to set a large distance between the initial depot and the\n",
" new nodes introduced, to avoid schedules having spurious transits through\n",
" those new nodes unless it's necessary to reload. This consideration is taken\n",
" into account in `create_distance_evaluator()`.\n",
"Last, it is useful to set a large distance between the initial depot and the\n",
"new nodes introduced, to avoid schedules having spurious transits through\n",
"those new nodes unless it's necessary to reload. This consideration is taken\n",
"into account in `create_distance_evaluator()`.\n",
"\n"
]
},
@@ -112,7 +112,7 @@
"from functools import partial\n",
"\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"###########################\n",
@@ -145,52 +145,69 @@
" (3, 7),\n",
" (6, 7),\n",
" (0, 8),\n",
" (7, 8)\n",
" (7, 8),\n",
" ]\n",
" # Compute locations in meters using the block dimension defined as follow\n",
" # Manhattan average block: 750ft x 264ft -> 228m x 80m\n",
" # here we use: 114m x 80m city block\n",
" # src: https://nyti.ms/2GDoRIe 'NY Times: Know Your distance'\n",
" data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]\n",
" data['num_locations'] = len(data['locations'])\n",
" data['demands'] = \\\n",
" [0, # depot\n",
" -_capacity, # unload depot_first\n",
" -_capacity, # unload depot_second\n",
" -_capacity, # unload depot_third\n",
" -_capacity, # unload depot_fourth\n",
" -_capacity, # unload depot_fifth\n",
" 3, 3, # 1, 2\n",
" 3, 4, # 3, 4\n",
" 3, 4, # 5, 6\n",
" 8, 8, # 7, 8\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",
" data['time_windows'] = \\\n",
" [(0, 0), # depot\n",
" (0, 1000), # unload depot_first\n",
" (0, 1000), # unload depot_second\n",
" (0, 1000), # unload depot_third\n",
" (0, 1000), # unload depot_fourth\n",
" (0, 1000), # unload depot_fifth\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",
" data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in _locations]\n",
" data[\"num_locations\"] = len(data[\"locations\"])\n",
" data[\"demands\"] = [\n",
" 0, # depot\n",
" -_capacity, # unload depot_first\n",
" -_capacity, # unload depot_second\n",
" -_capacity, # unload depot_third\n",
" -_capacity, # unload depot_fourth\n",
" -_capacity, # unload depot_fifth\n",
" 3,\n",
" 3, # 1, 2\n",
" 3,\n",
" 4, # 3, 4\n",
" 3,\n",
" 4, # 5, 6\n",
" 8,\n",
" 8, # 7, 8\n",
" 3,\n",
" 3, # 9,10\n",
" 3,\n",
" 3, # 11,12\n",
" 4,\n",
" 4, # 13, 14\n",
" 8,\n",
" 8,\n",
" ] # 15, 16\n",
" data[\"time_per_demand_unit\"] = 5 # 5 minutes/unit\n",
" data[\"time_windows\"] = [\n",
" (0, 0), # depot\n",
" (0, 1000), # unload depot_first\n",
" (0, 1000), # unload depot_second\n",
" (0, 1000), # unload depot_third\n",
" (0, 1000), # unload depot_fourth\n",
" (0, 1000), # unload depot_fifth\n",
" (75, 850),\n",
" (75, 850), # 1, 2\n",
" (60, 700),\n",
" (45, 550), # 3, 4\n",
" (0, 800),\n",
" (50, 600), # 5, 6\n",
" (0, 1000),\n",
" (10, 200), # 7, 8\n",
" (0, 1000),\n",
" (75, 850), # 9, 10\n",
" (85, 950),\n",
" (5, 150), # 11, 12\n",
" (15, 250),\n",
" (10, 200), # 13, 14\n",
" (45, 550),\n",
" (30, 400),\n",
" ] # 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[\"vehicle_speed\"] = 5 * 60 / 3.6 # Travel speed: 5km/h to convert in m/min\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
@@ -199,30 +216,29 @@
"#######################\n",
"def manhattan_distance(position_1, position_2):\n",
" \"\"\"Computes the Manhattan distance between two points\"\"\"\n",
" return (abs(position_1[0] - position_2[0]) +\n",
" abs(position_1[1] - position_2[1]))\n",
" return abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])\n",
"\n",
"\n",
"def create_distance_evaluator(data):\n",
" \"\"\"Creates callback to return distance between points.\"\"\"\n",
" _distances = {}\n",
" # precompute distance between location to have distance callback in O(1)\n",
" for from_node in range(data['num_locations']):\n",
" for from_node in range(data[\"num_locations\"]):\n",
" _distances[from_node] = {}\n",
" for to_node in range(data['num_locations']):\n",
" 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",
" _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",
" _distances[from_node][to_node] = manhattan_distance(\n",
" data[\"locations\"][from_node], data[\"locations\"][to_node]\n",
" )\n",
"\n",
" def distance_evaluator(manager, from_node, to_node):\n",
" \"\"\"Returns the manhattan distance between the two nodes\"\"\"\n",
" return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(\n",
" to_node)]\n",
" return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n",
"\n",
" return distance_evaluator\n",
"\n",
@@ -230,13 +246,14 @@
"def add_distance_dimension(routing, manager, data, distance_evaluator_index):\n",
" \"\"\"Add Global Span constraint\"\"\"\n",
" del manager\n",
" distance = 'Distance'\n",
" distance = \"Distance\"\n",
" routing.AddDimension(\n",
" distance_evaluator_index,\n",
" 0, # null slack\n",
" data['vehicle_max_distance'], # maximum distance per vehicle\n",
" data[\"vehicle_max_distance\"], # maximum distance per vehicle\n",
" True, # start cumul to zero\n",
" distance)\n",
" distance,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(distance)\n",
" # Try to minimize the max distance among vehicles.\n",
" # /!\\ It doesn't mean the standard deviation is minimized\n",
@@ -245,7 +262,7 @@
"\n",
"def create_demand_evaluator(data):\n",
" \"\"\"Creates callback to get demands at each location.\"\"\"\n",
" _demands = data['demands']\n",
" _demands = data[\"demands\"]\n",
"\n",
" def demand_evaluator(manager, from_node):\n",
" \"\"\"Returns the demand of the current node\"\"\"\n",
@@ -256,14 +273,15 @@
"\n",
"def add_capacity_constraints(routing, manager, data, demand_evaluator_index):\n",
" \"\"\"Adds capacity constraint\"\"\"\n",
" vehicle_capacity = data['vehicle_capacity']\n",
" capacity = 'Capacity'\n",
" vehicle_capacity = data[\"vehicle_capacity\"]\n",
" capacity = \"Capacity\"\n",
" routing.AddDimension(\n",
" demand_evaluator_index,\n",
" vehicle_capacity,\n",
" vehicle_capacity,\n",
" True, # start cumul to zero\n",
" capacity)\n",
" capacity,\n",
" )\n",
"\n",
" # Add Slack for reseting to zero unload depot nodes.\n",
" # e.g. vehicle with load 10/15 arrives at node 1 (depot unload)\n",
@@ -275,7 +293,7 @@
" routing.AddDisjunction([node_index], 0)\n",
"\n",
" # Allow to drop regular node with a cost.\n",
" for node in range(6, len(data['demands'])):\n",
" for node in range(6, len(data[\"demands\"])):\n",
" node_index = manager.NodeToIndex(node)\n",
" capacity_dimension.SlackVar(node_index).SetValue(0)\n",
" routing.AddDisjunction([node_index], 100_000)\n",
@@ -286,52 +304,56 @@
"\n",
" def service_time(data, node):\n",
" \"\"\"Gets the service time for the specified location.\"\"\"\n",
" return abs(data['demands'][node]) * data['time_per_demand_unit']\n",
" return abs(data[\"demands\"][node]) * data[\"time_per_demand_unit\"]\n",
"\n",
" def travel_time(data, from_node, to_node):\n",
" \"\"\"Gets the travel times between two locations.\"\"\"\n",
" if from_node == to_node:\n",
" travel_time = 0\n",
" else:\n",
" travel_time = manhattan_distance(\n",
" data['locations'][from_node],\n",
" data['locations'][to_node]) / data['vehicle_speed']\n",
" travel_time = (\n",
" manhattan_distance(\n",
" data[\"locations\"][from_node], data[\"locations\"][to_node]\n",
" )\n",
" / data[\"vehicle_speed\"]\n",
" )\n",
" return travel_time\n",
"\n",
" _total_time = {}\n",
" # precompute total time to have time callback in O(1)\n",
" for from_node in range(data['num_locations']):\n",
" for from_node in range(data[\"num_locations\"]):\n",
" _total_time[from_node] = {}\n",
" for to_node in range(data['num_locations']):\n",
" for to_node in range(data[\"num_locations\"]):\n",
" if from_node == to_node:\n",
" _total_time[from_node][to_node] = 0\n",
" else:\n",
" _total_time[from_node][to_node] = int(\n",
" service_time(data, from_node) +\n",
" travel_time(data, from_node, to_node))\n",
" service_time(data, from_node)\n",
" + travel_time(data, from_node, to_node)\n",
" )\n",
"\n",
" def time_evaluator(manager, from_node, to_node):\n",
" \"\"\"Returns the total time between the two nodes\"\"\"\n",
" return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(\n",
" to_node)]\n",
" return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(to_node)]\n",
"\n",
" return time_evaluator\n",
"\n",
"\n",
"def add_time_window_constraints(routing, manager, data, time_evaluator):\n",
" \"\"\"Add Time windows constraint\"\"\"\n",
" time = 'Time'\n",
" max_time = data['vehicle_max_time']\n",
" time = \"Time\"\n",
" max_time = data[\"vehicle_max_time\"]\n",
" routing.AddDimension(\n",
" time_evaluator,\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,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot\n",
" # and 'copy' the slack var in the solution object (aka Assignment) to print it\n",
" for location_idx, time_window in enumerate(data['time_windows']):\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",
@@ -339,70 +361,73 @@
" routing.AddToAssignment(time_dimension.SlackVar(index))\n",
" # Add time window constraints for each vehicle start node\n",
" # and 'copy' the slack var in the solution object (aka Assignment) to print it\n",
" for vehicle_id in range(data['num_vehicles']):\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",
" time_dimension.CumulVar(index).SetRange(\n",
" data[\"time_windows\"][0][0], data[\"time_windows\"][0][1]\n",
" )\n",
" routing.AddToAssignment(time_dimension.SlackVar(index))\n",
" # Warning: Slack var is not defined for vehicle's end node\n",
" #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))\n",
" # routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))\n",
"\n",
"\n",
"###########\n",
"# Printer #\n",
"###########\n",
"def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals\n",
"def print_solution(\n",
" data, manager, routing, assignment\n",
"): # pylint:disable=too-many-locals\n",
" \"\"\"Prints assignment on console\"\"\"\n",
" print(f'Objective: {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",
" capacity_dimension = routing.GetDimensionOrDie(\"Capacity\")\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" dropped = []\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(f'dropped orders: {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",
" print(f\"dropped reload stations: {dropped}\")\n",
"\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\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",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f' {manager.IndexToNode(index)} '\n",
" f'Load({assignment.Min(load_var)}) '\n",
" f'Time({assignment.Min(time_var)},{assignment.Max(time_var)}) ->'\n",
" f\" {manager.IndexToNode(index)} \"\n",
" f\"Load({assignment.Min(load_var)}) \"\n",
" f\"Time({assignment.Min(time_var)},{assignment.Max(time_var)}) ->\"\n",
" )\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" distance += routing.GetArcCostForVehicle(previous_index, index,\n",
" vehicle_id)\n",
" distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)\n",
" load_var = capacity_dimension.CumulVar(index)\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += (\n",
" f' {manager.IndexToNode(index)} '\n",
" f'Load({assignment.Min(load_var)}) '\n",
" f'Time({assignment.Min(time_var)},{assignment.Max(time_var)})\\n')\n",
" plan_output += f'Distance of the route: {distance}m\\n'\n",
" plan_output += f'Load of the route: {assignment.Min(load_var)}\\n'\n",
" plan_output += f'Time of the route: {assignment.Min(time_var)}min\\n'\n",
" f\" {manager.IndexToNode(index)} \"\n",
" f\"Load({assignment.Min(load_var)}) \"\n",
" f\"Time({assignment.Min(time_var)},{assignment.Max(time_var)})\\n\"\n",
" )\n",
" plan_output += f\"Distance of the route: {distance}m\\n\"\n",
" plan_output += f\"Load of the route: {assignment.Min(load_var)}\\n\"\n",
" plan_output += f\"Time of the route: {assignment.Min(time_var)}min\\n\"\n",
" print(plan_output)\n",
" total_distance += distance\n",
" total_load += assignment.Min(load_var)\n",
" total_time += assignment.Min(time_var)\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f'Total Load of all routes: {total_load}')\n",
" print(f'Total Time of all routes: {total_time}min')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
" print(f\"Total Load of all routes: {total_load}\")\n",
" print(f\"Total Time of all routes: {total_time}min\")\n",
"\n",
"\n",
"########\n",
@@ -414,15 +439,17 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager\n",
" manager = pywrapcp.RoutingIndexManager(data['num_locations'],\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" data[\"num_locations\"], data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
" # Define weight of each edge\n",
" distance_evaluator_index = routing.RegisterTransitCallback(\n",
" partial(create_distance_evaluator(data), manager))\n",
" partial(create_distance_evaluator(data), manager)\n",
" )\n",
" routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)\n",
"\n",
" # Add Distance constraint to minimize the longuest route\n",
@@ -430,20 +457,24 @@
"\n",
" # Add Capacity constraint\n",
" demand_evaluator_index = routing.RegisterUnaryTransitCallback(\n",
" partial(create_demand_evaluator(data), manager))\n",
" partial(create_demand_evaluator(data), manager)\n",
" )\n",
" add_capacity_constraints(routing, manager, data, demand_evaluator_index)\n",
"\n",
" # Add Time Window constraint\n",
" time_evaluator_index = routing.RegisterTransitCallback(\n",
" partial(create_time_evaluator(data), manager))\n",
" partial(create_time_evaluator(data), manager)\n",
" )\n",
" add_time_window_constraints(routing, manager, data, time_evaluator_index)\n",
"\n",
" # Setting first solution heuristic (cheapest addition).\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" ) # pylint: disable=no-member\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(3)\n",
"\n",
" # Solve the problem.\n",

View File

@@ -75,12 +75,12 @@
"\n",
"Capacitated Vehicle Routing Problem with Time Windows (CVRPTW).\n",
"\n",
" This is a sample using the routing library python wrapper to solve a CVRPTW\n",
" problem.\n",
" A description of the problem can be found here:\n",
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"This is a sample using the routing library python wrapper to solve a CVRPTW\n",
"problem.\n",
"A description of the problem can be found here:\n",
"http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"\n",
" Distances are in meters and time in minutes.\n",
"Distances are in meters and time in minutes.\n",
"\n"
]
},
@@ -92,8 +92,8 @@
"outputs": [],
"source": [
"import functools\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -386,7 +386,11 @@
" vehicle_break = data[\"breaks\"][v]\n",
" break_intervals[v] = [\n",
" routing.solver().FixedDurationIntervalVar(\n",
" 15, 100, vehicle_break[0], vehicle_break[1], f\"Break for vehicle {v}\"\n",
" 15,\n",
" 100,\n",
" vehicle_break[0],\n",
" vehicle_break[1],\n",
" f\"Break for vehicle {v}\",\n",
" )\n",
" ]\n",
" time_dimension.SetBreakIntervalsOfVehicle(\n",
@@ -396,7 +400,7 @@
" # Setting first solution heuristic (cheapest addition).\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" ) # pylint: disable=no-member\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def main():\n",
@@ -116,7 +116,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" ) # pylint: disable=no-member\n",
"\n",
" # Solve the problem.\n",

View File

@@ -87,8 +87,11 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy\n",
"RoutingSearchStatus = enums_pb2.RoutingSearchStatus\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -140,16 +143,24 @@
" return distance_callback\n",
"\n",
"\n",
"def print_solution(manager, routing, assignment):\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" status = routing.status()\n",
" print(f\"Status: {RoutingSearchStatus.Value.Name(status)}\")\n",
" if (\n",
" status != RoutingSearchStatus.ROUTING_OPTIMAL\n",
" and status != RoutingSearchStatus.ROUTING_SUCCESS\n",
" ):\n",
" print(\"No solution found!\")\n",
" return\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" index = routing.Start(0)\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
@@ -178,16 +189,13 @@
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.first_solution_strategy = FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
"\n",
" # Solve the problem.\n",
" assignment = routing.SolveWithParameters(search_parameters)\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
"\n",
" # Print solution on console.\n",
" if assignment:\n",
" print_solution(manager, routing, assignment)\n",
" print_solution(manager, routing, solution)\n",
"\n",
"\n",
"main()\n",

View File

@@ -84,8 +84,8 @@
"outputs": [],
"source": [
"import math\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -210,7 +210,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -155,7 +155,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -161,7 +161,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -75,12 +75,12 @@
"\n",
"Simple Vehicles Routing Problem (VRP).\n",
"\n",
" This is a sample using the routing library python wrapper to solve a VRP\n",
" problem.\n",
" A description of the problem can be found here:\n",
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"This is a sample using the routing library python wrapper to solve a VRP\n",
"problem.\n",
"A description of the problem can be found here:\n",
"http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"\n",
" Distances are in meters.\n",
"Distances are in meters.\n",
"\n"
]
},
@@ -91,8 +91,11 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy\n",
"RoutingSearchStatus = enums_pb2.RoutingSearchStatus\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -124,20 +127,28 @@
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" status = routing.status()\n",
" print(f\"Status: {RoutingSearchStatus.Value.Name(status)}\")\n",
" if (\n",
" status != RoutingSearchStatus.ROUTING_OPTIMAL\n",
" and status != RoutingSearchStatus.ROUTING_SUCCESS\n",
" ):\n",
" print(\"No solution found!\")\n",
" return\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
" total_distance = 0\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" for vehicle_index in range(manager.GetNumberOfVehicles()):\n",
" index = routing.Start(vehicle_index)\n",
" plan_output = f\"Route for vehicle {vehicle_index}:\\n\"\n",
" route_distance = 0\n",
" while not routing.IsEnd(index):\n",
" plan_output += f\" {manager.IndexToNode(index)} ->\"\n",
" previous_index = index\n",
" index = solution.Value(routing.NextVar(index))\n",
" route_distance += routing.GetArcCostForVehicle(\n",
" previous_index, index, vehicle_id\n",
" previous_index, index, vehicle_index\n",
" )\n",
" plan_output += f\" {manager.IndexToNode(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
@@ -146,7 +157,6 @@
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" # Instantiate the data problem.\n",
@@ -175,18 +185,13 @@
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.first_solution_strategy = FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
"\n",
" # Solve the problem.\n",
" solution = routing.SolveWithParameters(search_parameters)\n",
"\n",
" # Print solution on console.\n",
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print(\"No solution found !\")\n",
" print_solution(manager, routing, solution)\n",
"\n",
"\n",
"main()\n",

View File

@@ -75,12 +75,12 @@
"\n",
"Vehicle Routing Problem (VRP) with breaks.\n",
"\n",
" This is a sample using the routing library python wrapper to solve a VRP\n",
" problem.\n",
" A description of the problem can be found here:\n",
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"This is a sample using the routing library python wrapper to solve a VRP\n",
"problem.\n",
"A description of the problem can be found here:\n",
"http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"\n",
" Durations are in minutes.\n",
"Durations are in minutes.\n",
"\n"
]
},
@@ -91,8 +91,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -224,10 +224,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(2)\n",

View File

@@ -73,14 +73,15 @@
"metadata": {},
"source": [
"Vehicles Routing Problem (VRP) with breaks relative to the vehicle start time.\n",
" Each vehicles start at T:15min, T:30min, T:45min and T:60min respectively.\n",
"\n",
" Each vehicle must perform a break lasting 5 minutes,\n",
" starting between 25 and 45 minutes after route start.\n",
" e.g. vehicle 2 starting a T:45min must start a 5min breaks\n",
" between [45+25,45+45] i.e. in the range [70, 90].\n",
"Each vehicles start at T:15min, T:30min, T:45min and T:60min respectively.\n",
"\n",
" Durations are in minutes.\n",
"Each vehicle must perform a break lasting 5 minutes,\n",
"starting between 25 and 45 minutes after route start.\n",
"e.g. vehicle 2 starting a T:45min must start a 5min breaks\n",
"between [45+25,45+45] i.e. in the range [70, 90].\n",
"\n",
"Durations are in minutes.\n",
"\n"
]
},
@@ -91,16 +92,17 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data['time_matrix'] = [\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" data[\"time_matrix\"] = [\n",
" [0, 27, 38, 34, 29, 13, 25, 9, 15, 9, 26, 25, 19, 17, 23, 38, 33],\n",
" [27, 0, 34, 15, 9, 25, 36, 17, 34, 37, 54, 29, 24, 33, 50, 43, 60],\n",
" [38, 34, 0, 49, 43, 25, 13, 40, 23, 37, 20, 63, 58, 56, 39, 77, 37],\n",
@@ -120,46 +122,48 @@
" [33, 60, 37, 67, 62, 35, 24, 42, 25, 23, 17, 42, 36, 26, 9, 39, 0],\n",
" ]\n",
" # 15 min of service time\n",
" data['service_time'] = [15] * len(data['time_matrix'])\n",
" data['service_time'][data['depot']] = 0\n",
" assert len(data['time_matrix']) == len(data['service_time'])\n",
" data[\"service_time\"] = [15] * len(data[\"time_matrix\"])\n",
" data[\"service_time\"][data[\"depot\"]] = 0\n",
" assert len(data[\"time_matrix\"]) == len(data[\"service_time\"])\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {solution.ObjectiveValue()}')\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
"\n",
" print('Breaks:')\n",
" print(\"Breaks:\")\n",
" intervals = solution.IntervalVarContainer()\n",
" for i in range(intervals.Size()):\n",
" brk = intervals.Element(i)\n",
" if brk.PerformedValue() == 1:\n",
" print(f'{brk.Var().Name()}: ' +\n",
" f'Start({brk.StartValue()}) Duration({brk.DurationValue()})')\n",
" print(\n",
" f\"{brk.Var().Name()}: \"\n",
" + f\"Start({brk.StartValue()}) Duration({brk.DurationValue()})\"\n",
" )\n",
" else:\n",
" print(f'{brk.Var().Name()}: Unperformed')\n",
" print(f\"{brk.Var().Name()}: Unperformed\")\n",
"\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" total_time = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" while not routing.IsEnd(index):\n",
" time_var = time_dimension.CumulVar(index)\n",
" if routing.IsStart(index):\n",
" start_time = solution.Value(time_var)\n",
" plan_output += f'{manager.IndexToNode(index)} '\n",
" plan_output += f'Time({solution.Value(time_var)}) -> '\n",
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
" plan_output += f\"Time({solution.Value(time_var)}) -> \"\n",
" index = solution.Value(routing.NextVar(index))\n",
" time_var = time_dimension.CumulVar(index)\n",
" plan_output += f'{manager.IndexToNode(index)} '\n",
" plan_output += f'Time({solution.Value(time_var)})'\n",
" plan_output += f\"{manager.IndexToNode(index)} \"\n",
" plan_output += f\"Time({solution.Value(time_var)})\"\n",
" print(plan_output)\n",
" route_time = solution.Value(time_var) - start_time\n",
" print(f'Time of the route: {route_time}min\\n')\n",
" print(f\"Time of the route: {route_time}min\\n\")\n",
" total_time += route_time\n",
" print(f'Total time of all routes: {total_time}min')\n",
" print(f\"Total time of all routes: {total_time}min\")\n",
"\n",
"\n",
"def main():\n",
@@ -168,20 +172,20 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),\n",
" data['num_vehicles'], data['depot'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"time_matrix\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a 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",
" return data[\"time_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -189,13 +193,14 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 10, # need optional waiting time to place break\n",
" 180, # maximum time per vehicle\n",
" False, # Don't force start cumul to zero.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" time_dimension.SetGlobalSpanCostCoefficient(10)\n",
"\n",
@@ -209,27 +214,30 @@
" node_visit_transit = [0] * routing.Size()\n",
" for index in range(routing.Size()):\n",
" node = manager.IndexToNode(index)\n",
" node_visit_transit[index] = data['service_time'][node]\n",
" node_visit_transit[index] = data[\"service_time\"][node]\n",
"\n",
" # Add a break lasting 5 minutes, start between 25 and 45 minutes after route start\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" start_var = time_dimension.CumulVar(routing.Start(v))\n",
" break_start = routing.solver().Sum(\n",
" [routing.solver().IntVar(25, 45), start_var])\n",
" break_start = routing.solver().Sum([routing.solver().IntVar(25, 45), start_var])\n",
"\n",
" break_intervals = [\n",
" routing.solver().FixedDurationIntervalVar(break_start, 5,\n",
" f'Break for vehicle {v}')\n",
" routing.solver().FixedDurationIntervalVar(\n",
" break_start, 5, f\"Break for vehicle {v}\"\n",
" )\n",
" ]\n",
" time_dimension.SetBreakIntervalsOfVehicle(break_intervals, v,\n",
" node_visit_transit)\n",
" time_dimension.SetBreakIntervalsOfVehicle(\n",
" break_intervals, v, node_visit_transit\n",
" )\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(2)\n",
"\n",
@@ -240,7 +248,7 @@
" if solution:\n",
" print_solution(manager, routing, solution)\n",
" else:\n",
" print('No solution found !')\n",
" print(\"No solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -192,10 +192,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -205,10 +205,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",

View File

@@ -75,12 +75,12 @@
"\n",
"Simple Vehicles Routing Problem (VRP).\n",
"\n",
" This is a sample using the routing library python wrapper to solve a VRP\n",
" problem.\n",
" A description of the problem can be found here:\n",
" http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"This is a sample using the routing library python wrapper to solve a VRP\n",
"problem.\n",
"A description of the problem can be found here:\n",
"http://en.wikipedia.org/wiki/Vehicle_routing_problem.\n",
"\n",
" Distances are in meters.\n",
"Distances are in meters.\n",
"\n"
]
},
@@ -91,8 +91,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -146,7 +146,6 @@
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" # Instantiate the data problem.\n",
@@ -188,7 +187,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -146,7 +146,6 @@
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Solve the CVRP problem.\"\"\"\n",
" # Instantiate the data problem.\n",
@@ -188,10 +187,10 @@
" # Close model with the custom search parameters.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(5)\n",
" # When an initial solution is given for search, the model will be closed with\n",

View File

@@ -73,9 +73,9 @@
"metadata": {},
"source": [
"Vehicles Routing Problem (VRP) for delivering items from any suppliers.\n",
"Description:\n",
"Need to deliver some item X and Y at end nodes (at least 11 X and 13 Y).\n",
"Several locations provide them and even few provide both.\n",
"\n",
"Description: Need to deliver some item X and Y at end nodes (at least 11 X and\n",
"13 Y). Several locations provide them and even few provide both.\n",
"\n",
"fleet:\n",
" * vehicles: 2\n",
@@ -93,22 +93,22 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['num_vehicles'] = 2\n",
" data['starts'] = [0] * data['num_vehicles']\n",
" data['ends'] = [1] * data['num_vehicles']\n",
" assert len(data['starts']) == data['num_vehicles']\n",
" assert len(data['ends']) == data['num_vehicles']\n",
" data[\"num_vehicles\"] = 2\n",
" data[\"starts\"] = [0] * data[\"num_vehicles\"]\n",
" data[\"ends\"] = [1] * data[\"num_vehicles\"]\n",
" assert len(data[\"starts\"]) == data[\"num_vehicles\"]\n",
" assert len(data[\"ends\"]) == data[\"num_vehicles\"]\n",
"\n",
" # Need 11 X and 13 Y\n",
" data['providers_x'] = [\n",
" data[\"providers_x\"] = [\n",
" 0, # start\n",
" -11, # end\n",
" 2, # X supply 1\n",
@@ -127,7 +127,7 @@
" 0, # Y supply 5\n",
" 0, # Y supply 6\n",
" ]\n",
" data['providers_y'] = [\n",
" data[\"providers_y\"] = [\n",
" 0, # start\n",
" -13, # ends\n",
" 0, # X supply 1\n",
@@ -146,95 +146,350 @@
" 3, # Y supply 5\n",
" 5, # Y supply 6\n",
" ]\n",
" data['vehicle_capacities_x'] = [15] * data['num_vehicles']\n",
" data['vehicle_capacities_y'] = [15] * data['num_vehicles']\n",
" assert len(data['vehicle_capacities_x']) == data['num_vehicles']\n",
" assert len(data['vehicle_capacities_y']) == data['num_vehicles']\n",
" data['distance_matrix'] = [\n",
" data[\"vehicle_capacities_x\"] = [15] * data[\"num_vehicles\"]\n",
" data[\"vehicle_capacities_y\"] = [15] * data[\"num_vehicles\"]\n",
" assert len(data[\"vehicle_capacities_x\"]) == data[\"num_vehicles\"]\n",
" assert len(data[\"vehicle_capacities_y\"]) == data[\"num_vehicles\"]\n",
" data[\"distance_matrix\"] = [\n",
" [\n",
" 0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,\n",
" 468, 776, 662\n",
" 0,\n",
" 548,\n",
" 776,\n",
" 696,\n",
" 582,\n",
" 274,\n",
" 502,\n",
" 194,\n",
" 308,\n",
" 194,\n",
" 536,\n",
" 502,\n",
" 388,\n",
" 354,\n",
" 468,\n",
" 776,\n",
" 662,\n",
" ],\n",
" [\n",
" 548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,\n",
" 1016, 868, 1210\n",
" 548,\n",
" 0,\n",
" 684,\n",
" 308,\n",
" 194,\n",
" 502,\n",
" 730,\n",
" 354,\n",
" 696,\n",
" 742,\n",
" 1084,\n",
" 594,\n",
" 480,\n",
" 674,\n",
" 1016,\n",
" 868,\n",
" 1210,\n",
" ],\n",
" [\n",
" 776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,\n",
" 1130, 788, 1552, 754\n",
" 776,\n",
" 684,\n",
" 0,\n",
" 992,\n",
" 878,\n",
" 502,\n",
" 274,\n",
" 810,\n",
" 468,\n",
" 742,\n",
" 400,\n",
" 1278,\n",
" 1164,\n",
" 1130,\n",
" 788,\n",
" 1552,\n",
" 754,\n",
" ],\n",
" [\n",
" 696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,\n",
" 1164, 560, 1358\n",
" 696,\n",
" 308,\n",
" 992,\n",
" 0,\n",
" 114,\n",
" 650,\n",
" 878,\n",
" 502,\n",
" 844,\n",
" 890,\n",
" 1232,\n",
" 514,\n",
" 628,\n",
" 822,\n",
" 1164,\n",
" 560,\n",
" 1358,\n",
" ],\n",
" [\n",
" 582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,\n",
" 1050, 674, 1244\n",
" 582,\n",
" 194,\n",
" 878,\n",
" 114,\n",
" 0,\n",
" 536,\n",
" 764,\n",
" 388,\n",
" 730,\n",
" 776,\n",
" 1118,\n",
" 400,\n",
" 514,\n",
" 708,\n",
" 1050,\n",
" 674,\n",
" 1244,\n",
" ],\n",
" [\n",
" 274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,\n",
" 514, 1050, 708\n",
" 274,\n",
" 502,\n",
" 502,\n",
" 650,\n",
" 536,\n",
" 0,\n",
" 228,\n",
" 308,\n",
" 194,\n",
" 240,\n",
" 582,\n",
" 776,\n",
" 662,\n",
" 628,\n",
" 514,\n",
" 1050,\n",
" 708,\n",
" ],\n",
" [\n",
" 502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,\n",
" 514, 1278, 480\n",
" 502,\n",
" 730,\n",
" 274,\n",
" 878,\n",
" 764,\n",
" 228,\n",
" 0,\n",
" 536,\n",
" 194,\n",
" 468,\n",
" 354,\n",
" 1004,\n",
" 890,\n",
" 856,\n",
" 514,\n",
" 1278,\n",
" 480,\n",
" ],\n",
" [\n",
" 194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,\n",
" 662, 742, 856\n",
" 194,\n",
" 354,\n",
" 810,\n",
" 502,\n",
" 388,\n",
" 308,\n",
" 536,\n",
" 0,\n",
" 342,\n",
" 388,\n",
" 730,\n",
" 468,\n",
" 354,\n",
" 320,\n",
" 662,\n",
" 742,\n",
" 856,\n",
" ],\n",
" [\n",
" 308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,\n",
" 320, 1084, 514\n",
" 308,\n",
" 696,\n",
" 468,\n",
" 844,\n",
" 730,\n",
" 194,\n",
" 194,\n",
" 342,\n",
" 0,\n",
" 274,\n",
" 388,\n",
" 810,\n",
" 696,\n",
" 662,\n",
" 320,\n",
" 1084,\n",
" 514,\n",
" ],\n",
" [\n",
" 194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,\n",
" 274, 810, 468\n",
" 194,\n",
" 742,\n",
" 742,\n",
" 890,\n",
" 776,\n",
" 240,\n",
" 468,\n",
" 388,\n",
" 274,\n",
" 0,\n",
" 342,\n",
" 536,\n",
" 422,\n",
" 388,\n",
" 274,\n",
" 810,\n",
" 468,\n",
" ],\n",
" [\n",
" 536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,\n",
" 730, 388, 1152, 354\n",
" 536,\n",
" 1084,\n",
" 400,\n",
" 1232,\n",
" 1118,\n",
" 582,\n",
" 354,\n",
" 730,\n",
" 388,\n",
" 342,\n",
" 0,\n",
" 878,\n",
" 764,\n",
" 730,\n",
" 388,\n",
" 1152,\n",
" 354,\n",
" ],\n",
" [\n",
" 502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,\n",
" 308, 650, 274, 844\n",
" 502,\n",
" 594,\n",
" 1278,\n",
" 514,\n",
" 400,\n",
" 776,\n",
" 1004,\n",
" 468,\n",
" 810,\n",
" 536,\n",
" 878,\n",
" 0,\n",
" 114,\n",
" 308,\n",
" 650,\n",
" 274,\n",
" 844,\n",
" ],\n",
" [\n",
" 388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,\n",
" 536, 388, 730\n",
" 388,\n",
" 480,\n",
" 1164,\n",
" 628,\n",
" 514,\n",
" 662,\n",
" 890,\n",
" 354,\n",
" 696,\n",
" 422,\n",
" 764,\n",
" 114,\n",
" 0,\n",
" 194,\n",
" 536,\n",
" 388,\n",
" 730,\n",
" ],\n",
" [\n",
" 354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,\n",
" 342, 422, 536\n",
" 354,\n",
" 674,\n",
" 1130,\n",
" 822,\n",
" 708,\n",
" 628,\n",
" 856,\n",
" 320,\n",
" 662,\n",
" 388,\n",
" 730,\n",
" 308,\n",
" 194,\n",
" 0,\n",
" 342,\n",
" 422,\n",
" 536,\n",
" ],\n",
" [\n",
" 468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,\n",
" 342, 0, 764, 194\n",
" 468,\n",
" 1016,\n",
" 788,\n",
" 1164,\n",
" 1050,\n",
" 514,\n",
" 514,\n",
" 662,\n",
" 320,\n",
" 274,\n",
" 388,\n",
" 650,\n",
" 536,\n",
" 342,\n",
" 0,\n",
" 764,\n",
" 194,\n",
" ],\n",
" [\n",
" 776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,\n",
" 388, 422, 764, 0, 798\n",
" 776,\n",
" 868,\n",
" 1552,\n",
" 560,\n",
" 674,\n",
" 1050,\n",
" 1278,\n",
" 742,\n",
" 1084,\n",
" 810,\n",
" 1152,\n",
" 274,\n",
" 388,\n",
" 422,\n",
" 764,\n",
" 0,\n",
" 798,\n",
" ],\n",
" [\n",
" 662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,\n",
" 536, 194, 798, 0\n",
" 662,\n",
" 1210,\n",
" 754,\n",
" 1358,\n",
" 1244,\n",
" 708,\n",
" 480,\n",
" 856,\n",
" 514,\n",
" 468,\n",
" 354,\n",
" 844,\n",
" 730,\n",
" 536,\n",
" 194,\n",
" 798,\n",
" 0,\n",
" ],\n",
" ]\n",
" assert len(data['providers_x']) == len(data['distance_matrix'])\n",
" assert len(data['providers_y']) == len(data['distance_matrix'])\n",
" assert len(data[\"providers_x\"]) == len(data[\"distance_matrix\"])\n",
" assert len(data[\"providers_y\"]) == len(data[\"distance_matrix\"])\n",
" return data\n",
"\n",
"\n",
"def print_solution(data, manager, routing, assignment):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" # Display dropped nodes.\n",
" dropped_nodes = 'Dropped nodes:'\n",
" dropped_nodes = \"Dropped nodes:\"\n",
" for node in range(routing.Size()):\n",
" if routing.IsStart(node) or routing.IsEnd(node):\n",
" continue\n",
" if assignment.Value(routing.NextVar(node)) == node:\n",
" dropped_nodes += f' {manager.IndexToNode(node)}'\n",
" dropped_nodes += f\" {manager.IndexToNode(node)}\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
" total_distance = 0\n",
@@ -242,31 +497,31 @@
" total_load_y = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" index = routing.Start(vehicle_id)\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" route_distance = 0\n",
" route_load_x = 0\n",
" route_load_y = 0\n",
" while not routing.IsEnd(index):\n",
" node_index = manager.IndexToNode(index)\n",
" route_load_x += data['providers_x'][node_index]\n",
" route_load_y += data['providers_y'][node_index]\n",
" plan_output += f' {node_index} Load(X:{route_load_x}, Y:{route_load_y}) -> '\n",
" route_load_x += data[\"providers_x\"][node_index]\n",
" route_load_y += data[\"providers_y\"][node_index]\n",
" plan_output += f\" {node_index} Load(X:{route_load_x}, Y:{route_load_y}) -> \"\n",
" previous_index = index\n",
" previous_node_index = node_index\n",
" index = assignment.Value(routing.NextVar(index))\n",
" node_index = manager.IndexToNode(index)\n",
" #route_distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)\n",
" route_distance += data['distance_matrix'][previous_node_index][node_index]\n",
" # route_distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id)\n",
" route_distance += data[\"distance_matrix\"][previous_node_index][node_index]\n",
" node_index = manager.IndexToNode(index)\n",
" plan_output += f' {node_index} Load({route_load_x}, {route_load_y})\\n'\n",
" plan_output += f'Distance of the route: {route_distance}m\\n'\n",
" plan_output += f'Load of the route: X:{route_load_x}, Y:{route_load_y}\\n'\n",
" plan_output += f\" {node_index} Load({route_load_x}, {route_load_y})\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" plan_output += f\"Load of the route: X:{route_load_x}, Y:{route_load_y}\\n\"\n",
" print(plan_output)\n",
" total_distance += route_distance\n",
" total_load_x += route_load_x\n",
" total_load_y += route_load_y\n",
" print(f'Total Distance of all routes: {total_distance}m')\n",
" print(f'Total load of all routes: X:{total_load_x}, Y:{total_load_y}')\n",
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
" print(f\"Total load of all routes: X:{total_load_x}, Y:{total_load_y}\")\n",
"\n",
"\n",
"def main():\n",
@@ -275,9 +530,12 @@
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),\n",
" data['num_vehicles'], data['starts'],\n",
" data['ends'])\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]),\n",
" data[\"num_vehicles\"],\n",
" data[\"starts\"],\n",
" data[\"ends\"],\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -289,7 +547,7 @@
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" to_node = manager.IndexToNode(to_index)\n",
" return data['distance_matrix'][from_node][to_node]\n",
" return data[\"distance_matrix\"][from_node][to_node]\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(distance_callback)\n",
"\n",
@@ -297,13 +555,14 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Distance constraint.\n",
" dimension_name = 'Distance'\n",
" dimension_name = \"Distance\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # no slack\n",
" 2000, # vehicle maximum travel distance\n",
" True, # start cumul to zero\n",
" dimension_name)\n",
" dimension_name,\n",
" )\n",
" distance_dimension = routing.GetDimensionOrDie(dimension_name)\n",
" # Minimize the longest road\n",
" distance_dimension.SetGlobalSpanCostCoefficient(100)\n",
@@ -314,63 +573,67 @@
" \"\"\"Returns the demand of the node.\"\"\"\n",
" # Convert from routing variable Index to demands NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" return data['providers_x'][from_node]\n",
" return data[\"providers_x\"][from_node]\n",
"\n",
" demand_callback_x_index = routing.RegisterUnaryTransitCallback(\n",
" demand_callback_x)\n",
" demand_callback_x_index = routing.RegisterUnaryTransitCallback(demand_callback_x)\n",
" routing.AddDimensionWithVehicleCapacity(\n",
" demand_callback_x_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_capacities_x'], # vehicle maximum capacities\n",
" data[\"vehicle_capacities_x\"], # vehicle maximum capacities\n",
" True, # start cumul to zero\n",
" 'Load_x')\n",
" \"Load_x\",\n",
" )\n",
"\n",
" def demand_callback_y(from_index):\n",
" \"\"\"Returns the demand of the node.\"\"\"\n",
" # Convert from routing variable Index to demands NodeIndex.\n",
" from_node = manager.IndexToNode(from_index)\n",
" return data['providers_y'][from_node]\n",
" return data[\"providers_y\"][from_node]\n",
"\n",
" demand_callback_y_index = routing.RegisterUnaryTransitCallback(\n",
" demand_callback_y)\n",
" demand_callback_y_index = routing.RegisterUnaryTransitCallback(demand_callback_y)\n",
" routing.AddDimensionWithVehicleCapacity(\n",
" demand_callback_y_index,\n",
" 0, # null capacity slack\n",
" data['vehicle_capacities_y'], # vehicle maximum capacities\n",
" data[\"vehicle_capacities_y\"], # vehicle maximum capacities\n",
" True, # start cumul to zero\n",
" 'Load_y')\n",
" \"Load_y\",\n",
" )\n",
"\n",
" # Add constraint at end\n",
" solver = routing.solver()\n",
" load_x_dim = routing.GetDimensionOrDie('Load_x')\n",
" load_y_dim = routing.GetDimensionOrDie('Load_y')\n",
" load_x_dim = routing.GetDimensionOrDie(\"Load_x\")\n",
" load_y_dim = routing.GetDimensionOrDie(\"Load_y\")\n",
" ends = []\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" ends.append(routing.End(v))\n",
"\n",
" node_end = data['ends'][0]\n",
" node_end = data[\"ends\"][0]\n",
" solver.Add(\n",
" solver.Sum([load_x_dim.CumulVar(l)\n",
" for l in ends]) >= -data['providers_x'][node_end])\n",
" solver.Sum([load_x_dim.CumulVar(l) for l in ends])\n",
" >= -data[\"providers_x\"][node_end]\n",
" )\n",
" solver.Add(\n",
" solver.Sum([load_y_dim.CumulVar(l)\n",
" for l in ends]) >= -data['providers_y'][node_end])\n",
" #solver.Add(load_y_dim.CumulVar(end) >= -data['providers_y'][node_end])\n",
" solver.Sum([load_y_dim.CumulVar(l) for l in ends])\n",
" >= -data[\"providers_y\"][node_end]\n",
" )\n",
" # solver.Add(load_y_dim.CumulVar(end) >= -data['providers_y'][node_end])\n",
"\n",
" # Allow to freely drop any nodes.\n",
" penalty = 0\n",
" for node in range(0, len(data['distance_matrix'])):\n",
" if node not in data['starts'] and node not in data['ends']:\n",
" for node in range(0, len(data[\"distance_matrix\"])):\n",
" if node not in data[\"starts\"] and node not in data[\"ends\"]:\n",
" routing.AddDisjunction([manager.NodeToIndex(node)], penalty)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # Sets a time limit; default is 100 milliseconds.\n",
" #search_parameters.log_search = True\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",
" # Solve the problem.\n",
@@ -380,7 +643,7 @@
" if solution:\n",
" print_solution(data, manager, routing, solution)\n",
" else:\n",
" print('no solution found !')\n",
" print(\"no solution found !\")\n",
"\n",
"\n",
"main()\n",

View File

@@ -87,8 +87,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -140,7 +140,6 @@
" return data\n",
"\n",
"\n",
"\n",
"def print_solution(data, manager, routing, solution):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f\"Objective: {solution.ObjectiveValue()}\")\n",
@@ -181,7 +180,6 @@
" print(f\"Maximum of the route distances: {max_route_distance}m\")\n",
"\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Solve the CVRP problem.\"\"\"\n",
" # Instantiate the data problem.\n",
@@ -195,7 +193,6 @@
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
"\n",
" # Create and register a transit callback.\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the distance between the two nodes.\"\"\"\n",
@@ -292,10 +289,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" # search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(5)\n",

View File

@@ -78,11 +78,15 @@
"the relation between nodes and indices.\n",
"\n",
"Things to notice:\n",
"* Since we have two duplicates (node 5 and node 4) solver need 2 extra indices to have an unique index for each vehicle start/stop and locations.\n",
"* Solver needs to \"create\" an index for a vehicle 1 start since solver need an unique start index per vehicle.\n",
"* Since we have two duplicates (node 5 and node 4) solver need 2 extra indices\n",
"to have an unique index for each vehicle start/stop and locations.\n",
"* Solver needs to \"create\" an index for a vehicle 1 start since solver need an\n",
"unique start index per vehicle.\n",
"* All end nodes are moved to the end of the index list aka [15, 16, 17, 18].\n",
"* routing.Size() return the number of node which are not end nodes (here 15 aka [0-14])\n",
"note: using the two properties above, we know that any index in range(routing.Size()) is not a vehicle end node.\n",
"* routing.Size() return the number of node which are not end nodes (here 15 aka\n",
"[0-14])\n",
"note: using the two properties above, we know that any index in\n",
"range(routing.Size()) is not a vehicle end node.\n",
"\n",
"* Since end nodes are moved to the end, their respective \"empty\" node index are\n",
"reused so all locations indices are \"shifted\"\n",
@@ -91,9 +95,11 @@
"e.g. start node 7 mapped to index 4\n",
"\n",
"Takeaway:\n",
"* Allways use routing.Start(), routing.End(), manager.IndexToNode() or manager.NodeToIndex().\n",
"* Allways use routing.Start(), routing.End(), manager.IndexToNode() or\n",
"manager.NodeToIndex().\n",
"* Location node is not necessarily equal to its index.\n",
"* To loop through ALL indices use manager.GetNumberOfIndices() (Python) or manager::num_indices() (C++)\n",
"* To loop through ALL indices use manager.GetNumberOfIndices() (Python) or\n",
"manager::num_indices() (C++)\n",
"\n"
]
},
@@ -104,7 +110,6 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"\n",
"\n",
@@ -119,57 +124,61 @@
" manager = pywrapcp.RoutingIndexManager(locations, vehicles, starts, ends)\n",
" routing = pywrapcp.RoutingModel(manager)\n",
"\n",
" print('Starts/Ends:')\n",
" header = '| |'\n",
" separator = '|---|'\n",
" v_starts = '| start |'\n",
" v_ends = '| end |'\n",
" print(\"Starts/Ends:\")\n",
" header = \"| |\"\n",
" separator = \"|---|\"\n",
" v_starts = \"| start |\"\n",
" v_ends = \"| end |\"\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" header += f' vehicle {v} |'\n",
" separator += '---|'\n",
" v_starts += f' {starts[v]} |'\n",
" v_ends += f' {ends[v]} |'\n",
" header += f\" vehicle {v} |\"\n",
" separator += \"---|\"\n",
" v_starts += f\" {starts[v]} |\"\n",
" v_ends += f\" {ends[v]} |\"\n",
" print(header)\n",
" print(separator)\n",
" print(v_starts)\n",
" print(v_ends)\n",
"\n",
" print('\\nNodes:')\n",
" print(\"\\nNodes:\")\n",
" print(\n",
" '| locations | manager.GetNumberOfNodes | manager.GetNumberOfIndices | routing.nodes | routing.Size |'\n",
" \"| locations | manager.GetNumberOfNodes | manager.GetNumberOfIndices |\"\n",
" \" routing.nodes | routing.Size |\"\n",
" )\n",
" print('|---|---|---|---|---|')\n",
" print(\"|---|---|---|---|---|\")\n",
" print(\n",
" f'| {locations} | {manager.GetNumberOfNodes()} | {manager.GetNumberOfIndices()} | {routing.nodes()} | {routing.Size()} |'\n",
" f\"| {locations} | {manager.GetNumberOfNodes()} |\"\n",
" f\" {manager.GetNumberOfIndices()} | {routing.nodes()} |\"\n",
" f\" {routing.Size()} |\"\n",
" )\n",
"\n",
" print('\\nLocations:')\n",
" print('| node | index | routing.IsStart | routing.IsEnd |')\n",
" print('|---|---|---|---|')\n",
" print(\"\\nLocations:\")\n",
" print(\"| node | index | routing.IsStart | routing.IsEnd |\")\n",
" print(\"|---|---|---|---|\")\n",
" for node in range(manager.GetNumberOfNodes()):\n",
" if node in starts or node in ends:\n",
" continue\n",
" index = manager.NodeToIndex(node)\n",
" print(\n",
" f'| {node} | {index} | {routing.IsStart(index)} | {routing.IsEnd(index)} |'\n",
" f\"| {node} | {index} | {routing.IsStart(index)} |\"\n",
" f\" {routing.IsEnd(index)} |\"\n",
" )\n",
"\n",
" print('\\nStart/End:')\n",
" print(\n",
" '| vehicle | Start/end | node | index | routing.IsStart | routing.IsEnd |'\n",
" )\n",
" print('|---|---|---|---|---|---|')\n",
" print(\"\\nStart/End:\")\n",
" print(\"| vehicle | Start/end | node | index | routing.IsStart | routing.IsEnd |\")\n",
" print(\"|---|---|---|---|---|---|\")\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" start_index = routing.Start(v)\n",
" start_node = manager.IndexToNode(start_index)\n",
" print(\n",
" f'| {v} | start | {start_node} | {start_index} | {routing.IsStart(start_index)} | {routing.IsEnd(start_index)} |'\n",
" f\"| {v} | start | {start_node} | {start_index} |\"\n",
" f\" {routing.IsStart(start_index)} | {routing.IsEnd(start_index)} |\"\n",
" )\n",
" for v in range(manager.GetNumberOfVehicles()):\n",
" end_index = routing.End(v)\n",
" end_node = manager.IndexToNode(end_index)\n",
" print(\n",
" f'| {v} | end | {end_node} | {end_index} | {routing.IsStart(end_index)} | {routing.IsEnd(end_index)} |'\n",
" f\"| {v} | end | {end_node} | {end_index} |\"\n",
" f\" {routing.IsStart(end_index)} | {routing.IsEnd(end_index)} |\"\n",
" )\n",
"\n",
"\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -201,7 +201,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -204,7 +204,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -204,7 +204,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -248,7 +248,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -75,12 +75,12 @@
"\n",
"Simple Vehicles Routing Problem (VRP).\n",
"\n",
" This is a sample using the routing library python wrapper to solve a VRP\n",
" problem.\n",
"This is a sample using the routing library python wrapper to solve a VRP\n",
"problem.\n",
"\n",
" The solver stop after improving its solution 15 times or after 5 seconds.\n",
"The solver stop after improving its solution 15 times or after 5 seconds.\n",
"\n",
" Distances are in meters.\n",
"Distances are in meters.\n",
"\n"
]
},
@@ -93,8 +93,8 @@
"source": [
"import weakref\n",
"\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -127,7 +127,8 @@
"\n",
"\n",
"def print_solution(\n",
" routing_manager: pywrapcp.RoutingIndexManager, routing_model: pywrapcp.RoutingModel\n",
" routing_manager: pywrapcp.RoutingIndexManager,\n",
" routing_model: pywrapcp.RoutingModel,\n",
"):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(\"################\")\n",
@@ -151,7 +152,6 @@
" print(f\"Total Distance of all routes: {total_distance}m\")\n",
"\n",
"\n",
"\n",
"class SolutionCallback:\n",
" \"\"\"Create a solution callback.\"\"\"\n",
"\n",
@@ -169,14 +169,17 @@
" self.objectives = []\n",
"\n",
" def __call__(self):\n",
" objective = int(self._routing_model_ref().CostVar().Value())\n",
" objective = int(\n",
" self._routing_model_ref().CostVar().Value()\n",
" ) # pytype: disable=attribute-error\n",
" if not self.objectives or objective < self.objectives[-1]:\n",
" self.objectives.append(objective)\n",
" print_solution(self._routing_manager_ref(), self._routing_model_ref())\n",
" print_solution(\n",
" self._routing_manager_ref(), self._routing_model_ref()\n",
" ) # pytype: disable=attribute-error\n",
" self._counter += 1\n",
" if self._counter > self._counter_limit:\n",
" self._routing_model_ref().solver().FinishCurrentSearch()\n",
"\n",
" self._routing_model_ref().solver().FinishCurrentSearch() # pytype: disable=attribute-error\n",
"\n",
"\n",
"def main():\n",
@@ -225,10 +228,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(5)\n",
"\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -146,7 +146,10 @@
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" len(data[\"distance_matrix\"]), data[\"num_vehicles\"], data[\"starts\"], data[\"ends\"]\n",
" len(data[\"distance_matrix\"]),\n",
" data[\"num_vehicles\"],\n",
" data[\"starts\"],\n",
" data[\"ends\"],\n",
" )\n",
"\n",
" # Create Routing Model.\n",
@@ -180,7 +183,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -220,7 +220,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -74,19 +74,19 @@
"source": [
"Vehicles Routing Problem (VRP) with Time Window (TW) per vehicle.\n",
"\n",
" All time are in minutes using 0am as origin\n",
" e.g. 8am = 480, 11am = 660, 1pm = 780 ...\n",
"All time are in minutes using 0am as origin\n",
"e.g. 8am = 480, 11am = 660, 1pm = 780 ...\n",
"\n",
" We have 1 depot (0) and 16 locations (1-16).\n",
" We have a fleet of 4 vehicles (0-3) whose working time is [480, 1020] (8am-5pm)\n",
" We have the distance matrix between these locations and depot.\n",
" We have a service time of 25min at each location.\n",
"We have 1 depot (0) and 16 locations (1-16).\n",
"We have a fleet of 4 vehicles (0-3) whose working time is [480, 1020] (8am-5pm)\n",
"We have the distance matrix between these locations and depot.\n",
"We have a service time of 25min at each location.\n",
"\n",
" Locations are duplicated so we can simulate a TW per vehicle.\n",
" location: [01-16] vehicle: 0 TW: [540, 660] (9am-11am)\n",
" location: [17-32] vehicle: 1 TW: [660, 780] (11am-1pm)\n",
" location: [33-48] vehicle: 2 TW: [780, 900] (1pm-3pm)\n",
" location: [49-64] vehicle: 3 TW: [900, 1020] (3pm-5pm)\n",
"Locations are duplicated so we can simulate a TW per vehicle.\n",
"location: [01-16] vehicle: 0 TW: [540, 660] (9am-11am)\n",
"location: [17-32] vehicle: 1 TW: [660, 780] (11am-1pm)\n",
"location: [33-48] vehicle: 2 TW: [780, 900] (1pm-3pm)\n",
"location: [49-64] vehicle: 3 TW: [900, 1020] (3pm-5pm)\n",
"\n"
]
},
@@ -97,14 +97,15 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" data['time_matrix'] = [\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",
@@ -123,16 +124,16 @@
" [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['num_vehicles'] = 4\n",
" data['depot'] = 0\n",
" data[\"num_vehicles\"] = 4\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def print_solution(manager, routing, assignment):\n",
" \"\"\"Prints solution on console.\"\"\"\n",
" print(f'Objective: {assignment.ObjectiveValue()}')\n",
" print(f\"Objective: {assignment.ObjectiveValue()}\")\n",
" # Display dropped nodes.\n",
" dropped_nodes = 'Dropped nodes:'\n",
" dropped_nodes = \"Dropped nodes:\"\n",
" for index in range(routing.Size()):\n",
" if routing.IsStart(index) or routing.IsEnd(index):\n",
" continue\n",
@@ -142,15 +143,15 @@
" original = node\n",
" while original > 16:\n",
" original = original - 16\n",
" dropped_nodes += f' {node}({original})'\n",
" dropped_nodes += f\" {node}({original})\"\n",
" else:\n",
" dropped_nodes += f' {node}'\n",
" dropped_nodes += f\" {node}\"\n",
" print(dropped_nodes)\n",
" # Display routes\n",
" time_dimension = routing.GetDimensionOrDie('Time')\n",
" time_dimension = routing.GetDimensionOrDie(\"Time\")\n",
" total_time = 0\n",
" for vehicle_id in range(manager.GetNumberOfVehicles()):\n",
" plan_output = f'Route for vehicle {vehicle_id}:\\n'\n",
" plan_output = f\"Route for vehicle {vehicle_id}:\\n\"\n",
" index = routing.Start(vehicle_id)\n",
" start_time = 0\n",
" while not routing.IsEnd(index):\n",
@@ -160,22 +161,22 @@
" original = node\n",
" while original > 16:\n",
" original = original - 16\n",
" plan_output += f'{node}({original})'\n",
" plan_output += f\"{node}({original})\"\n",
" else:\n",
" plan_output += f'{node}'\n",
" plan_output += f' Time:{assignment.Value(time_var)} -> '\n",
" plan_output += f\"{node}\"\n",
" plan_output += f\" Time:{assignment.Value(time_var)} -> \"\n",
" if start_time == 0:\n",
" start_time = assignment.Value(time_var)\n",
" index = assignment.Value(routing.NextVar(index))\n",
" time_var = time_dimension.CumulVar(index)\n",
" node = manager.IndexToNode(index)\n",
" plan_output += f'{node} Time:{assignment.Value(time_var)}\\n'\n",
" plan_output += f\"{node} Time:{assignment.Value(time_var)}\\n\"\n",
" end_time = assignment.Value(time_var)\n",
" duration = end_time - start_time\n",
" plan_output += f'Duration of the route:{duration}min\\n'\n",
" plan_output += f\"Duration of the route:{duration}min\\n\"\n",
" print(plan_output)\n",
" total_time += duration\n",
" print(f'Total duration of all routes: {total_time}min')\n",
" print(f\"Total duration of all routes: {total_time}min\")\n",
"\n",
"\n",
"def main():\n",
@@ -185,9 +186,8 @@
"\n",
" # Create the routing index manager.\n",
" manager = pywrapcp.RoutingIndexManager(\n",
" 1 + 16 * 4, # number of locations\n",
" data['num_vehicles'],\n",
" data['depot'])\n",
" 1 + 16 * 4, data[\"num_vehicles\"], data[\"depot\"] # number of locations\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = pywrapcp.RoutingModel(manager)\n",
@@ -207,9 +207,9 @@
" to_node = to_node - 16\n",
" # add service of 25min for each location (except depot)\n",
" service_time = 0\n",
" if from_node != data['depot']:\n",
" if from_node != data[\"depot\"]:\n",
" service_time = 25\n",
" return data['time_matrix'][from_node][to_node] + service_time\n",
" return data[\"time_matrix\"][from_node][to_node] + service_time\n",
"\n",
" transit_callback_index = routing.RegisterTransitCallback(time_callback)\n",
"\n",
@@ -217,17 +217,18 @@
" routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)\n",
"\n",
" # Add Time Windows constraint.\n",
" time = 'Time'\n",
" time = \"Time\"\n",
" routing.AddDimension(\n",
" transit_callback_index,\n",
" 0, # allow waiting time (0 min)\n",
" 1020, # maximum time per vehicle (9 hours)\n",
" False, # Don't force start cumul to zero.\n",
" time)\n",
" time,\n",
" )\n",
" time_dimension = routing.GetDimensionOrDie(time)\n",
" # Add time window constraints for each location except depot.\n",
" for location_idx in range(17):\n",
" if location_idx == data['depot']:\n",
" if location_idx == data[\"depot\"]:\n",
" continue\n",
" # Vehicle 0 location TW: [9am, 11am]\n",
" index_0 = manager.NodeToIndex(location_idx)\n",
@@ -254,30 +255,32 @@
" routing.AddDisjunction([index_0, index_1, index_2, index_3], penalty, 1)\n",
"\n",
" # Add time window constraints for each vehicle start node.\n",
" depot_idx = data['depot']\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" depot_idx = data[\"depot\"]\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.Start(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(480, 1020) # (8am, 5pm)\n",
"\n",
" # Add time window constraints for each vehicle end node.\n",
" depot_idx = data['depot']\n",
" for vehicle_id in range(data['num_vehicles']):\n",
" depot_idx = data[\"depot\"]\n",
" for vehicle_id in range(data[\"num_vehicles\"]):\n",
" index = routing.End(vehicle_id)\n",
" time_dimension.CumulVar(index).SetRange(480, 1020) # (8am, 5pm)\n",
"\n",
" # Instantiate route start and end times to produce feasible times.\n",
" for i in range(data['num_vehicles']):\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",
" time_dimension.CumulVar(routing.Start(i))\n",
" )\n",
" routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",
" # Solve the problem.\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -220,10 +220,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.time_limit.FromSeconds(1)\n",
"\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
@@ -149,10 +149,10 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH\n",
" )\n",
" search_parameters.log_search = True\n",
" search_parameters.time_limit.FromSeconds(5)\n",

View File

@@ -83,8 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.routing import enums_pb2\n",
"\n",
"\n",
"def create_data_model():\n",
@@ -133,7 +133,6 @@
" return data\n",
"\n",
"\n",
"\n",
"def print_solution(routes, cumul_data):\n",
" \"\"\"Print the solution.\"\"\"\n",
" total_time = 0\n",
@@ -169,7 +168,6 @@
" print(route_str)\n",
"\n",
"\n",
"\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",
@@ -185,7 +183,6 @@
" return routes\n",
"\n",
"\n",
"\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",
@@ -207,7 +204,6 @@
" return cumul_data\n",
"\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Solve the VRP with time windows.\"\"\"\n",
" # Instantiate the data problem.\n",
@@ -268,7 +264,7 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
" )\n",
"\n",
" # Solve the problem.\n",

View File

@@ -72,8 +72,8 @@
"id": "description",
"metadata": {},
"source": [
"Cutting stock problem with the objective to minimize wasted space.\n",
"\n"
"\n",
"Cutting stock problem with the objective to minimize wasted space.\n"
]
},
{
@@ -85,9 +85,10 @@
"source": [
"import collections\n",
"import time\n",
"import numpy as np\n",
"\n",
"from ortools.sat.colab import flags\n",
"import numpy as np\n",
"\n",
"from google.protobuf import text_format\n",
"from ortools.linear_solver.python import model_builder as mb\n",
"from ortools.sat.python import cp_model\n",
@@ -95,13 +96,14 @@
"FLAGS = flags.FLAGS\n",
"\n",
"_OUTPUT_PROTO = flags.define_string(\n",
" 'output_proto', '', 'Output file to write the cp_model proto to.')\n",
" \"output_proto\", \"\", \"Output file to write the cp_model proto to.\"\n",
")\n",
"_PARAMS = flags.define_string(\n",
" 'params',\n",
" 'num_search_workers:8,log_search_progress:true,max_time_in_seconds:10',\n",
" 'Sat solver parameters.')\n",
"_SOLVER = flags.define_string(\n",
" 'solver', 'sat', 'Method used to solve: sat, mip.')\n",
" \"params\",\n",
" \"num_search_workers:8,log_search_progress:true,max_time_in_seconds:10\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"_SOLVER = flags.define_string(\"solver\", \"sat\", \"Method used to solve: sat, mip.\")\n",
"\n",
"\n",
"DESIRED_LENGTHS = [\n",
@@ -173,9 +175,9 @@
" states.append(new_state)\n",
" state_to_index[new_state] = new_state_index\n",
" # Add the transition\n",
" transitions.append([\n",
" current_state_index, new_state_index, item_index, card + 1\n",
" ])\n",
" transitions.append(\n",
" [current_state_index, new_state_index, item_index, card + 1]\n",
" )\n",
"\n",
" return states, transitions\n",
"\n",
@@ -183,14 +185,19 @@
"def solve_cutting_stock_with_arc_flow_and_sat(output_proto_file: str, params: str):\n",
" \"\"\"Solve the cutting stock with arc-flow and the CP-SAT solver.\"\"\"\n",
" items = regroup_and_count(DESIRED_LENGTHS)\n",
" print('Items:', items)\n",
" print(\"Items:\", items)\n",
" num_items = len(DESIRED_LENGTHS)\n",
"\n",
" max_capacity = max(POSSIBLE_CAPACITIES)\n",
" states, transitions = create_state_graph(items, max_capacity)\n",
"\n",
" print('Dynamic programming has generated', len(states), 'states and',\n",
" len(transitions), 'transitions')\n",
" print(\n",
" \"Dynamic programming has generated\",\n",
" len(states),\n",
" \"states and\",\n",
" len(transitions),\n",
" \"transitions\",\n",
" )\n",
"\n",
" incoming_vars = collections.defaultdict(list)\n",
" outgoing_vars = collections.defaultdict(list)\n",
@@ -208,8 +215,8 @@
" count = items[item_index][1]\n",
" max_count = count // card\n",
" count_var = model.NewIntVar(\n",
" 0, max_count,\n",
" 'i%i_f%i_t%i_C%s' % (item_index, incoming, outgoing, card))\n",
" 0, max_count, \"i%i_f%i_t%i_C%s\" % (item_index, incoming, outgoing, card)\n",
" )\n",
" incoming_vars[incoming].append(count_var)\n",
" outgoing_vars[outgoing].append(count_var)\n",
" item_vars[item_index].append(count_var)\n",
@@ -219,7 +226,7 @@
" for state_index, state in enumerate(states):\n",
" if state_index == 0:\n",
" continue\n",
" exit_var = model.NewIntVar(0, num_items, 'e%i' % state_index)\n",
" exit_var = model.NewIntVar(0, num_items, \"e%i\" % state_index)\n",
" outgoing_vars[state_index].append(exit_var)\n",
" incoming_sink_vars.append(exit_var)\n",
" price = price_usage(state, POSSIBLE_CAPACITIES)\n",
@@ -228,8 +235,7 @@
"\n",
" # Flow conservation\n",
" for state_index in range(1, len(states)):\n",
" model.Add(\n",
" sum(incoming_vars[state_index]) == sum(outgoing_vars[state_index]))\n",
" model.Add(sum(incoming_vars[state_index]) == sum(outgoing_vars[state_index]))\n",
"\n",
" # Flow going out of the source must go in the sink\n",
" model.Add(sum(outgoing_vars[0]) == sum(incoming_sink_vars))\n",
@@ -238,13 +244,17 @@
" for item_index, size_and_count in enumerate(items):\n",
" num_arcs = len(item_vars[item_index])\n",
" model.Add(\n",
" sum(item_vars[item_index][i] * item_coeffs[item_index][i]\n",
" for i in range(num_arcs)) == size_and_count[1])\n",
" sum(\n",
" item_vars[item_index][i] * item_coeffs[item_index][i]\n",
" for i in range(num_arcs)\n",
" )\n",
" == size_and_count[1]\n",
" )\n",
"\n",
" # Objective is the sum of waste\n",
" model.Minimize(\n",
" sum(objective_vars[i] * objective_coeffs[i]\n",
" for i in range(len(objective_vars))))\n",
" sum(objective_vars[i] * objective_coeffs[i] for i in range(len(objective_vars)))\n",
" )\n",
"\n",
" # Output model proto to file.\n",
" if output_proto_file:\n",
@@ -261,13 +271,18 @@
"def solve_cutting_stock_with_arc_flow_and_mip():\n",
" \"\"\"Solve the cutting stock with arc-flow and a MIP solver.\"\"\"\n",
" items = regroup_and_count(DESIRED_LENGTHS)\n",
" print('Items:', items)\n",
" print(\"Items:\", items)\n",
" num_items = len(DESIRED_LENGTHS)\n",
" max_capacity = max(POSSIBLE_CAPACITIES)\n",
" states, transitions = create_state_graph(items, max_capacity)\n",
"\n",
" print('Dynamic programming has generated', len(states), 'states and',\n",
" len(transitions), 'transitions')\n",
" print(\n",
" \"Dynamic programming has generated\",\n",
" len(states),\n",
" \"states and\",\n",
" len(transitions),\n",
" \"transitions\",\n",
" )\n",
"\n",
" incoming_vars = collections.defaultdict(list)\n",
" outgoing_vars = collections.defaultdict(list)\n",
@@ -285,8 +300,10 @@
" for outgoing, incoming, item_index, card in transitions:\n",
" count = items[item_index][1]\n",
" count_var = model.new_int_var(\n",
" 0, count, 'a%i_i%i_f%i_t%i_c%i' % (var_index, item_index, incoming,\n",
" outgoing, card))\n",
" 0,\n",
" count,\n",
" \"a%i_i%i_f%i_t%i_c%i\" % (var_index, item_index, incoming, outgoing, card),\n",
" )\n",
" var_index += 1\n",
" incoming_vars[incoming].append(count_var)\n",
" outgoing_vars[outgoing].append(count_var)\n",
@@ -296,7 +313,7 @@
" for state_index, state in enumerate(states):\n",
" if state_index == 0:\n",
" continue\n",
" exit_var = model.new_int_var(0, num_items, 'e%i' % state_index)\n",
" exit_var = model.new_int_var(0, num_items, \"e%i\" % state_index)\n",
" outgoing_vars[state_index].append(exit_var)\n",
" incoming_sink_vars.append(exit_var)\n",
" price = price_usage(state, POSSIBLE_CAPACITIES)\n",
@@ -306,41 +323,49 @@
" # Flow conservation\n",
" for state_index in range(1, len(states)):\n",
" model.add(\n",
" mb.LinearExpr.sum(incoming_vars[state_index]) == mb.LinearExpr.sum(\n",
" outgoing_vars[state_index]))\n",
" mb.LinearExpr.sum(incoming_vars[state_index])\n",
" == mb.LinearExpr.sum(outgoing_vars[state_index])\n",
" )\n",
"\n",
" # Flow going out of the source must go in the sink\n",
" model.add(\n",
" mb.LinearExpr.sum(outgoing_vars[0]) == mb.LinearExpr.sum(\n",
" incoming_sink_vars))\n",
" mb.LinearExpr.sum(outgoing_vars[0]) == mb.LinearExpr.sum(incoming_sink_vars)\n",
" )\n",
"\n",
" # Items must be placed\n",
" for item_index, size_and_count in enumerate(items):\n",
" num_arcs = len(item_vars[item_index])\n",
" model.add(\n",
" mb.LinearExpr.sum([item_vars[item_index][i] * item_coeffs[item_index][i]\n",
" for i in range(num_arcs)]) == size_and_count[1])\n",
" mb.LinearExpr.sum(\n",
" [\n",
" item_vars[item_index][i] * item_coeffs[item_index][i]\n",
" for i in range(num_arcs)\n",
" ]\n",
" )\n",
" == size_and_count[1]\n",
" )\n",
"\n",
" # Objective is the sum of waste\n",
" model.minimize(np.dot(objective_vars, objective_coeffs))\n",
"\n",
" solver = mb.ModelSolver('scip')\n",
" solver = mb.ModelSolver(\"scip\")\n",
" solver.enable_output(True)\n",
" status = solver.solve(model)\n",
"\n",
" ### Output the solution.\n",
" if status == mb.SolveStatus.OPTIMAL or status == mb.SolveStatus.FEASIBLE:\n",
" print('Objective value = %f found in %.2f s' %\n",
" (solver.objective_value, time.time() - start_time))\n",
" print(\n",
" \"Objective value = %f found in %.2f s\"\n",
" % (solver.objective_value, time.time() - start_time)\n",
" )\n",
" else:\n",
" print('No solution')\n",
" print(\"No solution\")\n",
"\n",
"\n",
"def main(_):\n",
" \"\"\"Main function\"\"\"\n",
" if _SOLVER.value == 'sat':\n",
" solve_cutting_stock_with_arc_flow_and_sat(_OUTPUT_PROTO.value,\n",
" _PARAMS.value)\n",
" \"\"\"Main function.\"\"\"\n",
" if _SOLVER.value == \"sat\":\n",
" solve_cutting_stock_with_arc_flow_and_sat(_OUTPUT_PROTO.value, _PARAMS.value)\n",
" else: # 'mip'\n",
" solve_cutting_stock_with_arc_flow_and_mip()\n",
"\n",

View File

@@ -108,7 +108,7 @@
"from matplotlib import pyplot as plt\n",
"from collections import namedtuple\n",
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.routing import enums_pb2\n",
"from datetime import datetime, timedelta\n",
"\n",
"\n",
@@ -693,7 +693,7 @@
" parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" # Setting first solution heuristic (cheapest addition).\n",
" parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" # Routing: forbids use of TSPOpt neighborhood, (this is the default behaviour)\n",
" parameters.local_search_operators.use_tsp_opt = pywrapcp.BOOL_FALSE\n",
" # Disabling Large Neighborhood Search, (this is the default behaviour)\n",

View File

@@ -0,0 +1,264 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# pentominoes_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/examples/pentominoes_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/examples/python/pentominoes_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Example to solves a pentomino paving problem.\n",
"\n",
"Given a subset of n different pentomino, the problem is to pave a square of\n",
"size 5 x n. The problem is reduced to an exact set cover problem and encoded\n",
"as a linear boolean problem.\n",
"\n",
"This problem comes from the game Katamino:\n",
"http://boardgamegeek.com/boardgame/6931/katamino\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from collections.abc import Sequence\n",
"from typing import Dict, List\n",
"\n",
"from ortools.sat.colab import flags\n",
"from google.protobuf import text_format\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"_PARAMS = flags.define_string(\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:false,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"\n",
"_PIECES = flags.define_string(\n",
" \"pieces\", \"FILNPTUVWXYZ\", \"The subset of pieces to consider.\"\n",
")\n",
"\n",
"\n",
"def is_one(mask: List[List[int]], x: int, y: int, orientation: int) -> bool:\n",
" \"\"\"Returns true if the oriented piece is 1 at position [i][j].\n",
"\n",
" The 3 bits in orientation respectively mean: transposition, symmetry by\n",
" x axis, symmetry by y axis.\n",
"\n",
" Args:\n",
" mask: The shape of the piece.\n",
" x: position.\n",
" y: position.\n",
" orientation: between 0 and 7.\n",
" \"\"\"\n",
" if orientation & 1:\n",
" tmp: int = x\n",
" x = y\n",
" y = tmp\n",
" if orientation & 2:\n",
" x = len(mask[0]) - 1 - x\n",
" if orientation & 4:\n",
" y = len(mask) - 1 - y\n",
" return mask[y][x] == 1\n",
"\n",
"\n",
"def get_height(mask: List[List[int]], orientation: int) -> int:\n",
" if orientation & 1:\n",
" return len(mask[0])\n",
" return len(mask)\n",
"\n",
"\n",
"def get_width(mask: List[List[int]], orientation: int) -> int:\n",
" if orientation & 1:\n",
" return len(mask)\n",
" return len(mask[0])\n",
"\n",
"\n",
"def orientation_is_redundant(mask: List[List[int]], orientation: int) -> bool:\n",
" \"\"\"Checks if the current rotated figure is the same as a previous rotation.\"\"\"\n",
" size_i: int = get_width(mask, orientation)\n",
" size_j: int = get_height(mask, orientation)\n",
" for o in range(orientation):\n",
" if size_i != get_width(mask, o):\n",
" continue\n",
" if size_j != get_height(mask, o):\n",
" continue\n",
"\n",
" is_the_same: bool = True\n",
" for k in range(size_i):\n",
" if not is_the_same:\n",
" break\n",
" for l in range(size_j):\n",
" if not is_the_same:\n",
" break\n",
" if is_one(mask, k, l, orientation) != is_one(mask, k, l, o):\n",
" is_the_same = False\n",
" if is_the_same:\n",
" return True\n",
" return False\n",
"\n",
"\n",
"def generate_and_solve_problem(pieces: Dict[str, List[List[int]]]) -> None:\n",
" \"\"\"Solves the pentominoes problem.\"\"\"\n",
" box_width = len(pieces)\n",
" box_height = 5\n",
"\n",
" model = cp_model.CpModel()\n",
" position_to_variables: List[List[List[cp_model.IntVar]]] = [\n",
" [[] for _ in range(box_width)] for _ in range(box_height)\n",
" ]\n",
"\n",
" for name, mask in pieces.items():\n",
" all_position_variables = []\n",
" for orientation in range(8):\n",
" if orientation_is_redundant(mask, orientation):\n",
" continue\n",
" piece_width = get_width(mask, orientation)\n",
" piece_height = get_height(mask, orientation)\n",
" for i in range(box_width - piece_width + 1):\n",
" for j in range(box_height - piece_height + 1):\n",
" v = model.new_bool_var(name)\n",
" all_position_variables.append(v)\n",
" for k in range(piece_width):\n",
" for l in range(piece_height):\n",
" if is_one(mask, k, l, orientation):\n",
" position_to_variables[j + l][i + k].append(v)\n",
"\n",
" # Only one combination is selected.\n",
" model.add_exactly_one(all_position_variables)\n",
"\n",
" for one_column in position_to_variables:\n",
" for all_pieces_in_one_position in one_column:\n",
" model.add_exactly_one(all_pieces_in_one_position)\n",
"\n",
" # Solve the model.\n",
" solver = cp_model.CpSolver()\n",
" if _PARAMS.value:\n",
" text_format.Parse(_PARAMS.value, solver.parameters)\n",
" status = solver.solve(model)\n",
"\n",
" print(\n",
" f\"Problem {_PIECES.value} solved in {solver.wall_time}s with status\"\n",
" f\" {solver.status_name(status)}\"\n",
" )\n",
"\n",
" # Print the solution.\n",
" if status == cp_model.OPTIMAL:\n",
" for y in range(box_height):\n",
" line = \"\"\n",
" for x in range(box_width):\n",
" for v in position_to_variables[y][x]:\n",
" if solver.BooleanValue(v):\n",
" line += v.name\n",
" break\n",
" print(line)\n",
"\n",
"\n",
"def main(argv: Sequence[str]) -> None:\n",
" if len(argv) > 1:\n",
" raise app.UsageError(\"Too many command-line arguments.\")\n",
"\n",
" # Pieces are stored in a matrix. mask[height][width]\n",
" pieces: Dict[str, List[List[int]]] = {\n",
" \"F\": [[0, 1, 1], [1, 1, 0], [0, 1, 0]],\n",
" \"I\": [[1, 1, 1, 1, 1]],\n",
" \"L\": [[1, 1, 1, 1], [1, 0, 0, 0]],\n",
" \"N\": [[1, 1, 1, 0], [0, 0, 1, 1]],\n",
" \"P\": [[1, 1, 1], [1, 1, 0]],\n",
" \"T\": [[1, 1, 1], [0, 1, 0], [0, 1, 0]],\n",
" \"U\": [[1, 0, 1], [1, 1, 1]],\n",
" \"V\": [[1, 0, 0], [1, 0, 0], [1, 1, 1]],\n",
" \"W\": [[1, 0, 0], [1, 1, 0], [0, 1, 1]],\n",
" \"X\": [[0, 1, 0], [1, 1, 1], [0, 1, 0]],\n",
" \"Y\": [[1, 1, 1, 1], [0, 1, 0, 0]],\n",
" \"Z\": [[1, 1, 0], [0, 1, 0], [0, 1, 1]],\n",
" }\n",
" selected_pieces: Dict[str, List[List[int]]] = {}\n",
" for p in _PIECES.value:\n",
" if p not in pieces:\n",
" print(f\"Piece {p} not found in the list of pieces\")\n",
" return\n",
" selected_pieces[p] = pieces[p]\n",
" generate_and_solve_problem(selected_pieces)\n",
"\n",
"\n",
"main()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -82,7 +82,7 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.routing import enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"\n",
"DISTANCE_MATRIX = [\n",
@@ -216,9 +216,9 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" search_parameters.time_limit.FromSeconds(15)\n",
" #search_parameters.log_search = True\n",
"\n",

View File

@@ -82,7 +82,7 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.routing import enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"\n",
"DISTANCE_MATRIX = [\n",
@@ -222,9 +222,9 @@
" # Setting first solution heuristic.\n",
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" search_parameters.local_search_metaheuristic = (\n",
" routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)\n",
" search_parameters.time_limit.FromSeconds(15)\n",
" #search_parameters.log_search = True\n",
"\n",

View File

@@ -96,7 +96,7 @@
"from functools import partial\n",
"import random\n",
"\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"from ortools.routing import enums_pb2\n",
"from ortools.constraint_solver import pywrapcp\n",
"\n",
"parser = argparse.ArgumentParser()\n",
@@ -166,7 +166,7 @@
" search_parameters = pywrapcp.DefaultRoutingSearchParameters()\n",
" # Setting first solution heuristic (cheapest addition).\n",
" search_parameters.first_solution_strategy = (\n",
" routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
" enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)\n",
"\n",
" # Setting the cost function.\n",
" # Put a callback to the distance accessor here. The callback takes two\n",

View File

@@ -91,6 +91,7 @@
"outputs": [],
"source": [
"import collections\n",
"import time\n",
"from typing import Optional\n",
"\n",
"from ortools.sat.colab import flags\n",
@@ -117,9 +118,9 @@
" + \" precedence graph.\",\n",
")\n",
"_DELAY_TIME_LIMIT = flags.define_float(\n",
" \"delay_time_limit\",\n",
" 20.0,\n",
" \"Time limit when computing min delay between tasks.\"\n",
" \"pairwise_delay_total_time_limit\",\n",
" 120.0,\n",
" \"Total time limit when computing min delay between tasks.\"\n",
" + \" A non-positive time limit disable min delays computation.\",\n",
")\n",
"_PREEMPTIVE_LB_TIME_LIMIT = flags.define_float(\n",
@@ -668,21 +669,30 @@
" ):\n",
" return delays, None, False\n",
"\n",
" time_limit = _DELAY_TIME_LIMIT.value\n",
" complete_problem_assignment = None\n",
" num_optimal_delays = 0\n",
" num_delays_not_found = 0\n",
" optimal_found = True\n",
" for start_task, end_task, active_tasks in task_intervals:\n",
" if time_limit <= 0:\n",
" optimal_found = False\n",
" print(f\" - #timeout ({_DELAY_TIME_LIMIT.value}s) reached\", flush=True)\n",
" break\n",
"\n",
" start_time = time.time()\n",
" min_delay, feasible_delay, assignment = solve_rcpsp(\n",
" problem,\n",
" \"\",\n",
" f\"num_search_workers:16,max_time_in_seconds:{_DELAY_TIME_LIMIT.value}\",\n",
" f\"num_search_workers:16,max_time_in_seconds:{time_limit}\",\n",
" set(active_tasks),\n",
" start_task,\n",
" end_task,\n",
" [],\n",
" delays,\n",
" )\n",
" time_limit -= time.time() - start_time\n",
"\n",
" if min_delay != -1:\n",
" delays[(start_task, end_task)] = min_delay, feasible_delay\n",
" if start_task == 0 and end_task == len(problem.tasks) - 1:\n",

View File

@@ -95,7 +95,7 @@
")\n",
"_PARAMS = flags.define_string(\n",
" \"params\",\n",
" \"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45\",\n",
" \"num_search_workers:16,log_search_progress:false,max_time_in_seconds:45\",\n",
" \"Sat solver parameters.\",\n",
")\n",
"_PREPROCESS = flags.define_bool(\n",
@@ -571,6 +571,7 @@
" if parameters:\n",
" text_format.Parse(parameters, solver.parameters)\n",
" solution_printer = SolutionPrinter()\n",
" solver.best_bound_callback = lambda a : print(f\"New objective lower bound: {a}\")\n",
" solver.solve(model, solution_printer)\n",
" for job_id in all_jobs:\n",
" print(\n",

View File

@@ -89,7 +89,6 @@
"outputs": [],
"source": [
"from ortools.constraint_solver import pywrapcp\n",
"from ortools.constraint_solver import routing_enums_pb2\n",
"\n",
"\n",
"###########################\n",

View File

@@ -130,6 +130,7 @@
" for arc in range(smcf.num_arcs()):\n",
" # Can ignore arcs leading out of source or into sink.\n",
" if smcf.tail(arc) != source and smcf.head(arc) != sink:\n",
"\n",
" # Arcs in the solution have a flow value of 1. Their start and end nodes\n",
" # give an assignment of worker to task.\n",
" if smcf.flow(arc) > 0:\n",

View File

@@ -161,6 +161,7 @@
" and smcf.tail(arc) != 12\n",
" and smcf.head(arc) != sink\n",
" ):\n",
"\n",
" # Arcs in the solution will have a flow value of 1.\n",
" # There start and end nodes give an assignment of worker to task.\n",
" if smcf.flow(arc) > 0:\n",

View File

@@ -83,43 +83,66 @@
"metadata": {},
"outputs": [],
"source": [
"from ortools.init.python import init\n",
"from ortools.linear_solver import pywraplp\n",
"\n",
"\n",
"def main():\n",
" print(\"Google OR-Tools version:\", init.OrToolsVersion.version_string())\n",
"\n",
" # Create the linear solver with the GLOP backend.\n",
" solver = pywraplp.Solver.CreateSolver(\"GLOP\")\n",
" if not solver:\n",
" print(\"Could not create solver GLOP\")\n",
" return\n",
"\n",
" # Create the variables x and y.\n",
" x = solver.NumVar(0, 1, \"x\")\n",
" y = solver.NumVar(0, 2, \"y\")\n",
" x_var = solver.NumVar(0, 1, \"x\")\n",
" y_var = solver.NumVar(0, 2, \"y\")\n",
"\n",
" print(\"Number of variables =\", solver.NumVariables())\n",
"\n",
" # Create a linear constraint, 0 <= x + y <= 2.\n",
" ct = solver.Constraint(0, 2, \"ct\")\n",
" ct.SetCoefficient(x, 1)\n",
" ct.SetCoefficient(y, 1)\n",
" infinity = solver.infinity()\n",
" # Create a linear constraint, x + y <= 2.\n",
" constraint = solver.Constraint(-infinity, 2, \"ct\")\n",
" constraint.SetCoefficient(x_var, 1)\n",
" constraint.SetCoefficient(y_var, 1)\n",
"\n",
" print(\"Number of constraints =\", solver.NumConstraints())\n",
"\n",
" # Create the objective function, 3 * x + y.\n",
" objective = solver.Objective()\n",
" objective.SetCoefficient(x, 3)\n",
" objective.SetCoefficient(y, 1)\n",
" objective.SetCoefficient(x_var, 3)\n",
" objective.SetCoefficient(y_var, 1)\n",
" objective.SetMaximization()\n",
"\n",
" print(f\"Solving with {solver.SolverVersion()}\")\n",
" solver.Solve()\n",
" result_status = solver.Solve()\n",
"\n",
" print(f\"Status: {result_status}\")\n",
" if result_status != pywraplp.Solver.OPTIMAL:\n",
" print(\"The problem does not have an optimal solution!\")\n",
" if result_status == pywraplp.Solver.FEASIBLE:\n",
" print(\"A potentially suboptimal solution was found\")\n",
" else:\n",
" print(\"The solver could not solve the problem.\")\n",
" return\n",
"\n",
" print(\"Solution:\")\n",
" print(\"Objective value =\", objective.Value())\n",
" print(\"x =\", x.solution_value())\n",
" print(\"y =\", y.solution_value())\n",
" print(\"x =\", x_var.solution_value())\n",
" print(\"y =\", y_var.solution_value())\n",
"\n",
" print(\"Advanced usage:\")\n",
" print(f\"Problem solved in {solver.wall_time():d} milliseconds\")\n",
" print(f\"Problem solved in {solver.iterations():d} iterations\")\n",
"\n",
"\n",
"init.CppBridge.init_logging(\"basic_example.py\")\n",
"cpp_flags = init.CppFlags()\n",
"cpp_flags.stderrthreshold = True\n",
"cpp_flags.log_prefix = False\n",
"init.CppBridge.set_flags(cpp_flags)\n",
"main()\n",
"\n"
]

View File

@@ -87,7 +87,7 @@
"\n",
"\n",
"def main():\n",
" # Create the mip solver with the SCIP backend.\n",
" # Create the mip solver with the CP-SAT backend.\n",
" solver = pywraplp.Solver.CreateSolver(\"SAT\")\n",
" if not solver:\n",
" return\n",

View File

@@ -0,0 +1,213 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# tsp"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/routing/tsp.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/routing/samples/tsp.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Simple Travelling Salesman Problem.\n",
"\n",
"A description of the problem can be found here:\n",
"http://en.wikipedia.org/wiki/Travelling_salesperson_problem.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from ortools.routing import enums_pb2\n",
"from ortools.routing import parameters_pb2\n",
"from ortools.routing.python import routing_model\n",
"\n",
"FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy\n",
"RoutingSearchStatus = enums_pb2.RoutingSearchStatus\n",
"\n",
"\n",
"def create_data_model():\n",
" \"\"\"Stores the data for the problem.\"\"\"\n",
" data = {}\n",
" # Locations in block units\n",
" locations = [\n",
" # fmt:off\n",
" (4, 4), # depot\n",
" (2, 0), (8, 0), # locations to visit\n",
" (0, 1), (1, 1),\n",
" (5, 2), (7, 2),\n",
" (3, 3), (6, 3),\n",
" (5, 5), (8, 5),\n",
" (1, 6), (2, 6),\n",
" (3, 7), (6, 7),\n",
" (0, 8), (7, 8)\n",
" # fmt:on\n",
" ]\n",
" # Convert locations in meters using a city block dimension of 114m x 80m.\n",
" data[\"locations\"] = [(l[0] * 114, l[1] * 80) for l in locations]\n",
" data[\"num_vehicles\"] = 1\n",
" data[\"depot\"] = 0\n",
" return data\n",
"\n",
"\n",
"def create_distance_callback(data, manager):\n",
" \"\"\"Creates callback to return distance between points.\"\"\"\n",
" distances_ = {}\n",
" index_manager_ = manager\n",
" # precompute distance between location to have distance callback in O(1)\n",
" for from_counter, from_node in enumerate(data[\"locations\"]):\n",
" distances_[from_counter] = {}\n",
" for to_counter, to_node in enumerate(data[\"locations\"]):\n",
" if from_counter == to_counter:\n",
" distances_[from_counter][to_counter] = 0\n",
" else:\n",
" distances_[from_counter][to_counter] = abs(\n",
" from_node[0] - to_node[0]\n",
" ) + abs(from_node[1] - to_node[1])\n",
"\n",
" def distance_callback(from_index, to_index):\n",
" \"\"\"Returns the manhattan distance between the two nodes.\"\"\"\n",
" # Convert from routing variable Index to distance matrix NodeIndex.\n",
" from_node = index_manager_.index_to_node(from_index)\n",
" to_node = index_manager_.index_to_node(to_index)\n",
" return distances_[from_node][to_node]\n",
"\n",
" return distance_callback\n",
"\n",
"\n",
"def print_solution(manager, routing, solution):\n",
" \"\"\"Prints assignment on console.\"\"\"\n",
" status = routing.status()\n",
" print(f\"Status: {RoutingSearchStatus.Value.Name(status)}\")\n",
" if (\n",
" status != RoutingSearchStatus.ROUTING_OPTIMAL\n",
" and status != RoutingSearchStatus.ROUTING_SUCCESS\n",
" ):\n",
" print(\"No solution found!\")\n",
" return\n",
" print(f\"Objective: {solution.objective_value()}\")\n",
" index = routing.start(0)\n",
" plan_output = \"Route for vehicle 0:\\n\"\n",
" route_distance = 0\n",
" while not routing.is_end(index):\n",
" plan_output += f\" {manager.index_to_node(index)} ->\"\n",
" previous_index = index\n",
" index = solution.value(routing.next_var(index))\n",
" route_distance += routing.get_arc_cost_for_vehicle(previous_index, index, 0)\n",
" plan_output += f\" {manager.index_to_node(index)}\\n\"\n",
" plan_output += f\"Distance of the route: {route_distance}m\\n\"\n",
" print(plan_output)\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Entry point of the program.\"\"\"\n",
" # Instantiate the data problem.\n",
" data = create_data_model()\n",
"\n",
" # Create the routing index manager.\n",
" manager = routing_model.RoutingIndexManager(\n",
" len(data[\"locations\"]), data[\"num_vehicles\"], data[\"depot\"]\n",
" )\n",
"\n",
" # Create Routing Model.\n",
" routing = routing_model.RoutingModel(manager)\n",
"\n",
" # Create and register a transit callback.\n",
" distance_callback = create_distance_callback(data, manager)\n",
" transit_callback_index = routing.register_transit_callback(distance_callback)\n",
"\n",
" # Define cost of each arc.\n",
" routing.set_arc_cost_evaluator_of_all_vehicles(transit_callback_index)\n",
"\n",
" # Setting first solution heuristic.\n",
" search_parameters: parameters_pb2.RoutingSearchParameters = (\n",
" routing_model.default_routing_search_parameters()\n",
" )\n",
" search_parameters.first_solution_strategy = FirstSolutionStrategy.PATH_CHEAPEST_ARC\n",
"\n",
" # Solve the problem.\n",
" solution = routing.solve()\n",
" # solution = routing.solve_with_parameters(search_parameters)\n",
"\n",
" # Print solution on console.\n",
" print_solution(manager, routing, solution)\n",
"\n",
"\n",
"main()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -169,10 +169,13 @@
"\n",
" for b in active_bins:\n",
" print(f\"Bin {b}\")\n",
" items_in_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x].index\n",
" for item in items_in_bin:\n",
" items_in_active_bin = x_values.xs(b, level=\"bin\").loc[lambda x: x].index\n",
" for item in items_in_active_bin:\n",
" print(f\" Item {item} - weight {items.loc[item].weight}\")\n",
" print(f\" Packed items weight: {items.loc[items_in_bin].sum().to_string()}\")\n",
" print(\n",
" \" Packed items weight:\"\n",
" f\" {items.loc[items_in_active_bin].sum().to_string()}\"\n",
" )\n",
" print()\n",
"\n",
" print(f\"Total packed weight: {items.weight.sum()}\")\n",

View File

@@ -0,0 +1,150 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# bool_and_int_var_product_sample_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/sat/bool_and_int_var_product_sample_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/sat/samples/bool_and_int_var_product_sample_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Code sample that encodes the product of a Boolean and an integer variable.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, variables: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__variables = variables\n",
"\n",
" def on_solution_callback(self) -> None:\n",
" for v in self.__variables:\n",
" print(f\"{v}={self.value(v)}\", end=\" \")\n",
" print()\n",
"\n",
"\n",
"def build_product_var(\n",
" model: cp_model.CpModel, b: cp_model.IntVar, x: cp_model.IntVar, name: str\n",
") -> cp_model.IntVar:\n",
" \"\"\"Builds the product of a Boolean variable and an integer variable.\"\"\"\n",
" p = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_flat_intervals(x.proto.domain).union_with(\n",
" cp_model.Domain(0, 0)\n",
" ),\n",
" name,\n",
" )\n",
" model.add(p == x).only_enforce_if(b)\n",
" model.add(p == 0).only_enforce_if(~b)\n",
" return p\n",
"\n",
"\n",
"def bool_and_int_var_product_sample_sat():\n",
" \"\"\"Encoding of the product of two Boolean variables.\n",
"\n",
" p == x * y, which is the same as p <=> x and y\n",
" \"\"\"\n",
" model = cp_model.CpModel()\n",
" b = model.new_bool_var(\"b\")\n",
" x = model.new_int_var_from_domain(\n",
" cp_model.Domain.from_values([1, 2, 3, 5, 6, 7, 9, 10]), \"x\"\n",
" )\n",
" p = build_product_var(model, b, x, \"p\")\n",
"\n",
" # Search for x and b values in increasing order.\n",
" model.add_decision_strategy(\n",
" [b, x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" )\n",
"\n",
" # Create a solver and solve.\n",
" solver = cp_model.CpSolver()\n",
" solution_printer = VarArraySolutionPrinter([x, b, p])\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solver.parameters.search_branching = cp_model.FIXED_SEARCH\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"bool_and_int_var_product_sample_sat()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -73,7 +73,7 @@
"metadata": {},
"source": [
"\n",
"Solves a simple scheduling problem with a variable work load.\n"
"Solves a scheduling problem with a min and max profile for the work load.\n"
]
},
{
@@ -90,22 +90,38 @@
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:\n",
" \"\"\"Creates the two dataframes that describes the model.\"\"\"\n",
"def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:\n",
" \"\"\"Creates the dataframes that describes the model.\"\"\"\n",
"\n",
" capacity_str: str = \"\"\"\n",
" start_hour capacity\n",
" max_load_str: str = \"\"\"\n",
" start_hour max_load\n",
" 0 0\n",
" 2 0\n",
" 4 1\n",
" 6 3\n",
" 8 6\n",
" 4 3\n",
" 6 6\n",
" 8 8\n",
" 10 12\n",
" 12 8\n",
" 14 12\n",
" 16 10\n",
" 18 4\n",
" 20 2\n",
" 18 6\n",
" 20 4\n",
" 22 0\n",
" \"\"\"\n",
"\n",
" min_load_str: str = \"\"\"\n",
" start_hour min_load\n",
" 0 0\n",
" 2 0\n",
" 4 0\n",
" 6 0\n",
" 8 3\n",
" 10 3\n",
" 12 1\n",
" 14 3\n",
" 16 3\n",
" 18 1\n",
" 20 1\n",
" 22 0\n",
" \"\"\"\n",
"\n",
@@ -143,29 +159,74 @@
" t30 90 4 2\n",
" \"\"\"\n",
"\n",
" capacity_df = pd.read_table(io.StringIO(capacity_str), sep=r\"\\s+\")\n",
" max_load_df = pd.read_table(io.StringIO(max_load_str), sep=r\"\\s+\")\n",
" min_load_df = pd.read_table(io.StringIO(min_load_str), sep=r\"\\s+\")\n",
" tasks_df = pd.read_table(io.StringIO(tasks_str), index_col=0, sep=r\"\\s+\")\n",
" return capacity_df, tasks_df\n",
" return max_load_df, min_load_df, tasks_df\n",
"\n",
"\n",
"def main() -> None:\n",
"def check_solution(\n",
" tasks: list[tuple[int, int, int]],\n",
" min_load_df: pd.DataFrame,\n",
" max_load_df: pd.DataFrame,\n",
" period_length: int,\n",
" horizon: int,\n",
") -> bool:\n",
" \"\"\"Checks the solution validity against the min and max load constraints.\"\"\"\n",
" minutes_per_hour = 60\n",
" actual_load_profile = [0 for _ in range(horizon)]\n",
" min_load_profile = [0 for _ in range(horizon)]\n",
" max_load_profile = [0 for _ in range(horizon)]\n",
"\n",
" # The complexity of the checker is linear in the number of time points, and\n",
" # should be improved.\n",
" for task in tasks:\n",
" for t in range(task[1]):\n",
" actual_load_profile[task[0] + t] += task[2]\n",
" for row in max_load_df.itertuples():\n",
" for t in range(period_length):\n",
" max_load_profile[row.start_hour * minutes_per_hour + t] = row.max_load\n",
" for row in min_load_df.itertuples():\n",
" for t in range(period_length):\n",
" min_load_profile[row.start_hour * minutes_per_hour + t] = row.min_load\n",
"\n",
" for time in range(horizon):\n",
" if actual_load_profile[time] > max_load_profile[time]:\n",
" print(\n",
" f\"actual load {actual_load_profile[time]} at time {time} is greater\"\n",
" f\" than max load {max_load_profile[time]}\"\n",
" )\n",
" return False\n",
" if actual_load_profile[time] < min_load_profile[time]:\n",
" print(\n",
" f\"actual load {actual_load_profile[time]} at time {time} is\"\n",
" f\" less than min load {min_load_profile[time]}\"\n",
" )\n",
" return False\n",
" return True\n",
"\n",
"\n",
"def main(_) -> None:\n",
" \"\"\"Create the model and solves it.\"\"\"\n",
" capacity_df, tasks_df = create_data_model()\n",
" max_load_df, min_load_df, tasks_df = create_data_model()\n",
"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Get the max capacity from the capacity dataframe.\n",
" max_capacity = capacity_df.capacity.max()\n",
" print(f\"Max capacity = {max_capacity}\")\n",
" max_load = max_load_df.max_load.max()\n",
" print(f\"Max capacity = {max_load}\")\n",
" print(f\"#tasks = {len(tasks_df)}\")\n",
"\n",
" minutes_per_period: int = 120\n",
" minutes_per_hour: int = 60\n",
" horizon: int = 24 * 60\n",
"\n",
" # Variables\n",
" starts = model.new_int_var_series(\n",
" name=\"starts\", lower_bounds=0, upper_bounds=horizon, index=tasks_df.index\n",
" name=\"starts\",\n",
" lower_bounds=0,\n",
" upper_bounds=horizon - tasks_df.duration,\n",
" index=tasks_df.index,\n",
" )\n",
" performed = model.new_bool_var_series(name=\"performed\", index=tasks_df.index)\n",
"\n",
@@ -177,21 +238,72 @@
" are_present=performed,\n",
" )\n",
"\n",
" # Set up the profile. We use fixed (intervals, demands) to fill in the space\n",
" # between the actual load profile and the max capacity.\n",
" time_period_intervals = model.new_fixed_size_interval_var_series(\n",
" name=\"time_period_intervals\",\n",
" index=capacity_df.index,\n",
" starts=capacity_df.start_hour * minutes_per_period,\n",
" sizes=minutes_per_period,\n",
" # Set up the max profile. We use fixed (intervals, demands) to fill in the\n",
" # space between the actual max load profile and the max capacity.\n",
" time_period_max_intervals = model.new_fixed_size_interval_var_series(\n",
" name=\"time_period_max_intervals\",\n",
" index=max_load_df.index,\n",
" starts=max_load_df.start_hour * minutes_per_hour,\n",
" sizes=minutes_per_hour * 2,\n",
" )\n",
" time_period_heights = max_capacity - capacity_df.capacity\n",
" time_period_max_heights = max_load - max_load_df.max_load\n",
"\n",
" # Cumulative constraint.\n",
" # Cumulative constraint for the max profile.\n",
" model.add_cumulative(\n",
" intervals.to_list() + time_period_intervals.to_list(),\n",
" tasks_df.load.to_list() + time_period_heights.to_list(),\n",
" max_capacity,\n",
" intervals.to_list() + time_period_max_intervals.to_list(),\n",
" tasks_df.load.to_list() + time_period_max_heights.to_list(),\n",
" max_load,\n",
" )\n",
"\n",
" # Set up complemented intervals (from 0 to start, and from start + size to\n",
" # horizon).\n",
" prefix_intervals = model.new_optional_interval_var_series(\n",
" name=\"prefix_intervals\",\n",
" index=tasks_df.index,\n",
" starts=0,\n",
" sizes=starts,\n",
" ends=starts,\n",
" are_present=performed,\n",
" )\n",
"\n",
" suffix_intervals = model.new_optional_interval_var_series(\n",
" name=\"suffix_intervals\",\n",
" index=tasks_df.index,\n",
" starts=starts + tasks_df.duration,\n",
" sizes=horizon - starts - tasks_df.duration,\n",
" ends=horizon,\n",
" are_present=performed,\n",
" )\n",
"\n",
" # Set up the min profile. We use complemented intervals to maintain the\n",
" # complement of the work load, and fixed intervals to enforce the min\n",
" # number of active workers per time period.\n",
" #\n",
" # Note that this works only if the max load cumulative is also added to the\n",
" # model.\n",
" time_period_min_intervals = model.new_fixed_size_interval_var_series(\n",
" name=\"time_period_min_intervals\",\n",
" index=min_load_df.index,\n",
" starts=min_load_df.start_hour * minutes_per_hour,\n",
" sizes=minutes_per_hour * 2,\n",
" )\n",
" time_period_min_heights = min_load_df.min_load\n",
"\n",
" # We take into account optional intervals. The actual capacity of the min load\n",
" # cumulative is the sum of all the active demands.\n",
" sum_of_demands = sum(tasks_df.load)\n",
" complement_capacity = model.new_int_var(0, sum_of_demands, \"complement_capacity\")\n",
" model.add(complement_capacity == performed.dot(tasks_df.load))\n",
"\n",
" # Cumulative constraint for the min profile.\n",
" model.add_cumulative(\n",
" prefix_intervals.to_list()\n",
" + suffix_intervals.to_list()\n",
" + time_period_min_intervals.to_list(),\n",
" tasks_df.load.to_list()\n",
" + tasks_df.load.to_list()\n",
" + time_period_min_heights.to_list(),\n",
" complement_capacity,\n",
" )\n",
"\n",
" # Objective: maximize the value of performed intervals.\n",
@@ -202,18 +314,32 @@
" # Create the solver and solve the model.\n",
" solver = cp_model.CpSolver()\n",
" solver.parameters.log_search_progress = True\n",
" solver.parameters.num_workers = 8\n",
" solver.parameters.num_workers = 16\n",
" solver.parameters.max_time_in_seconds = 30.0\n",
" status = solver.solve(model)\n",
"\n",
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
" start_values = solver.values(starts)\n",
" performed_values = solver.boolean_values(performed)\n",
" tasks: list[tuple[int, int, int]] = []\n",
" for task in tasks_df.index:\n",
" if performed_values[task]:\n",
" print(f\"task {task} starts at {start_values[task]}\")\n",
" print(\n",
" f'task {task} duration={tasks_df[\"duration\"][task]} '\n",
" f'load={tasks_df[\"load\"][task]} starts at {start_values[task]}'\n",
" )\n",
" tasks.append(\n",
" (start_values[task], tasks_df.duration[task], tasks_df.load[task])\n",
" )\n",
" else:\n",
" print(f\"task {task} is not performed\")\n",
" assert check_solution(\n",
" tasks=tasks,\n",
" min_load_df=min_load_df,\n",
" max_load_df=max_load_df,\n",
" period_length=2 * minutes_per_hour,\n",
" horizon=horizon,\n",
" )\n",
" elif status == cp_model.INFEASIBLE:\n",
" print(\"No solution found\")\n",
" else:\n",

View File

@@ -0,0 +1,150 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "google",
"metadata": {},
"source": [
"##### Copyright 2023 Google LLC."
]
},
{
"cell_type": "markdown",
"id": "apache",
"metadata": {},
"source": [
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"you may not use this file except in compliance with the License.\n",
"You may obtain a copy of the License at\n",
"\n",
" http://www.apache.org/licenses/LICENSE-2.0\n",
"\n",
"Unless required by applicable law or agreed to in writing, software\n",
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"See the License for the specific language governing permissions and\n",
"limitations under the License.\n"
]
},
{
"cell_type": "markdown",
"id": "basename",
"metadata": {},
"source": [
"# index_first_boolvar_true_sample_sat"
]
},
{
"cell_type": "markdown",
"id": "link",
"metadata": {},
"source": [
"<table align=\"left\">\n",
"<td>\n",
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/sat/index_first_boolvar_true_sample_sat.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
"</td>\n",
"<td>\n",
"<a href=\"https://github.com/google/or-tools/blob/main/ortools/sat/samples/index_first_boolvar_true_sample_sat.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
"</td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "doc",
"metadata": {},
"source": [
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "install",
"metadata": {},
"outputs": [],
"source": [
"%pip install ortools"
]
},
{
"cell_type": "markdown",
"id": "description",
"metadata": {},
"source": [
"\n",
"Compute the index of the first Boolean variable set to true.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code",
"metadata": {},
"outputs": [],
"source": [
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
"class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
" \"\"\"Print intermediate solutions.\"\"\"\n",
"\n",
" def __init__(self, index: cp_model.IntVar, boolvars: list[cp_model.IntVar]):\n",
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
" self.__index = index\n",
" self.__boolvars = boolvars\n",
"\n",
" def on_solution_callback(self) -> None:\n",
" line = \"\"\n",
" for v in self.__boolvars:\n",
" line += f\"{self.value(v)}\"\n",
" line += f\" -> {self.value(self.__index)}\"\n",
" print(line)\n",
"\n",
"\n",
"def index_of_first_bool_at_true_sample_sat():\n",
" \"\"\"Compute the index of the first Boolean variable set to true.\"\"\"\n",
"\n",
" # Model.\n",
" model = cp_model.CpModel()\n",
"\n",
" # Variables\n",
" num_bool_vars = 5\n",
" bool_vars = [model.new_bool_var(f\"{i}\") for i in range(num_bool_vars)]\n",
" index = model.new_int_var(0, num_bool_vars, \"index\")\n",
"\n",
" # Channeling between the index and the Boolean variables.\n",
" model.add_min_equality(\n",
" index,\n",
" [\n",
" num_bool_vars - bool_vars[i] * (num_bool_vars - i)\n",
" for i in range(num_bool_vars)\n",
" ],\n",
" )\n",
"\n",
" # Flip bool_vars in increasing order.\n",
" model.add_decision_strategy(\n",
" bool_vars, cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE\n",
" )\n",
"\n",
" # Create a solver and solve with a fixed search.\n",
" solver = cp_model.CpSolver()\n",
"\n",
" # Force the solver to follow the decision strategy exactly.\n",
" solver.parameters.search_branching = cp_model.FIXED_SEARCH\n",
"\n",
" # Search and print out all solutions.\n",
" solver.parameters.enumerate_all_solutions = True\n",
" solution_printer = VarArraySolutionPrinter(index, bool_vars)\n",
" solver.solve(model, solution_printer)\n",
"\n",
"\n",
"index_of_first_bool_at_true_sample_sat()\n",
"\n"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -83,6 +83,8 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import Union\n",
"\n",
"from ortools.sat.python import cp_model\n",
"\n",
"\n",
@@ -136,7 +138,7 @@
" else:\n",
" max_shifts_per_nurse = min_shifts_per_nurse + 1\n",
" for n in all_nurses:\n",
" num_shifts_worked = 0\n",
" num_shifts_worked: Union[cp_model.LinearExpr, int] = 0\n",
" for d in all_days:\n",
" for s in all_shifts:\n",
" num_shifts_worked += shifts[(n, d, s)]\n",