Skip to content

Heterogeneous Fleet

TQrouting supports heterogeneous fleets — you can define multiple vehicle types with different capacities, constraints, depot locations, and fleet sizes. The solver assigns customers to the best vehicle type automatically.


Different Vehicle Types

Define vehicle types with varying capabilities:

from tqrouting import TQRouter, VRPInstance, Customer, VehicleType, Location

router = TQRouter()

# Two depots: the trucks' warehouse (matrix_id 0) in the city centre and the
# vans' hub (matrix_id 1) in a suburb half an hour away. Durations in seconds.
instance = VRPInstance(
    customers=[
        Customer(location=Location(matrix_id=2), delivery_load=50),   # near the warehouse
        Customer(location=Location(matrix_id=3), delivery_load=15),   # near the warehouse
        Customer(location=Location(matrix_id=4), delivery_load=8),    # near the van hub
        Customer(location=Location(matrix_id=5), delivery_load=40),   # near the van hub
    ],
    fleet=[
        VehicleType(
            id="large-truck",
            start_location=Location(matrix_id=0),
            end_location=Location(matrix_id=0),
            capacity_load=200,
            max_duration="10:00:00",
            n_vehicles=2,
        ),
        VehicleType(
            id="small-van",
            start_location=Location(matrix_id=1),
            end_location=Location(matrix_id=1),
            capacity_load=50,
            max_duration="06:00:00",
            n_vehicles=4,
        ),
    ],
    duration_matrix=[
        [0, 1800, 600, 900, 2400, 2700],
        [1800, 0, 1500, 1200, 720, 480],
        [600, 1500, 0, 300, 2100, 2400],
        [900, 1200, 300, 0, 1800, 2100],
        [2400, 720, 2100, 1800, 0, 360],
        [2700, 480, 2400, 2100, 360, 0],
    ],
)

solution = router.solve(instance, time_limit=2)

for route in solution.routes:
    customers = [v.customer_id for v in route.visits]
    print(f"Route {route.id} ({route.vehicle_type_id}): customers {customers}, "
          f"load {route.route_delivery_load:g}, {route.route_duration / 60:.0f} min")

Output:

Route 0 (large-truck): customers [0, 1], load 65, 30 min
Route 1 (small-van): customers [3, 2], load 48, 26 min

The two customers near the warehouse go on a truck; the two near the suburban hub fit into one van (48 of 50 units), which is 40 minutes closer to them than any truck. Nothing forces this split — the solver picks it because it is the cheapest total.

Each vehicle type can have its own:

  • capacity_load (and capacity_volume for 2-D) — different load limits
  • fixed_vehicle_cost — different per-vehicle costs (drives fleet minimization)
  • max_duration — different shift lengths
  • max_distance — different range limits
  • shift_start / shift_end — different operating hours
  • n_vehicles — different fleet sizes
  • start_location / end_location — different depots

Minimizing fleet size

Set fixed_vehicle_cost on a vehicle type to charge a fixed cost each time one of its vehicles is used. The solver then balances that cost against routing cost, preferring to serve all customers with fewer vehicles when the saved fixed cost outweighs the extra travel:

VehicleType(start_location=Location(matrix_id=0), capacity_load=100, fixed_vehicle_cost=500)

With mixed types, giving each type a different fixed_vehicle_cost lets the solver choose the cheaper fleet composition.


Multiple Depots

Assign different vehicle types to different starting locations. The solver decides which depot should serve which customers:

instance = VRPInstance(
    customers=[
        Customer(location=Location(matrix_id=2), delivery_load=10),
        Customer(location=Location(matrix_id=3), delivery_load=8),
        Customer(location=Location(matrix_id=4), delivery_load=15),
        Customer(location=Location(matrix_id=5), delivery_load=12),
    ],
    fleet=[
        VehicleType(
            id="depot-north",
            start_location=Location(matrix_id=0),
            end_location=Location(matrix_id=0),
            capacity_load=30,
            n_vehicles=2,
        ),
        VehicleType(
            id="depot-south",
            start_location=Location(matrix_id=1),
            end_location=Location(matrix_id=1),
            capacity_load=30,
            n_vehicles=2,
        ),
    ],
    distance_matrix=[
        [0, 50, 10, 15, 40, 45],
        [50, 0, 45, 40, 12, 8],
        [10, 45, 0, 5, 35, 40],
        [15, 40, 5, 0, 30, 35],
        [40, 12, 35, 30, 0, 6],
        [45, 8, 40, 35, 6, 0],
    ],
)

solution = router.solve(instance)

for route in solution.routes:
    print(f"Route {route.id} (depot: {route.vehicle_type_id}):")
    customers = [v.customer_id for v in route.visits]
    print(f"  Customers: {customers}")

Open Routes (Different Start and End)

Vehicles can end at a different location than where they started:

VehicleType(
    start_location=Location(matrix_id=0),    # Starts at warehouse
    end_location=Location(matrix_id=5),      # Ends at parking lot
    n_vehicles=3,
)

One-Way Routes (No Return)

If you don't specify end_location, vehicles finish at their last customer — no return trip is added. This models scenarios like one-way deliveries or field workers who go home after their last stop.

VehicleType(
    start_location=Location(matrix_id=0),
    capacity_load=30,
    n_vehicles=5,
    # No end_location → route ends at last customer
)

Info

When end_location is not set, the solver does not add return travel to the route duration or distance.


Fleet Size

n_vehicles sets the maximum number of vehicles of that type. The solver uses as few vehicles as needed to serve all customers — it won't create empty routes.

fleet=[
    VehicleType(
        start_location=Location(matrix_id=0),
        capacity_load=30,
        n_vehicles=10,    # Up to 10 available, solver uses only what's needed
    ),
]

Interactive Example

See the constraints notebook for runnable multi-depot and heterogeneous fleet examples.