Skip to content

Conversation

cyril23
Copy link

@cyril23 cyril23 commented Oct 13, 2025

Summary

This PR fixes a regression introduced in v9.9 (commit 847cfc9) where FinalizeAllowedVehicles() incorrectly excluded vehicles from nodes with negative unary transits, causing heterogeneous VRP problems with reload/load-tracking dimensions to produce suboptimal solutions with excessive dropped stops.

The Bug

The optimization in FinalizeAllowedVehicles() checked:

if (std::abs(transit) <= capacity) continue;

This is incorrect for negative transits. A vehicle with capacity=50000 was excluded from nodes with transit=-70000, even when slack_max=140000 was sufficient to absorb the negative transit.

Why PR #4853 Was Incorrect

The previous fix attempted to skip all negative transits:

if (transit <= 0 || transit <= capacity) continue;

As @lperron correctly noted, this breaks feasibility guarantees. A negative transit with abs(transit) > slack_max should genuinely exclude vehicles, but that fix allowed them through.

The Correct Fix

Negative transits are feasible when the vehicle can absorb them via combined capacity and slack:

const int64_t min_allowed_transit = CapSub(0, CapAdd(capacity, slack_max));
if (transit <= capacity && transit >= min_allowed_transit) continue;

This translates to:

  • Positive transits: transit ≤ capacity (must fit in vehicle)
  • Negative transits: abs(transit) ≤ capacity + slack_max (absorbed via capacity headroom + slack)

This is mathematically correct: a vehicle starting with cumul = capacity can decrease by up to capacity + slack_max before violating the lower bound (0).

Regression Test

A regression test reproducing the exact bug is available: test_issue_4133_regression.py

Results with this fix:

[PASS] No drops and multiple vehicle classes used (4 tours, vehicles [0, 1, 2, 3]) with OR-Tools 10.0.9999

Expected results on v9.9-9.14 (broken versions):

[FAIL] Regression detected: dropped customers [1, 8, 14, 16, 17, 18, 19, 20, 21, 22, 23, 28, 29] with OR-Tools 9.14.6206

Impact

Fixes routing problems affected for 20+ months (v9.9.3963 through v9.14.6206):

  • Heterogeneous VRP with reload dimensions
  • Multi-class pickup/delivery with load-tracking dimensions
  • Any scenario using negative unary transits for algorithmic purposes

Fixes #4133

…ehicles with negative transits

The FinalizeAllowedVehicles() function was using abs() on transit values to check
against vehicle capacity, which incorrectly excluded vehicles when:
- CARGO_LOAD dimensions use negative constants for cumulative load tracking
- Reload dimensions use negative values at landfills/depots

This fix modifies the capacity check to only apply to positive transit values,
since negative transits are used for algorithmic purposes (reload semantics,
load tracking) rather than actual cargo capacity requirements.

Changes:
- Lines 1414-1421: Pre-processing loop now only considers positive transits
  for max_node_transit calculation
- Lines 1444-1448: Main capacity check now skips negative transits entirely

This resolves the regression introduced in commit 847cfc9 (Dec 1, 2023)
affecting OR-Tools versions 9.9.3963 through 9.14.6206.

Fixes: google#4133
Testing: test_ortools_version_standalone_codex.py (multi-class VRP with load tracking)
…llowedVehicles

The FinalizeAllowedVehicles() optimization incorrectly excluded vehicles
from nodes with negative unary transits. The bug checked abs(transit)
against capacity alone, but negative transits are feasible when
abs(transit) <= capacity + slack_max (vehicle can absorb the negative
transit via both capacity headroom and dimension slack).

This caused heterogeneous VRP problems with reload/load-tracking
dimensions to incorrectly exclude smaller vehicles, forcing suboptimal
solutions with dropped stops.

The fix properly bounds negative transits by combining capacity and
slack_max, while positive transits continue to check capacity alone.

Fixes google#4133
cyril23 added a commit to LogicsSoftwareGmbH/or-tools that referenced this pull request Oct 13, 2025
…n FinalizeAllowedVehicles (v9.12)

Backports the fix from PR google#4868 to v9.12 branch.

The FinalizeAllowedVehicles() optimization incorrectly excluded vehicles
from nodes with negative unary transits. The bug checked abs(transit)
against capacity alone, but negative transits are feasible when
abs(transit) <= capacity + slack_max (vehicle can absorb the negative
transit via both capacity headroom and dimension slack).

This caused heterogeneous VRP problems with reload/load-tracking
dimensions to incorrectly exclude smaller vehicles, forcing suboptimal
solutions with dropped stops.

The fix properly bounds negative transits by combining capacity and
slack_max, while positive transits continue to check capacity alone.

Changes:
- Pre-processing now tracks max positive transit, min (negative) transit,
  and slack_max separately instead of using abs() on all transits
- Capacity optimization check considers both positive and negative bounds
- Per-node feasibility check uses proper bounds: transit <= capacity &&
  transit >= min_allowed_transit

Tested with regression script from issue google#4133.

Fixes google#4133
Backport-of: google#4868
@StevenGay
Copy link
Collaborator

Thanks for your explanation and your code, I agree with the general modification, this was an actual bug.
We modified the code internally with a simpler change that should be more tight than your PR, it will be available shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants