Fix vehicle fixed cost accounting in fragment-vs-route deltas - #1244
Conversation
akifcorduk
left a comment
There was a problem hiding this comment.
I think it looks good, thanks Miles! I would also ask Rajesh's reviews on possible side effects as he had some comments on the previous cost computation on node.cuh
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughCentralizes vehicle fixed-cost injection into vehicle_fixed_cost_node_t::get_cost, removes explicit fixed-cost special-casing from delta and edge computations, and adds a runtime assertion validating cycle-cost consistency after local-search moves. ChangesVehicle Fixed Cost Centralization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cpp/src/routing/local_search/local_search.cu`:
- Around line 314-315: The assertion only checks an upper bound and misses large
negative mismatches; update the consistency check in the local search to use an
absolute difference: replace the current cuopt_assert condition that compares
(cost_after - cost_before) - move_candidates.cycles.total_cycle_cost < 1. with
one that tests the absolute error, e.g. fabs((cost_after - cost_before) -
move_candidates.cycles.total_cycle_cost) < 1. so the assertion is symmetric;
ensure any needed math header/functions are available for fabs (or std::abs)
where cuopt_assert is invoked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 7fd82fc6-4658-464e-8ac4-16cc1c1eb806
📒 Files selected for processing (6)
cpp/src/routing/crossovers/ox_kernels.cuhcpp/src/routing/crossovers/ox_recombiner.cuhcpp/src/routing/local_search/local_search.cucpp/src/routing/local_search/vrp/vrp_search.cucpp/src/routing/node/node.cuhcpp/src/routing/node/vehicle_fixed_cost_node.cuh
| cuopt_assert((cost_after - cost_before) - move_candidates.cycles.total_cycle_cost < 1., | ||
| "Cost mismatch after a move"); |
There was a problem hiding this comment.
Make the cycle-cost consistency assert symmetric.
Line 314 currently only checks an upper bound; large negative mismatches still pass. Use absolute error for a true consistency check.
Suggested fix
- cuopt_assert((cost_after - cost_before) - move_candidates.cycles.total_cycle_cost < 1.,
+ cuopt_assert(abs((cost_after - cost_before) - move_candidates.cycles.total_cycle_cost) < 1.,
"Cost mismatch after a move");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cpp/src/routing/local_search/local_search.cu` around lines 314 - 315, The
assertion only checks an upper bound and misses large negative mismatches;
update the consistency check in the local search to use an absolute difference:
replace the current cuopt_assert condition that compares (cost_after -
cost_before) - move_candidates.cycles.total_cycle_cost < 1. with one that tests
the absolute error, e.g. fabs((cost_after - cost_before) -
move_candidates.cycles.total_cycle_cost) < 1. so the assertion is symmetric;
ensure any needed math header/functions are available for fabs (or std::abs)
where cuopt_assert is invoked.
|
@hlinsen @akifcorduk do you like claude's in-line comments or should they be deleted? |
|
@mlubin I think they can stay, they are explaining some non-trivial logic. |
They're useful we can keep them |
b4ff2d9 to
3dbc941
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cpp/src/routing/crossovers/ox_recombiner.cuh (1)
844-845: ⚡ Quick winRemove unused variable
optimal_vehicle_fixed_cost.The
optimal_vehicle_fixed_costvariable is computed but no longer used after Line 872 was updated to usecostdirectly (which now includes the fixed cost viavehicle_fixed_cost_node_t::get_cost).♻️ Proposed fix to remove dead code
- double optimal_vehicle_fixed_cost = - dimensions_info.has_dimension(dim_t::VEHICLE_FIXED_COST) ? vehicle_info.fixed_cost : 0.; - // Here we have to use the vehicle type 'veh'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/src/routing/crossovers/ox_recombiner.cuh` around lines 844 - 845, Remove the unused local variable optimal_vehicle_fixed_cost (and its conditional assignment using dimensions_info.has_dimension(dim_t::VEHICLE_FIXED_COST) ? vehicle_info.fixed_cost : 0.) from ox_recombiner.cuh; the code now uses cost (which already includes the fixed cost via vehicle_fixed_cost_node_t::get_cost), so delete the optimal_vehicle_fixed_cost declaration and assignment to eliminate dead code and any compiler warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cpp/src/routing/crossovers/ox_recombiner.cuh`:
- Around line 844-845: Remove the unused local variable
optimal_vehicle_fixed_cost (and its conditional assignment using
dimensions_info.has_dimension(dim_t::VEHICLE_FIXED_COST) ?
vehicle_info.fixed_cost : 0.) from ox_recombiner.cuh; the code now uses cost
(which already includes the fixed cost via vehicle_fixed_cost_node_t::get_cost),
so delete the optimal_vehicle_fixed_cost declaration and assignment to eliminate
dead code and any compiler warnings.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 5b614d74-ae86-4bd2-9187-7d41ff3b85b1
📒 Files selected for processing (6)
cpp/src/routing/crossovers/ox_kernels.cuhcpp/src/routing/crossovers/ox_recombiner.cuhcpp/src/routing/local_search/local_search.cucpp/src/routing/local_search/vrp/vrp_search.cucpp/src/routing/node/node.cuhcpp/src/routing/node/vehicle_fixed_cost_node.cuh
🚧 Files skipped from review as they are similar to previous changes (4)
- cpp/src/routing/crossovers/ox_kernels.cuh
- cpp/src/routing/node/node.cuh
- cpp/src/routing/local_search/local_search.cu
- cpp/src/routing/local_search/vrp/vrp_search.cu
The cycle-finder's `total_cycle_cost` was systematically under-counting vehicle fixed cost when a cycle activated a previously-empty route via the SPECIAL (unrouted) pseudo-node. The realized post-move cost included the activation, the predicted total didn't — manifesting as flaky `cost_after - cost_before - total_cycle_cost < 1.` aborts in `level0_retail/retail_float_test_t.CVRPTW_Retail/18`. Root cause: `vehicle_fixed_cost_node_t::get_cost` was a no-op. When `node_t::calculate_forward_all_and_delta` aggregated the fragment's per-dimension costs, the VEHICLE_FIXED_COST dimension contributed 0 from the new (fragment) side while `old_obj_cost` carried the route's actual fixed cost. A defensive `obj_weights[VEHICLE_FIXED_COST] = 0` line stripped both sides symmetrically, which kept same-route same- vehicle moves correct but silently dropped the activation cost any time a cycle changed a route's occupancy. Fix: have the fragment-side `get_cost` contribute `vehicle_info.fixed_cost` so the fragment correctly represents an active route. With the fragment correct, several caller-side workarounds become redundant: - `node.cuh`: drop the `obj_weights[VEHICLE_FIXED_COST] = 0` zero-out in both forward and backward delta paths. - `vrp_search.cu::evaluate_2_opt_route`: drop the manual `(F_new − F_old)` add for HVRP vehicle swaps; the fragment cost now carries the swap delta directly. - `ox_kernels.cuh` and `ox_recombiner.cuh`: drop the manual `optimal_vehicle_fixed_cost +` in OX edge weights; `cost_combine` now returns the full edge cost. - `local_search.cu`: re-enable the `Cost mismatch after a move` assertion at the strict `< 1.` bound. Verified by hammering CVRPTW_Retail/18 for 300 iterations under ASSERT_MODE: 0 aborts and 0 near-misses (residual >= 0.1), versus a pre-fix ~1.7% per-iteration abort rate. Full ROUTING_TEST suite passes. Closes NVIDIA#845 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Miles Lubin <mlubin@nvidia.com>
3dbc941 to
189099c
Compare
|
/ok to test 189099c |
@mlubin, there was an error processing your request: See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/ |
|
/ok to test 180458f |
Pre-commit fixes: - node.cuh: reflow two `objective_cost_t::dot` calls per clang-format - bump SPDX copyright year on the four files touched by the previous commit Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Miles Lubin <mlubin@nvidia.com>
|
/ok to test 8d6305e |
|
I'll merge given the two approvals and rare green checkmark. @rg20 ptal at your convenience and I'll follow up on any comments. |
|
/merge |
(Claude-discovered fix.)
The cycle-finder's
total_cycle_costwas systematically under-counting vehicle fixed cost when a cycle activated a previously-empty route via the SPECIAL (unrouted) pseudo-node. The realized post-move cost included the activation, the predicted total didn't — manifesting as flakycost_after - cost_before - total_cycle_cost < 1.aborts inlevel0_retail/retail_float_test_t.CVRPTW_Retail/18.Root cause:
vehicle_fixed_cost_node_t::get_costwas a no-op. Whennode_t::calculate_forward_all_and_deltaaggregated the fragment's per-dimension costs, the VEHICLE_FIXED_COST dimension contributed 0 from the new (fragment) side whileold_obj_costcarried the route's actual fixed cost. A defensiveobj_weights[VEHICLE_FIXED_COST] = 0line stripped both sides symmetrically, which kept same-route same- vehicle moves correct but silently dropped the activation cost any time a cycle changed a route's occupancy.Fix: have the fragment-side
get_costcontributevehicle_info.fixed_costso the fragment correctly represents an active route. With the fragment correct, several caller-side workarounds become redundant:node.cuh: drop theobj_weights[VEHICLE_FIXED_COST] = 0zero-out in both forward and backward delta paths.vrp_search.cu::evaluate_2_opt_route: drop the manual(F_new − F_old)add for HVRP vehicle swaps; the fragment cost now carries the swap delta directly.ox_kernels.cuhandox_recombiner.cuh: drop the manualoptimal_vehicle_fixed_cost +in OX edge weights;cost_combinenow returns the full edge cost.local_search.cu: re-enable theCost mismatch after a moveassertion at the strict< 1.bound.Verified by running CVRPTW_Retail/18 for 300 iterations under ASSERT_MODE: 0 aborts and 0 near-misses (residual >= 0.1), versus a pre-fix ~1.7% per-iteration abort rate. Full ROUTING_TEST suite passes.
Closes #845