Skip to content

Commit 1f61869

Browse files
committed
Backport: Fix issue google#4133 - Correct negative transit handling in 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
1 parent b8e881f commit 1f61869

File tree

1 file changed

+31
-11
lines changed

1 file changed

+31
-11
lines changed

ortools/constraint_solver/routing.cc

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,18 +1402,28 @@ void RoutingModel::AddRouteConstraint(
14021402
void RoutingModel::FinalizeAllowedVehicles() {
14031403
const std::vector<RoutingDimension*> unary_dimensions = GetUnaryDimensions();
14041404

1405-
// Pre-process the node transit values to find the maximum transit for each
1406-
// unary dimension to avoid unnecessary computations below.
1407-
std::vector<int64_t> dimension_max_node_transit(unary_dimensions.size(), 0);
1408-
for (int i = 0; i < unary_dimensions.size(); ++i) {
1409-
int64_t& max_node_transit = dimension_max_node_transit[i];
1410-
const RoutingDimension* dimension = unary_dimensions[i];
1405+
// Pre-process the node transit values to find, for each unary dimension, the
1406+
// maximum positive transit and the most negative transit. These bounds let us
1407+
// skip per-node checks when a vehicle's capacity and slack dominate them.
1408+
const int num_dimensions = unary_dimensions.size();
1409+
std::vector<int64_t> dimension_max_positive_transit(num_dimensions, 0);
1410+
std::vector<int64_t> dimension_min_transit(num_dimensions, 0);
1411+
std::vector<int64_t> dimension_slack_max(num_dimensions, 0);
1412+
for (int i = 0; i < num_dimensions; ++i) {
1413+
int64_t& max_positive_transit = dimension_max_positive_transit[i];
1414+
int64_t& min_transit = dimension_min_transit[i];
1415+
const RoutingDimension* const dimension = unary_dimensions[i];
1416+
dimension_slack_max[i] = dimension->SlackVar(0)->Max();
14111417
for (int node = 0; node < Size(); ++node) {
14121418
if (IsStart(node)) continue;
14131419
for (int callback_index : dimension->class_evaluators_) {
1414-
max_node_transit = std::max(
1415-
max_node_transit,
1416-
std::abs(UnaryTransitCallbackOrNull(callback_index)(node)));
1420+
const int64_t transit = UnaryTransitCallbackOrNull(callback_index)(node);
1421+
if (transit > max_positive_transit) {
1422+
max_positive_transit = transit;
1423+
}
1424+
if (transit < min_transit) {
1425+
min_transit = transit;
1426+
}
14171427
}
14181428
}
14191429
}
@@ -1428,7 +1438,13 @@ void RoutingModel::FinalizeAllowedVehicles() {
14281438
dim->GetUnaryTransitEvaluator(vehicle);
14291439
DCHECK(transit_evaluator != nullptr);
14301440
const int64_t capacity = dim->vehicle_capacities()[vehicle];
1431-
if (capacity >= dimension_max_node_transit[i]) continue;
1441+
const int64_t slack_max = dimension_slack_max[i];
1442+
const int64_t min_allowed_transit =
1443+
CapSub(0, CapAdd(capacity, slack_max));
1444+
if (capacity >= dimension_max_positive_transit[i] &&
1445+
dimension_min_transit[i] >= min_allowed_transit) {
1446+
continue;
1447+
}
14321448

14331449
for (int node = 0; node < Size(); ++node) {
14341450
if (IsStart(node)) continue;
@@ -1437,7 +1453,11 @@ void RoutingModel::FinalizeAllowedVehicles() {
14371453
// The vehicle is already forbidden for this node.
14381454
continue;
14391455
}
1440-
if (std::abs(transit_evaluator(node)) <= capacity) continue;
1456+
// Positive transits must fit within the vehicle capacity. Negative
1457+
// transits can only decrease the cumul as far as the capacity supplemented
1458+
// by the dimension slack allows.
1459+
const int64_t transit = transit_evaluator(node);
1460+
if (transit <= capacity && transit >= min_allowed_transit) continue;
14411461

14421462
// 'node' can't be served by 'vehicle', so we remove the 'vehicle'
14431463
// from the node's set of allowed_vehicles_.

0 commit comments

Comments
 (0)