From 1731cb66bd3067e093f904089fae1c94b7f886f2 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Thu, 30 Jan 2025 14:28:07 +0100 Subject: [PATCH] update vrp samples --- .../constraint_solver/samples/cvrp_reload.py | 53 ++++++++++--------- .../constraint_solver/samples/cvrptw_break.py | 16 +++--- ortools/constraint_solver/samples/vrp.py | 10 ++-- .../samples/vrp_breaks_from_start.py | 13 ++--- .../samples/vrp_global_span.py | 11 ++-- .../samples/vrp_initial_routes.py | 1 - .../samples/vrp_items_to_deliver.py | 13 +++-- .../constraint_solver/samples/vrp_node_max.py | 2 - .../samples/vrp_nodes_indices.py | 18 ++++--- .../samples/vrp_solution_callback.py | 13 ++--- .../samples/vrp_time_windows_per_vehicles.py | 24 +++++---- .../samples/vrptw_store_solution_data.py | 4 -- 12 files changed, 95 insertions(+), 83 deletions(-) diff --git a/ortools/constraint_solver/samples/cvrp_reload.py b/ortools/constraint_solver/samples/cvrp_reload.py index a86316b39c..fee9315eed 100755 --- a/ortools/constraint_solver/samples/cvrp_reload.py +++ b/ortools/constraint_solver/samples/cvrp_reload.py @@ -15,31 +15,31 @@ # limitations under the License. """Capacitated Vehicle Routing Problem (CVRP). - This is a sample using the routing library python wrapper to solve a CVRP - problem while allowing multiple trips, i.e., vehicles can return to a depot - to reset their load ("reload"). +This is a sample using the routing library python wrapper to solve a CVRP +problem while allowing multiple trips, i.e., vehicles can return to a depot +to reset their load ("reload"). - A description of the CVRP problem can be found here: - http://en.wikipedia.org/wiki/Vehicle_routing_problem. +A description of the CVRP problem can be found here: +http://en.wikipedia.org/wiki/Vehicle_routing_problem. - Distances are in meters. +Distances are in meters. - In order to implement multiple trips, new nodes are introduced at the same - locations of the original depots. These additional nodes can be dropped - from the schedule at 0 cost. +In order to implement multiple trips, new nodes are introduced at the same +locations of the original depots. These additional nodes can be dropped +from the schedule at 0 cost. - The max_slack parameter associated to the capacity constraints of all nodes - can be set to be the maximum of the vehicles' capacities, rather than 0 like - in a traditional CVRP. Slack is required since before a solution is found, - it is not known how much capacity will be transferred at the new nodes. For - all the other (original) nodes, the slack is then re-set to 0. +The max_slack parameter associated to the capacity constraints of all nodes +can be set to be the maximum of the vehicles' capacities, rather than 0 like +in a traditional CVRP. Slack is required since before a solution is found, +it is not known how much capacity will be transferred at the new nodes. For +all the other (original) nodes, the slack is then re-set to 0. - The above two considerations are implemented in `add_capacity_constraints()`. +The above two considerations are implemented in `add_capacity_constraints()`. - Last, it is useful to set a large distance between the initial depot and the - new nodes introduced, to avoid schedules having spurious transits through - those new nodes unless it's necessary to reload. This consideration is taken - into account in `create_distance_evaluator()`. +Last, it is useful to set a large distance between the initial depot and the +new nodes introduced, to avoid schedules having spurious transits through +those new nodes unless it's necessary to reload. This consideration is taken +into account in `create_distance_evaluator()`. """ from functools import partial @@ -336,31 +336,34 @@ def print_solution( continue index = routing.Start(vehicle_id) plan_output = f"Route for vehicle {vehicle_id}:\n" + load_value = 0 distance = 0 while not routing.IsEnd(index): - load_var = capacity_dimension.CumulVar(index) time_var = time_dimension.CumulVar(index) plan_output += ( f" {manager.IndexToNode(index)} " - f"Load({assignment.Min(load_var)}) " + f"Load({assignment.Min(capacity_dimension.CumulVar(index))}) " f"Time({assignment.Min(time_var)},{assignment.Max(time_var)}) ->" ) previous_index = index index = assignment.Value(routing.NextVar(index)) distance += distance_dimension.GetTransitValue(previous_index, index, vehicle_id) - load_var = capacity_dimension.CumulVar(index) + # capacity dimension TransitVar is negative at reload stations during replenishment + # don't want to consider those values when calculating the total load of the route + # hence only considering the positive values + load_value += max(0, capacity_dimension.GetTransitValue(previous_index, index, vehicle_id)) time_var = time_dimension.CumulVar(index) plan_output += ( f" {manager.IndexToNode(index)} " - f"Load({assignment.Min(load_var)}) " + f"Load({assignment.Min(capacity_dimension.CumulVar(index))}) " f"Time({assignment.Min(time_var)},{assignment.Max(time_var)})\n" ) plan_output += f"Distance of the route: {distance}m\n" - plan_output += f"Load of the route: {assignment.Min(load_var)}\n" + plan_output += f"Load of the route: {load_value}\n" plan_output += f"Time of the route: {assignment.Min(time_var)}min\n" print(plan_output) total_distance += distance - total_load += assignment.Min(load_var) + total_load += load_value total_time += assignment.Min(time_var) print(f"Total Distance of all routes: {total_distance}m") print(f"Total Load of all routes: {total_load}") diff --git a/ortools/constraint_solver/samples/cvrptw_break.py b/ortools/constraint_solver/samples/cvrptw_break.py index c3922567f9..f0a391dbc2 100755 --- a/ortools/constraint_solver/samples/cvrptw_break.py +++ b/ortools/constraint_solver/samples/cvrptw_break.py @@ -15,12 +15,12 @@ # [START program] """Capacitated Vehicle Routing Problem with Time Windows (CVRPTW). - This is a sample using the routing library python wrapper to solve a CVRPTW - problem. - A description of the problem can be found here: - http://en.wikipedia.org/wiki/Vehicle_routing_problem. +This is a sample using the routing library python wrapper to solve a CVRPTW +problem. +A description of the problem can be found here: +http://en.wikipedia.org/wiki/Vehicle_routing_problem. - Distances are in meters and time in minutes. +Distances are in meters and time in minutes. """ # [START import] @@ -329,7 +329,11 @@ def main(): vehicle_break = data["breaks"][v] break_intervals[v] = [ routing.solver().FixedDurationIntervalVar( - 15, 100, vehicle_break[0], vehicle_break[1], f"Break for vehicle {v}" + 15, + 100, + vehicle_break[0], + vehicle_break[1], + f"Break for vehicle {v}", ) ] time_dimension.SetBreakIntervalsOfVehicle( diff --git a/ortools/constraint_solver/samples/vrp.py b/ortools/constraint_solver/samples/vrp.py index 011a422004..2a4d857a34 100755 --- a/ortools/constraint_solver/samples/vrp.py +++ b/ortools/constraint_solver/samples/vrp.py @@ -15,12 +15,12 @@ # [START program] """Simple Vehicles Routing Problem (VRP). - This is a sample using the routing library python wrapper to solve a VRP - problem. - A description of the problem can be found here: - http://en.wikipedia.org/wiki/Vehicle_routing_problem. +This is a sample using the routing library python wrapper to solve a VRP +problem. +A description of the problem can be found here: +http://en.wikipedia.org/wiki/Vehicle_routing_problem. - Distances are in meters. +Distances are in meters. """ # [START import] diff --git a/ortools/constraint_solver/samples/vrp_breaks_from_start.py b/ortools/constraint_solver/samples/vrp_breaks_from_start.py index eb1be8b1dd..0792b901cd 100755 --- a/ortools/constraint_solver/samples/vrp_breaks_from_start.py +++ b/ortools/constraint_solver/samples/vrp_breaks_from_start.py @@ -13,14 +13,15 @@ # limitations under the License. # [START program] """Vehicles Routing Problem (VRP) with breaks relative to the vehicle start time. - Each vehicles start at T:15min, T:30min, T:45min and T:60min respectively. - Each vehicle must perform a break lasting 5 minutes, - starting between 25 and 45 minutes after route start. - e.g. vehicle 2 starting a T:45min must start a 5min breaks - between [45+25,45+45] i.e. in the range [70, 90]. +Each vehicles start at T:15min, T:30min, T:45min and T:60min respectively. - Durations are in minutes. +Each vehicle must perform a break lasting 5 minutes, +starting between 25 and 45 minutes after route start. +e.g. vehicle 2 starting a T:45min must start a 5min breaks +between [45+25,45+45] i.e. in the range [70, 90]. + +Durations are in minutes. """ # [START import] diff --git a/ortools/constraint_solver/samples/vrp_global_span.py b/ortools/constraint_solver/samples/vrp_global_span.py index 3d88b0dbee..cb37a2ad95 100755 --- a/ortools/constraint_solver/samples/vrp_global_span.py +++ b/ortools/constraint_solver/samples/vrp_global_span.py @@ -15,12 +15,12 @@ # [START program] """Simple Vehicles Routing Problem (VRP). - This is a sample using the routing library python wrapper to solve a VRP - problem. - A description of the problem can be found here: - http://en.wikipedia.org/wiki/Vehicle_routing_problem. +This is a sample using the routing library python wrapper to solve a VRP +problem. +A description of the problem can be found here: +http://en.wikipedia.org/wiki/Vehicle_routing_problem. - Distances are in meters. +Distances are in meters. """ # [START import] @@ -85,7 +85,6 @@ def print_solution(data, manager, routing, solution): max_route_distance = max(route_distance, max_route_distance) print(f"Maximum of the route distances: {max_route_distance}m") - # [END solution_printer] diff --git a/ortools/constraint_solver/samples/vrp_initial_routes.py b/ortools/constraint_solver/samples/vrp_initial_routes.py index eecccb054c..a70634205b 100755 --- a/ortools/constraint_solver/samples/vrp_initial_routes.py +++ b/ortools/constraint_solver/samples/vrp_initial_routes.py @@ -87,7 +87,6 @@ def print_solution(data, manager, routing, solution): max_route_distance = max(route_distance, max_route_distance) print(f"Maximum of the route distances: {max_route_distance}m") - # [END solution_printer] diff --git a/ortools/constraint_solver/samples/vrp_items_to_deliver.py b/ortools/constraint_solver/samples/vrp_items_to_deliver.py index 7225f0113f..b64917b982 100755 --- a/ortools/constraint_solver/samples/vrp_items_to_deliver.py +++ b/ortools/constraint_solver/samples/vrp_items_to_deliver.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # [START program] """Vehicles Routing Problem (VRP) for delivering items from any suppliers. -Description: -Need to deliver some item X and Y at end nodes (at least 11 X and 13 Y). -Several locations provide them and even few provide both. + +Description: Need to deliver some item X and Y at end nodes (at least 11 X and +13 Y). Several locations provide them and even few provide both. fleet: * vehicles: 2 @@ -465,8 +465,11 @@ def main(): # Create the routing index manager. # [START index_manager] - manager = pywrapcp.RoutingIndexManager( - len(data["distance_matrix"]), data["num_vehicles"], data["starts"], data["ends"] + manager = pywraprouting.RoutingIndexManager( + len(data["distance_matrix"]), + data["num_vehicles"], + data["starts"], + data["ends"], ) # [END index_manager] diff --git a/ortools/constraint_solver/samples/vrp_node_max.py b/ortools/constraint_solver/samples/vrp_node_max.py index 5c1092382b..8bead8183e 100755 --- a/ortools/constraint_solver/samples/vrp_node_max.py +++ b/ortools/constraint_solver/samples/vrp_node_max.py @@ -75,7 +75,6 @@ def create_data_model(): data["depot"] = 0 return data - # [END data_model] @@ -121,7 +120,6 @@ def print_solution(data, manager, routing, solution): max_route_distance = max(route_distance, max_route_distance) print(f"Maximum of the route distances: {max_route_distance}m") - # [END solution_printer] diff --git a/ortools/constraint_solver/samples/vrp_nodes_indices.py b/ortools/constraint_solver/samples/vrp_nodes_indices.py index 6d6fc605a4..94ccf77612 100755 --- a/ortools/constraint_solver/samples/vrp_nodes_indices.py +++ b/ortools/constraint_solver/samples/vrp_nodes_indices.py @@ -18,11 +18,15 @@ This script generate few markdown tables to better understand the relation between nodes and indices. Things to notice: -* 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. -* Solver needs to "create" an index for a vehicle 1 start since solver need an unique start index per vehicle. +* 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. +* Solver needs to "create" an index for a vehicle 1 start since solver need an +unique start index per vehicle. * All end nodes are moved to the end of the index list aka [15, 16, 17, 18]. -* routing.Size() return the number of node which are not end nodes (here 15 aka [0-14]) -note: using the two properties above, we know that any index in range(routing.Size()) is not a vehicle end node. +* routing.Size() return the number of node which are not end nodes (here 15 aka +[0-14]) +note: using the two properties above, we know that any index in +range(routing.Size()) is not a vehicle end node. * Since end nodes are moved to the end, their respective "empty" node index are reused so all locations indices are "shifted" @@ -31,9 +35,11 @@ e.g. node 9 is mapped to index 6 e.g. start node 7 mapped to index 4 Takeaway: -* Allways use routing.Start(), routing.End(), manager.IndexToNode() or manager.NodeToIndex(). +* Allways use routing.Start(), routing.End(), manager.IndexToNode() or +manager.NodeToIndex(). * Location node is not necessarily equal to its index. -* To loop through ALL indices use manager.GetNumberOfIndices() (Python) or manager::num_indices() (C++) +* To loop through ALL indices use manager.GetNumberOfIndices() (Python) or +manager::num_indices() (C++) """ from ortools.constraint_solver import routing_enums_pb2 diff --git a/ortools/constraint_solver/samples/vrp_solution_callback.py b/ortools/constraint_solver/samples/vrp_solution_callback.py index 7d1093e068..a95dcb3ac3 100755 --- a/ortools/constraint_solver/samples/vrp_solution_callback.py +++ b/ortools/constraint_solver/samples/vrp_solution_callback.py @@ -15,12 +15,12 @@ # [START program] """Simple Vehicles Routing Problem (VRP). - This is a sample using the routing library python wrapper to solve a VRP - problem. +This is a sample using the routing library python wrapper to solve a VRP +problem. - The solver stop after improving its solution 15 times or after 5 seconds. +The solver stop after improving its solution 15 times or after 5 seconds. - Distances are in meters. +Distances are in meters. """ # [START import] @@ -90,7 +90,6 @@ def print_solution( total_distance += route_distance print(f"Total Distance of all routes: {total_distance}m") - # [END solution_callback_printer] @@ -112,7 +111,9 @@ class SolutionCallback: self.objectives = [] def __call__(self): - objective = int(self._routing_model_ref().CostVar().Value()) + objective = int( + self._routing_model_ref().CostVar().Value() + ) # pytype: disable=attribute-error if not self.objectives or objective < self.objectives[-1]: self.objectives.append(objective) print_solution(self._routing_manager_ref(), self._routing_model_ref()) diff --git a/ortools/constraint_solver/samples/vrp_time_windows_per_vehicles.py b/ortools/constraint_solver/samples/vrp_time_windows_per_vehicles.py index ebf9bb8115..c814af54cc 100755 --- a/ortools/constraint_solver/samples/vrp_time_windows_per_vehicles.py +++ b/ortools/constraint_solver/samples/vrp_time_windows_per_vehicles.py @@ -14,19 +14,19 @@ # [START program] """Vehicles Routing Problem (VRP) with Time Window (TW) per vehicle. - All time are in minutes using 0am as origin - e.g. 8am = 480, 11am = 660, 1pm = 780 ... +All time are in minutes using 0am as origin +e.g. 8am = 480, 11am = 660, 1pm = 780 ... - We have 1 depot (0) and 16 locations (1-16). - We have a fleet of 4 vehicles (0-3) whose working time is [480, 1020] (8am-5pm) - We have the distance matrix between these locations and depot. - We have a service time of 25min at each location. +We have 1 depot (0) and 16 locations (1-16). +We have a fleet of 4 vehicles (0-3) whose working time is [480, 1020] (8am-5pm) +We have the distance matrix between these locations and depot. +We have a service time of 25min at each location. - Locations are duplicated so we can simulate a TW per vehicle. - location: [01-16] vehicle: 0 TW: [540, 660] (9am-11am) - location: [17-32] vehicle: 1 TW: [660, 780] (11am-1pm) - location: [33-48] vehicle: 2 TW: [780, 900] (1pm-3pm) - location: [49-64] vehicle: 3 TW: [900, 1020] (3pm-5pm) +Locations are duplicated so we can simulate a TW per vehicle. +location: [01-16] vehicle: 0 TW: [540, 660] (9am-11am) +location: [17-32] vehicle: 1 TW: [660, 780] (11am-1pm) +location: [33-48] vehicle: 2 TW: [780, 900] (1pm-3pm) +location: [49-64] vehicle: 3 TW: [900, 1020] (3pm-5pm) """ # [START import] @@ -88,6 +88,8 @@ def print_solution(manager, routing, assignment): time_dimension = routing.GetDimensionOrDie("Time") total_time = 0 for vehicle_id in range(manager.GetNumberOfVehicles()): + if not routing.IsVehicleUsed(assignment, vehicle_id): + continue plan_output = f"Route for vehicle {vehicle_id}:\n" index = routing.Start(vehicle_id) start_time = 0 diff --git a/ortools/constraint_solver/samples/vrptw_store_solution_data.py b/ortools/constraint_solver/samples/vrptw_store_solution_data.py index bab40b558f..6abc76ce83 100755 --- a/ortools/constraint_solver/samples/vrptw_store_solution_data.py +++ b/ortools/constraint_solver/samples/vrptw_store_solution_data.py @@ -69,7 +69,6 @@ def create_data_model(): data["depot"] = 0 return data - # [END data_model] @@ -110,7 +109,6 @@ def print_solution(routes, cumul_data): route_str += f"Total time: {total_time}min" print(route_str) - # [END solution_printer] @@ -129,7 +127,6 @@ def get_routes(solution, routing, manager): routes.append(route) return routes - # [END get_routes] @@ -154,7 +151,6 @@ def get_cumul_data(solution, routing, dimension): cumul_data.append(route_data) return cumul_data - # [END get_cumulative_data]