Route Duration & Distance Limits¶
Limit how long or how far each route can be. Useful for shift lengths, driver hour regulations, fuel constraints, or electric vehicle range.
Duration Limits¶
Set max_duration on a vehicle type to cap the total time of its routes.
Route duration = driving time + service time + waiting time.
from tqrouting import TQRouter, VRPInstance, Customer, VehicleType, Location
router = TQRouter()
instance = VRPInstance(
customers=[
Customer(location=Location(matrix_id=1), delivery_load=10, service_time="00:10:00"),
Customer(location=Location(matrix_id=2), delivery_load=8, service_time="00:05:00"),
Customer(location=Location(matrix_id=3), delivery_load=12, service_time="00:07:30"),
Customer(location=Location(matrix_id=4), delivery_load=6, service_time="00:03:20"),
],
fleet=[
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
capacity_load=30,
max_duration="01:00:00", # 1 hour maximum per route
n_vehicles=4,
),
],
duration_matrix=[
[0, 300, 500, 400, 600],
[300, 0, 250, 350, 400],
[500, 250, 0, 200, 300],
[400, 350, 200, 0, 150],
[600, 400, 300, 150, 0],
],
)
solution = router.solve(instance, time_limit=2)
for route in solution.routes:
print(f"Route {route.id}: duration = {route.route_duration:.0f} s (max: 3600 s)")
Output:
You can use time strings instead of seconds:
VehicleType(
start_location=Location(matrix_id=0),
max_duration="08:00:00", # 8-hour maximum route duration
n_vehicles=5,
)
Distance Limits¶
Set max_distance on a vehicle type to cap the total travel distance of its routes.
Route distance = sum of all driving distances on the route.
instance = VRPInstance(
customers=[
Customer(location=Location(matrix_id=1), delivery_load=10),
Customer(location=Location(matrix_id=2), delivery_load=8),
Customer(location=Location(matrix_id=3), delivery_load=12),
],
fleet=[
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
capacity_load=30,
max_distance=50000, # 50 km maximum per route
n_vehicles=3,
),
],
distance_matrix=[
[0, 10000, 25000, 18000],
[10000, 0, 15000, 20000],
[25000, 15000, 0, 12000],
[18000, 20000, 12000, 0],
],
)
solution = router.solve(instance, time_limit=2)
Note
max_distance is specified in the same unit as your distance matrix (typically meters). It does not accept time strings.
Vehicle Shift Window¶
shift_start and shift_end define when the vehicle can operate:
- The vehicle cannot depart before
shift_start - The vehicle must return to its end location by
shift_end
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
shift_start="07:00:00", # Cannot leave before 7 AM
shift_end="17:00:00", # Must be back by 5 PM
n_vehicles=5,
)
This effectively constrains the route to fit within a time window for the vehicle itself.
Note
The vehicle shift window is also covered in Time Windows. Both pages describe the same constraint for convenience.
Combining Limits¶
You can use max_duration, max_distance, and a shift window on the same vehicle type — all constraints are enforced simultaneously:
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
max_duration="08:00:00", # 8-hour shift
max_distance=100000, # 100 km range
shift_start="06:00:00", # Earliest departure
shift_end="18:00:00", # Latest return
n_vehicles=5,
)
Schedule Awareness¶
When a route has a deadline — a shift_end or customer time windows — the solver computes departure bounds and arrival windows for it. Take the duration-limited fleet from the first example and give it a shift:
fleet=[
VehicleType(
start_location=Location(matrix_id=0),
end_location=Location(matrix_id=0),
capacity_load=30,
max_duration="01:00:00",
shift_start="08:00:00",
shift_end="12:00:00",
n_vehicles=4,
),
]
Because the input uses time strings, to_dict() reports the bounds in the same format:
solution = router.solve(instance, time_limit=2)
for route in solution.to_dict()["routes"]:
print(f"Route {route['id']}: {route['route_duration']} long, "
f"can depart between {route['route_earliest_departure']} and {route['route_latest_departure']}")
# Override the departure time and recalculate all ETAs
solution.set_departure_time(route_id=0, departure_time="09:30:00")
Output:
Route 0: 00:20:00 long, can depart between 08:00:00 and 11:40:00
Route 1: 00:38:20 long, can depart between 08:00:00 and 11:21:40
max_duration on its own only bounds the earliest departure (a route that would overrun the limit has to start later). Without a shift_end or time windows nothing bounds the end of the route, and route_latest_departure stays None.
See Reading Results → Schedule Awareness for the full reference.
Tuning¶
If the solver has difficulty finding feasible solutions with tight route limits, increase the corresponding penalty weights:
The shift window has its own weight, penalty_shift_end, for overtime past shift_end. Any of these limits can also be made soft (priced instead of enforced) via soft_constraints=["max_duration", "max_distance", "shift_end"] — see Soft Constraints.
Interactive Example
See the constraints notebook for examples combining duration limits with other constraints.