Skip to content

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:

Route 0: duration = 1200 s (max: 3600 s)
Route 1: duration = 2300 s (max: 3600 s)

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:

solution = router.solve(
    instance,
    penalty_duration=100.0,
    penalty_distance=100.0,
)

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.