Skip to content

Improve TSP with different vehicle start/end locations#1254

Open
hlinsen wants to merge 7 commits into
NVIDIA:release/26.06from
hlinsen:improve-vehicle-locs
Open

Improve TSP with different vehicle start/end locations#1254
hlinsen wants to merge 7 commits into
NVIDIA:release/26.06from
hlinsen:improve-vehicle-locs

Conversation

@hlinsen
Copy link
Copy Markdown
Contributor

@hlinsen hlinsen commented May 20, 2026

Improve routing support for TSP models that use set_vehicle_locations() with distinct start and
end locations. These models are fixed paths, not closed tours, so some TSP-specific operators need
to avoid cyclic assumptions around the start/end boundary.

  • Add path-aware EAX handling for fixed-start/fixed-end TSPs:

    • Detect TSPs with vehicle locations and no single depot.
    • Use a virtual depot during EAX recombination so endpoint depots can be normalized locally.
    • Keep normal single-depot behavior unchanged.
  • Add path-aware OX handling:

    • Use a linear offspring fill for fixed-path TSPs.
    • Avoid wrapping across the route end/start boundary, which is only valid for closed tours.
    • Fold the path logic into fill_offspring(..., linear_path_tsp) to reduce duplicate helper code.
  • Update sliding TSP local search

    • Allow insertion at the start-depot boundary not only nodes_to_consider
  • Add a unit test covering TSP with order locations plus vehicle start/end locations.

@hlinsen hlinsen added this to the 26.06 milestone May 20, 2026
@hlinsen hlinsen requested a review from a team as a code owner May 20, 2026 05:43
@hlinsen hlinsen added the non-breaking Introduces a non-breaking change label May 20, 2026
@hlinsen hlinsen requested review from akifcorduk and yuwenchen95 May 20, 2026 05:43
@hlinsen hlinsen added improvement Improves an existing functionality P0 labels May 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 38f7902f-37e9-4da6-be9f-20a40e5b22a3

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf4fcd and 29b4bde.

📒 Files selected for processing (1)
  • cpp/src/routing/local_search/sliding_tsp.cu

📝 Walkthrough

Walkthrough

This PR extends genetic algorithm recombination and local search to support TSP path problems (fixed start/end locations) by introducing virtual-depot normalization in EAX, linear-path offspring generation in OX, and updating candidate enumeration and node-info extraction in sliding TSP search.

Changes

TSP Path Problem Support in Genetic Algorithm Components

Layer / File(s) Summary
EAX Recombiner: Virtual-Depot Normalization Framework
cpp/src/routing/crossovers/eax_recombiner.hpp
a_eax gains a use_virtual_depot_node flag and helper methods: get_path_tsp_virtual_depot() detects fixed-start/end TSP paths, set_recombination_depot() selects the recombination depot, and normalize_depot() maps endpoints to the selected depot. Normalization is applied in fill_eset() for edge comparisons, populate_helper_to_graph() for route-traversal node transfer, and ab_graph_to_solution() skips depot index processing when virtual mode is active.
OX Recombiner: Linear-Path Offspring Generation
cpp/src/routing/crossovers/ox_recombiner.cuh
OX<Solution> adds use_linear_ox_for_path_tsp() helper to detect single-route path-TSP. fill_offspring() accepts a linear_path_tsp parameter and conditionally switches between cycle-based OX and a linear-path variant that injects a genome_B fragment then fills remaining positions from genome_A in order. Offspring allocation is unified to genome_A.size() + 1.
Sliding TSP Local Search: Candidate Enumeration and Node-Info Updates
cpp/src/routing/local_search/sliding_tsp.cu
find_sliding_moves_tsp candidate enumeration adds an explicit depot-insertion sentinel at position 0, with other candidates derived from sampled nodes. execute_sliding_moves_tsp now extracts NodeInfo from the shared route's get_node(...).node_info() instead of manually constructing from order locations, handling the depot sentinel appropriately.
Test Coverage for Single-Vehicle TSP Scenarios
cpp/tests/routing/unit_tests/order_locations.cu
New GTest case tsp_vehicle_locations constructs a single-vehicle routing model, solves it, asserts success, and validates route structure via check_route. SPDX copyright year updated to 2026.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Suggested reviewers

  • akifcorduk
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective of the changeset: improving TSP support for vehicles with different start/end locations.
Description check ✅ Passed The description directly relates to the changeset, detailing specific improvements to EAX, OX, sliding TSP, and tests for handling fixed-path TSPs with distinct vehicle start/end locations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/routing/local_search/sliding_tsp.cu (1)

320-328: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Don't resolve NodeInfo from mutable route indices.

cand.window_start and cand.insertion_pos are based on the original route layout, but earlier accepted slides can shift unrelated indices without marking that whole span in moved_regions. After that, s_route.get_node(cand.*) can refer to a different node than original_*_node_id, so these assertions can trip on valid candidate sets, or the rewiring can use the wrong endpoints if assertions are compiled out. Please keep the stable node ids from moved_regions as the source of truth here and resolve NodeInfo from a node-id lookup that also preserves depot vs. order node types.

As per coding guidelines, "Flag algorithm correctness errors including logic errors in optimization algorithms".

🤖 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/sliding_tsp.cu` around lines 320 - 328, The
assertions and NodeInfo resolution must not use mutable route indices
(s_route.get_node(cand.window_start) / get_node(cand.insertion_pos)); instead,
look up NodeInfo by the stable node ids recorded in moved_regions (e.g. use the
stored original_*_node_id values) so you always resolve the same node even if
prior accepted slides shifted route indices; replace the get_node calls used to
build original_node_info, original_fragment_end_node_info, and
original_node_insertion_info with a node-id based lookup that preserves depot
vs. order node types and then assert those NodeInfo.node() values against
original_node_id, original_fragment_end_node_id, and original_node_insertion.
🤖 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.

Outside diff comments:
In `@cpp/src/routing/local_search/sliding_tsp.cu`:
- Around line 320-328: The assertions and NodeInfo resolution must not use
mutable route indices (s_route.get_node(cand.window_start) /
get_node(cand.insertion_pos)); instead, look up NodeInfo by the stable node ids
recorded in moved_regions (e.g. use the stored original_*_node_id values) so you
always resolve the same node even if prior accepted slides shifted route
indices; replace the get_node calls used to build original_node_info,
original_fragment_end_node_info, and original_node_insertion_info with a node-id
based lookup that preserves depot vs. order node types and then assert those
NodeInfo.node() values against original_node_id, original_fragment_end_node_id,
and original_node_insertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9ff553ae-0585-45e2-b1c6-0da86e5faf46

📥 Commits

Reviewing files that changed from the base of the PR and between d76e91a and 0cf4fcd.

📒 Files selected for processing (1)
  • cpp/src/routing/local_search/sliding_tsp.cu

@rgsl888prabhu rgsl888prabhu changed the base branch from main to release/26.06 May 20, 2026 17:26
NodeInfo<i_t>(original_node_insertion,
sol.problem.order_info.get_order_location(original_node_insertion),
node_type_t::DELIVERY);
original_node_insertion == start_depot_node_info.node()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The node value for the depot is undefined, I think

bool use_linear_ox_for_path_tsp(Solution const& S, int n_routes) const
{
if (!S.problem->is_tsp || n_routes != 1) { return false; }
if (S.problem->data_view_ptr->get_vehicle_locations().first == nullptr) { return false; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the vehicle start location and end locations are same, but they are set by the user?


detail::NodeInfo<> normalize_depot(detail::NodeInfo<> node) const
{
if (use_virtual_depot_node && node.is_depot()) { return single_depot_node.value(); }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the logic here?

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

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change P0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants