update vrp samples

This commit is contained in:
Laurent Perron
2025-01-30 14:28:07 +01:00
parent 88c61c005b
commit 1731cb66bd
12 changed files with 95 additions and 83 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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())

View File

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

View File

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