Many real fleets have vehicles that can be physically reconfigured between trips: a van whose seats fold to trade passenger slots for cargo space, a truck with removable refrigerated sections, a vehicle with an optional trailer. Each configuration is a different capacity vector, and a vehicle operates a whole route in exactly one of them.
This isn't expressible today. Multi-dimensional capacity tracks several metrics at once, but the limits are fixed — there's no way to say "10 pallets or 6 pallets + 2 fridge slots, your choice per route". Modeling each configuration as a duplicate vehicle doesn't work either, since nothing stops the solver from using two clones of the same physical vehicle simultaneously.
Proposal
A capacity_profiles key on vehicles, mutually exclusive with capacity:
"vehicles": [{
"id": 1,
"capacity_profiles": [
{ "name": "standard", "capacity": [10, 0] },
{ "name": "mixed", "capacity": [6, 2] }
]
}]
Semantics: a set of tasks is feasible for a vehicle iff at least one profile fits the resulting load at every route step — a single profile applies to the whole route. The output reports the profile in use via a capacity_profile route key (first fitting profile in input order, so users order by preference).
Note this is strictly stronger than checking each step against the component-wise max of the profiles: a peak load of [8, 1] fits the max envelope of the example above but fits neither profile, and must be rejected.
Implementation
I have a working implementation on a fork branch: https://github.com/Desert-Acacia/vroom/tree/feature/capacity-profiles
Design in brief:
Vehicle::capacity becomes the component-wise max over profiles — still a sound necessary condition, so all existing pre-filters (margins, quick load checks in local search) are untouched.
- The exact feasibility checks in
RawRoute::is_valid_addition_for_capacity* require a single profile to satisfy all conditions (std::ranges::any_of over profiles). Same for the direct checks in RouteExchange::is_valid, route split, and initial-route validation.
- With no
capacity_profiles, the profile vector holds the single plain capacity, so the default path is behaviorally identical to today. Existing inputs are unaffected.
- In plan mode, load violations are reported against the profile minimizing the number of violated steps.
Open questions I'd appreciate input on before opening a PR:
- Naming — happy to adjust
capacity_profiles / capacity_profile if you prefer something that avoids overloading "profile" (which already means routing profile).
- Plan-mode profile selection (minimize violated steps) — is that the semantics you'd want, or would e.g. minimizing total overload be preferable?
- Performance — the single-profile hot path is a one-element
any_of over the same comparisons as today, but I'm glad to run the benchmark instances and post numbers with the PR.
If this fits the project's scope, I'll clean the branch up against current master and submit a PR.
Many real fleets have vehicles that can be physically reconfigured between trips: a van whose seats fold to trade passenger slots for cargo space, a truck with removable refrigerated sections, a vehicle with an optional trailer. Each configuration is a different capacity vector, and a vehicle operates a whole route in exactly one of them.
This isn't expressible today. Multi-dimensional
capacitytracks several metrics at once, but the limits are fixed — there's no way to say "10 pallets or 6 pallets + 2 fridge slots, your choice per route". Modeling each configuration as a duplicate vehicle doesn't work either, since nothing stops the solver from using two clones of the same physical vehicle simultaneously.Proposal
A
capacity_profileskey on vehicles, mutually exclusive withcapacity:Semantics: a set of tasks is feasible for a vehicle iff at least one profile fits the resulting load at every route step — a single profile applies to the whole route. The output reports the profile in use via a
capacity_profileroute key (first fitting profile in input order, so users order by preference).Note this is strictly stronger than checking each step against the component-wise max of the profiles: a peak load of
[8, 1]fits the max envelope of the example above but fits neither profile, and must be rejected.Implementation
I have a working implementation on a fork branch: https://github.com/Desert-Acacia/vroom/tree/feature/capacity-profiles
Design in brief:
Vehicle::capacitybecomes the component-wise max over profiles — still a sound necessary condition, so all existing pre-filters (margins, quick load checks in local search) are untouched.RawRoute::is_valid_addition_for_capacity*require a single profile to satisfy all conditions (std::ranges::any_ofover profiles). Same for the direct checks inRouteExchange::is_valid, route split, and initial-route validation.capacity_profiles, the profile vector holds the single plain capacity, so the default path is behaviorally identical to today. Existing inputs are unaffected.Open questions I'd appreciate input on before opening a PR:
capacity_profiles/capacity_profileif you prefer something that avoids overloading "profile" (which already means routing profile).any_ofover the same comparisons as today, but I'm glad to run the benchmark instances and post numbers with the PR.If this fits the project's scope, I'll clean the branch up against current master and submit a PR.