Skip to content

Defining a Problem

A routing problem in TQrouting consists of customers to visit, a fleet of vehicles, and distance/duration matrices describing travel costs between locations.

VRPInstance

VRPInstance is the main container for a complete problem definition.

from tqrouting import VRPInstance, Customer, VehicleType, Location

instance = VRPInstance(
    customers=[...],           # List of customers to serve
    fleet=[...],               # List of vehicle types available
    duration_matrix=[...],     # Travel times between locations
    distance_matrix=[...],     # Travel distances between locations
)

Distance vs. Duration Matrix

You must provide at least one of these two matrices. The solver always optimizes travel duration (the duration_matrix drives the objective function and all time-related scheduling). If you only provide distance_matrix, the solver treats it as the duration matrix as well. The max_distance constraint is measured on the distance_matrix — the only place the distance matrix is used on its own. Provide both matrices when your distances and durations differ.


Location

Every customer and vehicle depot references a location. You can either provide a precomputed distance/duration matrix and reference locations by their matrix index (matrix_id), or provide coordinates and let TQrouting compute the matrices for you.

TQrouting supports two types of coordinates: Cartesian coordinates (x, y) for use with Euclidean distances, and geographic coordinates (lat, lon) for routing within street networks. When geographic coordinates are used, you can use a matrix provider (OSRM or Google Maps) to compute road-network distances and travel times.

x/y coordinates are all-or-none: once one location has them, every location — customers, start and end locations — needs them as well, and a partial set raises a ValidationError naming the location without them. A location may carry a matrix_id and coordinates at the same time.

Option 1: Matrix Index (Direct)

Assign each location a position in your distance/duration matrix:

Location(matrix_id=0)   # Row/column 0 in the matrix
Location(matrix_id=5)   # Row/column 5 in the matrix

Option 2: Cartesian Coordinates

For problems common in academic benchmarks (Euclidean distances), synthetic instances, or intra-logistics scenarios (custom distance and duration matrices). Use with EuclideanMatrixProvider or custom distance and duration matrices.

Location(x=20.5, y=35.0)
Location(x=42.0, y=18.7)

Option 3: Geographic Coordinates

For routing within street networks. Use with a matrix provider (OSRM, Google Maps):

Location(lat=48.137, lon=11.575)   # Munich city center
Location(lat=48.265, lon=11.671)   # Munich airport

Customer

A Customer defines a stop that must (or may, in prize-collecting problems) be served. Each customer has one required attribute (location) and several optional attributes.

Customer Attributes

Field Type Required Description
location Location Yes Customer location
id str or int No Your custom identifier (order number, database ID, etc.)
delivery_load float No Units to deliver to this customer (weight dimension)
pickup_load float No Units to pick up from this customer (weight dimension)
delivery_volume float No Second capacity dimension (volume) to deliver — see 2-D capacity
pickup_volume float No Second capacity dimension (volume) to pick up
service_time float, int, or str No Time spent serving this customer
time_window TimeWindow No Allowed visit window
profit float No Reward for visiting (for prize-collecting problems)

Attribute Details:

  • location is required and specifies where the customer is located. See the Location section above for the supported formats.

  • id is an optional custom identifier for this customer. If you set id on any customer, you must set it on all customers. IDs must be unique within customers and can be strings or integers. After solving, the solution uses your custom IDs (e.g., visit.customer_id returns "ORD-001").

  • delivery_load represents the amount of goods to be delivered to this customer. If provided, it activates the Capacitated VRP: the total delivery load on a route must not exceed the vehicle's capacity_load. A vehicle type without capacity_load is covered in Two-dimensional capacity. The value must be a non-negative float, measured in the same unit as the vehicle capacity_load (e.g., kilograms). Unless the problem has a prize-collecting character, each customer's delivery_load should not exceed the capacity_load of the largest vehicle type.

  • pickup_load represents the amount of goods to be picked up from this customer. If at least one customer has a pickup_load greater than zero, the Pickup and Delivery mode is automatically activated. In this mode, a vehicle starts its route fully loaded with all goods to be delivered. As it visits delivery customers, the onboard load decreases; at pickup customers, the onboard load increases. The capacity constraint ensures that the peak onboard load at any point along the route does not exceed the vehicle's capacity_load. A customer may have both delivery_load and pickup_load — in this case, the delivery is performed first, then the pickup.

  • delivery_volume / pickup_volume carry a second, independent quantity (typically volume) alongside the weight in delivery_load / pickup_load, constrained by the vehicle's capacity_volume. See Two-dimensional capacity.

  • service_time is the time spent servicing this customer (e.g., unloading goods, walking from parking to delivery point, obtaining a signature, etc.). The time unit must be consistent with other time-related parameters (time_window, max_duration, shift_start, shift_end). Accepts seconds as a number or a string in HH:MM:SS format.

  • time_window constrains when service can begin at this customer. If a vehicle arrives before the window opens, it waits. If no time_window is specified, the customer can be served at any time. See Time Windows for more details.

  • profit activates the prize-collecting mode when provided. The solver will select which customers to visit to maximize total collected profit. Profits must be non-negative values. Since the mode covers the whole instance, set profit on every customer or on none — a mix raises a ValidationError. See Prize-collecting for more details.

Example

from tqrouting import Customer, Location, TimeWindow

customer = Customer(
    location=Location(matrix_id=1),
    id="ORD-001",
    delivery_load=15.0,
    pickup_load=5.0,
    service_time="00:05:00",                    # 5 minutes
    time_window=TimeWindow(
        start="08:00:00",                       # 8:00 AM
        end="17:00:00",                         # 5:00 PM
    ),
    profit=100.0,
)

VehicleType

A VehicleType defines a type of vehicle in your fleet. You can have multiple vehicle types with different capabilities.

VehicleType Attributes

Field Type Required Description
id str or int No Your custom identifier
start_location Location Yes Depot / starting point
end_location Location No Return point. If omitted, routes end at the last customer (open-ended).
capacity_load float No Maximum load the vehicle can carry (weight dimension)
capacity_volume float No Second capacity dimension (volume) — see 2-D capacity
reload float, int, or str No Allow mid-route depot reloads (multi-trip); the value is the turnaround time per reload
fixed_vehicle_cost float No Cost added to the objective per used vehicle of this type (fleet minimization)
n_vehicles int No Number of available vehicles of this type
max_duration float, int, or str No Maximum route duration
max_distance float No Maximum route distance
shift_start float, int, or str No Earliest time the vehicle can depart
shift_end float, int, or str No Latest time the vehicle must return

Attribute Details:

  • id is an optional custom identifier. If you set id on any vehicle type, you must set it on all vehicle types. IDs must be unique within vehicle types and can be strings or integers.

  • start_location is required and specifies where vehicles of this type begin their routes. It follows the same format as customer locations (see Location).

  • end_location specifies where vehicles return after completing their route. If omitted, routes are open-ended — vehicles finish at their last customer and no return travel is added to the route duration or distance. If end_location is the same as start_location, vehicles return to their depot (the most common case).

  • capacity_load defines the maximum load the vehicle can carry. When set, the total delivery_load of all customers on a route must not exceed the capacity_load (or, in pickup-and-delivery mode, the peak onboard load must not exceed it). Should be measured in the same unit as customers' delivery_load / pickup_load. See Two-dimensional capacity for vehicle types without it.

  • capacity_volume is the vehicle's limit for the second quantity (volume). See Two-dimensional capacity.

  • reload allows vehicles of this type to return to their start_location mid-route, reload (the carried load resets), and continue — one vehicle running several trips in a single route. The value is the turnaround time spent at the depot per reload stop (0 = instant); accepts seconds or an HH:MM:SS string. Requires a capacity. See Multi-Trip Routes.

  • fixed_vehicle_cost adds a fixed cost to the objective for each used vehicle of this type, in the same units as the routing objective. It lets the solver trade extra travel against using fewer vehicles (fleet minimization). Vehicles that stay unused cost nothing. Defaults to 0 (off).

  • n_vehicles limits how many vehicles of this type are available. The solver uses only as many vehicles as needed — it won't create empty routes. If not specified, the number of vehicles is unlimited.

  • max_duration caps the total time of each route of this vehicle type. Route duration includes driving time, service time, and waiting time. It must use the same time unit as other time-related parameters. Accepts seconds as a number or a string in HH:MM:SS format.

  • max_distance caps the total travel distance of each route (sum of travel distances between consecutive stops, including from/to depot). Specified in the same unit as the distance matrix (e.g., kilometers). Does not accept time strings.

  • shift_start defines the earliest time vehicles of this type can depart. No vehicle of this type can start its route before this time.

  • shift_end defines the latest time vehicles of this type must return. No vehicle of this type can finish its route after this time.

Two-dimensional capacity

Demand and capacity can cover two independent quantities — for example weight and volume. Use the load fields for the first (delivery_load / pickup_load on customers, capacity_load on vehicles) and the volume fields for the second (delivery_volume / pickup_volume, capacity_volume). You can constrain just one of them or both.

An instance is two-dimensional when both quantities appear in it — in customer demands or in vehicle capacities. Then every vehicle type must set the capacity of each quantity your customers carry; leaving one out raises a ValidationError. With a single quantity, a vehicle type without its capacity carries unlimited load, and the SDK warns about it. Each quantity is enforced independently — a route is feasible only if it stays within every capacity you set.

# 2-D: each vehicle limited to 1000 kg AND 14 m³
Customer(location=Location(matrix_id=1), delivery_load=120, delivery_volume=1.8)
VehicleType(start_location=Location(matrix_id=0), capacity_load=1000, capacity_volume=14)

In pickup-and-delivery mode the peak-onboard-load rule is applied per dimension. See Capacity Constraints.

Example

from tqrouting import VehicleType, Location

vehicle = VehicleType(
    start_location=Location(matrix_id=0),
    id="truck-A",
    end_location=Location(matrix_id=0),
    capacity_load=1000,
    n_vehicles=5,
    max_duration="08:00:00",                    # 8 hours
    max_distance=50000,                         # 50 km
    shift_start="07:00:00",              # 7:00 AM
    shift_end="18:00:00",                # 6:00 PM
)

Time Windows

Time windows constrain when a customer can be served:

from tqrouting import TimeWindow

tw = TimeWindow(start="08:00:00", end="12:00:00")  # 8:00 AM to 12:00 PM

Time Format

Every time field takes either a number or a string in HH:MM:SS format, and one instance uses a single format throughout: all time fields as numbers, or all of them as strings. Mixing the two is a ValidationError that names both fields.

A string is a count of seconds written as hours:minutes:seconds. Hours are not limited to 24, so "30:00:00" is 108000 seconds, and durations use the same notation as instants. Because a string always means seconds, use strings only when your duration matrix is in seconds; with a matrix in any other unit, give times as numbers in that unit.

from tqrouting import Customer, VehicleType, Location, TimeWindow

# The same constraints in the two formats (one instance uses one of them):
customer_numeric = Customer(
    location=Location(matrix_id=1),
    service_time=900,                                  # 900 seconds
    time_window=TimeWindow(start=28800, end=61200),    # 8:00–17:00
)

customer_strings = Customer(
    location=Location(matrix_id=1),
    service_time="00:15:00",                           # 15 minutes → 900 s
    time_window=TimeWindow(start="08:00:00", end="17:00:00"),
)

# Works for vehicles too:
vehicle = VehicleType(
    start_location=Location(matrix_id=0),
    shift_start="07:30:00",         # → 27000 seconds
    shift_end="19:00:00",           # → 68400 seconds
    max_duration="10:00:00",        # → 36000 seconds (10 hours)
)

The following fields accept time strings:

  • TimeWindow.start, TimeWindow.end
  • Customer.service_time
  • VehicleType.shift_start, VehicleType.shift_end
  • VehicleType.max_duration
  • VehicleType.reload

The solution is written in the format the instance used — see Time Formatting.


Custom IDs

By default, TQrouting uses 0-based positional indices for customers and vehicles. You can assign your own IDs to match your domain data:

instance = VRPInstance(
    customers=[
        Customer(id="ORD-001", location=Location(matrix_id=1), delivery_load=9),
        Customer(id="ORD-002", location=Location(matrix_id=2), delivery_load=4),
    ],
    fleet=[
        VehicleType(id="truck-A", start_location=Location(matrix_id=0), capacity_load=20, n_vehicles=3),
        VehicleType(id="van-B", start_location=Location(matrix_id=0), capacity_load=10, n_vehicles=2),
    ],
    distance_matrix=[[0, 2, 5], [2, 0, 7], [5, 7, 0]],
)

solution = router.solve(instance)

for route in solution.routes:
    print(f"Vehicle: {route.vehicle_type_id}")        # "truck-A" or "van-B"
    for visit in route.visits:
        print(f"  → {visit.customer_id}")              # "ORD-001" or "ORD-002"

Rules for custom IDs:

  • If you set id on any customer, you must set it on all customers (same rule for vehicle types)
  • IDs must be unique within customers and within vehicle types
  • IDs can be strings or integers
  • Customers and vehicle types are separate namespaces, so the same ID may appear in both
  • Breaking either rule raises a ValidationError naming the entry — see Error Handling

Serialization

Convert between VRPInstance and dicts/JSON files:

# VRPInstance → dict
problem_dict = instance.to_dict()

# dict → VRPInstance
instance = VRPInstance.from_dict(problem_dict)

# VRPInstance → JSON file
instance.to_json("problem.json")

# JSON file → VRPInstance
instance = VRPInstance.from_json("problem.json")

The dict/JSON format uses the same field names as the Python classes (customers, fleet, duration_matrix, distance_matrix), so what you see in code is exactly what you get in JSON.

Unknown keys are an error: from_dict() / from_json() (and validate_input()) raise ValidationError naming the field and suggesting the closest valid name:

ValidationError: fleet[0]: unknown field 'max_durtion' (did you mean 'max_duration'?)

Keep your own metadata outside the instance document.


Next Steps