Skip to content

routing: add distance breaks - #1196

Open
np96 wants to merge 15 commits into
NVIDIA:mainfrom
np96:feature/distance-breaks
Open

routing: add distance breaks#1196
np96 wants to merge 15 commits into
NVIDIA:mainfrom
np96:feature/distance-breaks

Conversation

@np96

@np96 np96 commented May 11, 2026

Copy link
Copy Markdown
Contributor

Description

Adds per-vehicle distance-based breaks to routing, enabling charging, refueling, or rest stops to be constrained by cumulative travel distance rather than time.

  • Exposes one break per call or entry through C++, Python (DataModel.add_vehicle_distance_break(vehicle_id, distance_min, distance_max, duration, locations=None)), the server, and gRPC. Repeat calls or entries for additional vehicles or breaks.
  • Treats distance_max as a hard cumulative-distance limit and distance_min as a soft target. Early arrival contributes to Objective.DISTANCE_BREAK_COST; its default weight is 1.0, and 0.0 disables only this penalty.
  • Supports restricting eligible break locations and adds validation, serialization coverage, solver tests, and a documented Python example.

Distance breaks cannot be combined with uniform breaks configured through add_break_dimension. They also inherit the existing exactly-N-breaks-per-route behavior tracked in #1195.

Issue

Addresses #589

Checklist

  • I am familiar with the Contributing Guidelines.
  • Testing
    • New or existing tests cover these changes
    • Added tests
    • Created an issue to follow-up
    • NA
  • Documentation
    • The documentation is up to date with these changes
    • Added new documentation
    • NA

@np96
np96 requested review from a team as code owners May 11, 2026 09:31
@copy-pr-bot

copy-pr-bot Bot commented May 11, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request adds cumulative-distance vehicle breaks with soft minimum targets, hard maximum limits, optional locations, multiple cycles, and configurable objective weighting. It integrates the feature across C++, Python, server APIs, solver state, serialization, tests, and documentation.

Changes

Distance-breaks integration

Layer / File(s) Summary
Break contracts and public APIs
cpp/include/cuopt/routing/..., cpp/src/routing/data_model_view.cu, python/cuopt/cuopt/routing/...
Adds distance-break types, DISTANCE_BREAK_COST, C++ and Python registration methods, location validation, serialization, deferred setters, and corrected zero-based validation bounds.
Distance-window solver state
cpp/src/routing/node/..., cpp/src/routing/route/..., cpp/src/routing/problem/..., cpp/src/routing/util_kernels/...
Propagates distance windows and excess through nodes and routes. Stores special-node bounds and computes distance infeasibility and distance-break costs.
Server data pipeline
python/cuopt_server/cuopt_server/utils/routing/..., python/cuopt_server/cuopt_server/utils/solver.py, python/cuopt_server/cuopt_server/tests/utils/utils.py
Adds server fleet models, objective mapping, normalization, validation, request forwarding, solver registration, and location preparation for vehicle distance breaks.
Tests, examples, and documentation
cpp/tests/routing/..., python/cuopt/cuopt/tests/routing/..., python/cuopt_server/cuopt_server/tests/..., docs/cuopt/source/...
Adds propagation, solver, API, serialization, server, regression, multi-cycle, mixed-fleet, example, and feature documentation coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: ramakrishnap-nv, aliceb-nv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.74% 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
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.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding distance-based breaks to routing.
Description check ✅ Passed The description directly explains the distance-break functionality, APIs, behavior, validation, tests, documentation, and known limitations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

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

⚠️ Outside diff range comments (1)
python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py (1)

473-552: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mirror vehicle_distance_breaks through update_fleet_data() too.

This PR wires the new field through the create/set path only. update_fleet_data() still has no parameter, validation call, or storage branch for vehicle_distance_breaks, so update callers cannot modify or clear distance breaks after initial creation.

🤖 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 `@python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py`
around lines 473 - 552, update_fleet_data is missing the new
vehicle_distance_breaks parameter and plumbing; add a vehicle_distance_breaks
parameter to update_fleet_data, call
get_none_for_empty_list(vehicle_distance_breaks) inside it, pass
vehicle_distance_breaks through to the validate_fleet_data(...) call (match the
updating=True usage used in update path), and ensure the validated value is
stored in the same internal field that set_fleet_data uses (the model attribute
used to persist distance breaks). This mirrors the handling in set_fleet_data
and allows callers to modify/clear distance breaks on updates.
🧹 Nitpick comments (2)
python/cuopt_server/cuopt_server/utils/routing/data_definition.py (1)

134-168: ⚡ Quick win

Document float precision accurately and consider adding a Pydantic validator for range constraints.

  1. Type documentation mismatch: The Field descriptions state dtype: float32 for max_range and min_range, but the Python float type is typically 64-bit. Either update the documentation to float64 or explicitly use np.float32 if 32-bit precision is required for downstream processing.

  2. Missing constraint validation: The min_range description states "0 <= min_range < max_range", but no Pydantic validator enforces this relationship. While validation_fleet_data.py may validate this (per the PR summary), consider adding a @root_validator here for defense-in-depth:

🛡️ Suggested Pydantic validator
`@root_validator`(skip_on_failure=True)
def validate_range_bounds(cls, values):
    min_range = values.get('min_range', 0.0)
    max_range = values.get('max_range')
    if max_range is not None and min_range >= max_range:
        raise ValueError(f"min_range ({min_range}) must be < max_range ({max_range})")
    return values
🤖 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 `@python/cuopt_server/cuopt_server/utils/routing/data_definition.py` around
lines 134 - 168, The Field descriptions for max_range and min_range incorrectly
state "dtype: float32" while the attributes use Python float (64-bit); update
the docstrings to reflect float64 or switch the type to np.float32 if 32-bit is
required (refer to max_range and min_range Field definitions). Also add a
Pydantic class-level validator (use `@root_validator`(skip_on_failure=True)) in
the same data model to enforce 0 <= min_range < max_range and raise a clear
ValueError if the constraint fails (refer to min_range, max_range and the class
where Field is defined).
cpp/tests/routing/unit_tests/distance_breaks.cu (1)

275-368: ⚡ Quick win

Add a regression for the lower-bound side of the distance window.

These tests pin d_max enforcement and multi-cycle insertion, but I don't see a case that asserts the new min_range behavior when a charger is reached before the lower bound. That is the part of the feature that differs most from time-based breaks, so it should have an explicit regression.

As per coding guidelines, .github/.coderabbit_review_guide.md: "require edge-case coverage for empty/invalid windows, boundary tolerances, multi-cycle window generation, and infeasible scenarios".

🤖 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/tests/routing/unit_tests/distance_breaks.cu` around lines 275 - 368, Add
a regression test that verifies the distance-break lower bound (min_range) is
enforced: create a new TEST (e.g., distance_breaks,
break_distance_window_min_enforced) using a small cost matrix and
order/charging_station setup similar to break_distance_window_enforced but call
data_model.add_distance_break with nonzero min_range (e.g.,
add_distance_break(0, min_range, max_range, ...)). Solve and iterate
h.locations/h.node_types to compute cumulative distance like in the existing
test and assert any found BREAK satisfies cumulative >= min_range and cumulative
<= max_range, and assert a break was found; also include a case where the
nearest charger would occur before min_range so the solver must place the break
later to validate the lower-bound behavior.
🤖 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/data_model_view.cu`:
- Around line 36-42: thrust::unique only removes consecutive duplicates so the
current check can miss non-adjacent duplicates; fix by sorting the copied device
vector before calling unique: after creating tmp and copying locations
(rmm::device_uvector<i_t> tmp(...) and raft::copy(...)), call thrust::sort with
the same thrust policy/stream (handle->get_thrust_policy(), tmp.begin(),
tmp.end()), then call thrust::unique and recompute unique_items as end -
tmp.begin(); keep the cuopt::cuopt_expects(n == unique_items, ...) assertion but
run it after the sort+unique sequence.
- Around line 25-43: In validate_break_locations, explicitly reject negative
counts and null pointer inputs before any device-side work: add checks so that
if n < 0 you fail via cuopt::cuopt_expects with
cuopt::error_type_t::ValidationError, and if n > 0 && locations == nullptr you
likewise fail with a ValidationError; perform these checks before calling
cuopt::routing::detail::check_min_max_values, before allocating
rmm::device_uvector, and before any raft::device_span construction in callers
(e.g., places building raft::device_span<const i_t>(break_locations,
num_break_locations)).

In `@cpp/src/routing/local_search/delivery_insertion.cuh`:
- Around line 106-108: The early returns in the delivery insertion scan abort on
the first distance-window miss; change them to continue scanning (or otherwise
only break on monotone/upper-bound-only failures) so a candidate that is
initially too-early for distance_min can still become feasible later as
cumulative distance grows. Concretely, replace the return-after-failure in calls
to node_t<i_t,f_t,REQUEST>::window_forward_feasible and
node_t<i_t,f_t,REQUEST>::window_backward_feasible (and the analogous checks at
the other mentioned sites) with logic that continues the loop unless the
feasibility failure is provably monotone (e.g., an upper-bound violation),
ensuring the scan does not prematurely stop. Ensure you update all occurrences
noted (the forward/backward checks around the delivery insertion scan) so they
follow the same continue-or-break-on-monotone rule.

In `@cpp/src/routing/node/distance_node.cuh`:
- Around line 96-105: The combine() function currently only sums window
mismatches and ignores vehicle_info.max_cost; update combine to also compute the
combined raw travel distance for the two fragments (combined_raw = prev raw
distance + distance_between + next raw distance—use the appropriate raw distance
fields on distance_node_t), then if vehicle_info.max_cost is finite add max(0.,
combined_raw - vehicle_info.max_cost) to the returned excess (i.e., return
prev.excess_forward + next.excess_backward + upper_excess + lower_excess +
max_distance_excess) so joins that violate max_cost are penalized/treated as
infeasible; keep using symbols combine, vehicle_info.max_cost, distance_between,
prev and next to locate and change the code.

In `@cpp/src/routing/problem/problem.cu`:
- Around line 689-690: The code is populating
node_distance_min_h/node_distance_max_h for every non-uniform break rather than
only for distance-based breaks, which makes special_nodes.distance_min non-empty
and incorrectly flips has_distance_window in populate_dimensions_info(); fix by
only appending to node_distance_min_h and node_distance_max_h when
vehicle_break.is_distance_based_ is true (e.g., wrap the push_back calls in an
if (vehicle_break.is_distance_based_) block), and/or introduce an explicit
per-node distance-based flag vector (e.g., node_is_distance_based_h) that is
populated from add_vehicle_break(...) and used by populate_dimensions_info()
instead of relying on buffer non-emptiness so time-only breaks remain
unmodified.
- Around line 654-670: The overlap validation only compares the current
time-based break to the immediately previous break and skips if that previous
break is distance-based; update the check in the block using
non_uniform_breaks.at(v), break_earliest_h, break_latest_h, is_distance_based_,
dim and v so it searches backward for the most recent prior break where
is_distance_based_ == false and then assert break_earliest_h[v][dim] >=
break_latest_h[v][prev_time_dim] via cuopt_expects (instead of comparing only
dim-1); ensure this logic runs only when the current break is time-based and
preserves the existing error message "breaks should not be overlapping!".

In `@cpp/src/routing/route/distance_route.cuh`:
- Around line 180-196: compute_cost currently overwrites inf_cost[dim_t::DIST]
in the has_distance_window branch with only window-related excess, dropping
vehicle_max_cost infeasibility; change the has_distance_window logic in
compute_cost so inf_cost[dim_t::DIST] accumulates both the window excess
(excess_forward[n_nodes_route] + upper_bound + lower_bound) and any vehicle max
cost excess (max(0., distance_forward[n_nodes_route] - vehicle_info.max_cost)),
e.g. compute both contributions and set inf_cost[dim_t::DIST] to their sum or
the max as appropriate so vehicle_info.max_cost is enforced even when
dim_info.has_distance_window is true.

In `@cpp/src/routing/util_kernels/set_nodes_data.cuh`:
- Around line 61-62: The header set_nodes_data.cuh is using
std::numeric_limits<f_t>::max() (see the f_t vehicle_max_cost assignment) but
doesn't include <limits>, making the header rely on transitive includes; fix it
by adding a direct `#include` <limits> to set_nodes_data.cuh so the use of
std::numeric_limits is self-contained and order-independent.

In `@cpp/tests/routing/routing_test.cuh`:
- Around line 1038-1063: Add an explicit assertion that distance break nodes are
actually present in the solution produced by test_cvrptw_distance_breaks: after
constructing host_assignment_t h_routing_solution(routing_solution) and before
calling check_distance_break_windows, scan the routes in h_routing_solution and
assert that at least one node with type BREAK (or, stronger, at least one BREAK
per vehicle that had add_distance_break called) is present; this ensures
add_distance_break calls actually resulted in emitted BREAK nodes rather than
silently being dropped.

In `@docs/cuopt/source/cuopt-python/routing/routing-examples.rst`:
- Around line 73-80: Update the documentation text around
DataModel.add_distance_break to correctly describe the generated windows when
n_cycles is nonzero: state that cycle k yields the window [k * max_range +
min_range, (k + 1) * max_range] (so each window width is max_range - min_range,
not always max_range) and explicitly note that visits before the lower bound
(the min_range edge) are penalized rather than strictly forbidden; reference the
parameters n_cycles, max_range and min_range in the description to make the
behavior precise and consistent with the public API.

In `@docs/cuopt/source/routing-features.rst`:
- Around line 66-73: Update the add_distance_break docs to accurately describe
cycle windows and lower-bound behavior: state that each cycle k=0..n_cycles-1
defines a required stop window [k*max_range + min_range, (k+1)*max_range] whose
width is (max_range - min_range) when min_range>0, clarify whether endpoints are
inclusive, and explicitly note that min_range is not a hard feasibility cutoff
but a preferred lower bound (early charging is allowed by the solver and will be
penalized according to the implementation rather than strictly forbidden); keep
references to add_distance_break, n_cycles, max_range, min_range (distance_min),
and charging_stations so readers can locate the behavior in the API.

In `@python/cuopt_server/cuopt_server/utils/routing/solver.py`:
- Around line 229-243: The vehicle_distance_breaks block currently passes raw
charging_stations into data_model.add_distance_break; for waypoint-graph
requests you must remap those station IDs through optimization_data.locations
(use the same locations.loc[...] pattern used for
vehicle_locations/vehicle_break_locations) to convert to the correct index
positions and build a cudf.Series of the remapped indices (or None) before
calling add_distance_break; additionally ensure any station IDs present only in
vehicle_distance_breaks are appended to optimization_data.locations (so they
exist when the cost matrix is generated) and add validation to detect
index-mapping mismatches or missing station entries at the server/library
boundary.

In `@python/cuopt/cuopt/routing/vehicle_routing.py`:
- Around line 395-428: add_distance_break is missing type hints and complete
docstring details; update its signature to include Python typing (e.g.,
vehicle_ids: Union[int, Sequence[int]], max_range: float, charge_duration: int,
charging_stations: Optional[cudf.Series]=None, min_range: float=0.0, n_cycles:
Optional[int]=None) and document returns and errors: state that max_range and
min_range are in the primary cost-matrix units, the method mutates the model in
place and returns None, and list ValueError conditions to raise (e.g., negative
max_range or min_range, min_range > max_range, negative or zero charge_duration,
n_cycles if provided must be > 0, and invalid/empty vehicle_ids); also mention
any numeric tolerances/semantics for cycles and cumulative-distance windows;
keep the function name add_distance_break references so reviewers can locate the
change.
- Around line 431-433: The handling of vehicle_ids only checks for Python int
and should accept NumPy integer scalars like n_cycles does; update the
conditional that normalizes vehicle_ids (the block using "if
isinstance(vehicle_ids, int): vehicle_ids = [vehicle_ids]") to check
isinstance(vehicle_ids, (int, np.integer)) so np.int32/np.int64 scalars are
converted to a single-item list before the for vid in vehicle_ids loop; mirror
the same pattern used for n_cycles and ensure numpy is imported or referenced as
np in the module.

---

Outside diff comments:
In `@python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py`:
- Around line 473-552: update_fleet_data is missing the new
vehicle_distance_breaks parameter and plumbing; add a vehicle_distance_breaks
parameter to update_fleet_data, call
get_none_for_empty_list(vehicle_distance_breaks) inside it, pass
vehicle_distance_breaks through to the validate_fleet_data(...) call (match the
updating=True usage used in update path), and ensure the validated value is
stored in the same internal field that set_fleet_data uses (the model attribute
used to persist distance breaks). This mirrors the handling in set_fleet_data
and allows callers to modify/clear distance breaks on updates.

---

Nitpick comments:
In `@cpp/tests/routing/unit_tests/distance_breaks.cu`:
- Around line 275-368: Add a regression test that verifies the distance-break
lower bound (min_range) is enforced: create a new TEST (e.g., distance_breaks,
break_distance_window_min_enforced) using a small cost matrix and
order/charging_station setup similar to break_distance_window_enforced but call
data_model.add_distance_break with nonzero min_range (e.g.,
add_distance_break(0, min_range, max_range, ...)). Solve and iterate
h.locations/h.node_types to compute cumulative distance like in the existing
test and assert any found BREAK satisfies cumulative >= min_range and cumulative
<= max_range, and assert a break was found; also include a case where the
nearest charger would occur before min_range so the solver must place the break
later to validate the lower-bound behavior.

In `@python/cuopt_server/cuopt_server/utils/routing/data_definition.py`:
- Around line 134-168: The Field descriptions for max_range and min_range
incorrectly state "dtype: float32" while the attributes use Python float
(64-bit); update the docstrings to reflect float64 or switch the type to
np.float32 if 32-bit is required (refer to max_range and min_range Field
definitions). Also add a Pydantic class-level validator (use
`@root_validator`(skip_on_failure=True)) in the same data model to enforce 0 <=
min_range < max_range and raise a clear ValueError if the constraint fails
(refer to min_range, max_range and the class where Field is defined).
🪄 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: 14c568cf-bc52-4b28-af4b-5bdfafbcb7f4

📥 Commits

Reviewing files that changed from the base of the PR and between b311a99 and cf56d55.

📒 Files selected for processing (39)
  • cpp/include/cuopt/routing/data_model_view.hpp
  • cpp/include/cuopt/routing/routing_structures.hpp
  • cpp/src/routing/data_model_view.cu
  • cpp/src/routing/dimensions.cuh
  • cpp/src/routing/ges/compute_delivery_insertions.cuh
  • cpp/src/routing/ges/lexicographic_search/lexicographic_search.cu
  • cpp/src/routing/ges/squeeze.cuh
  • cpp/src/routing/local_search/breaks_insertion.cu
  • cpp/src/routing/local_search/compute_ejections.cuh
  • cpp/src/routing/local_search/compute_insertions.cu
  • cpp/src/routing/local_search/delivery_insertion.cuh
  • cpp/src/routing/local_search/permutation_helper.cuh
  • cpp/src/routing/local_search/sliding_window.cu
  • cpp/src/routing/node/break_node.cuh
  • cpp/src/routing/node/distance_node.cuh
  • cpp/src/routing/node/node.cuh
  • cpp/src/routing/problem/problem.cu
  • cpp/src/routing/problem/special_nodes.cuh
  • cpp/src/routing/route/break_route.cuh
  • cpp/src/routing/route/distance_route.cuh
  • cpp/src/routing/solution/solution.cuh
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/tests/routing/CMakeLists.txt
  • cpp/tests/routing/level1/l1_routing_test.cu
  • cpp/tests/routing/routing_test.cuh
  • cpp/tests/routing/unit_tests/distance_breaks.cu
  • docs/cuopt/source/cuopt-python/routing/examples/distance_break_example.py
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
  • docs/cuopt/source/routing-features.rst
  • python/cuopt/cuopt/routing/vehicle_routing.pxd
  • python/cuopt/cuopt/routing/vehicle_routing.py
  • python/cuopt/cuopt/routing/vehicle_routing_wrapper.pyx
  • python/cuopt/cuopt/tests/routing/test_distance_breaks.py
  • python/cuopt_server/cuopt_server/tests/utils/utils.py
  • python/cuopt_server/cuopt_server/utils/routing/data_definition.py
  • python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py
  • python/cuopt_server/cuopt_server/utils/routing/solver.py
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
  • python/cuopt_server/cuopt_server/utils/solver.py

Comment thread cpp/src/routing/data_model_view.cu
Comment thread cpp/src/routing/data_model_view.cu
Comment on lines 106 to 108
if (!node_t<i_t, f_t, REQUEST>::window_forward_feasible(
delivery_node, route.vehicle_info(), weights, excess_limit)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't abort the insertion scan on the first distance-window miss.

These returns were safe for time-window pruning, but they are too aggressive once window_forward_feasible / window_backward_feasible includes distance breaks. A candidate can be invalid only because it is too early for distance_min, then become valid a few positions later as cumulative distance grows. Returning here drops the rest of the scan and can miss valid or lower-cost insertions.

Please keep scanning after a lower-bound/window-slack failure here, or only early-exit on cases that are still monotone (for example, proven upper-bound violations).

Also applies to: 141-145, 201-203, 235-239

🤖 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/delivery_insertion.cuh` around lines 106 - 108,
The early returns in the delivery insertion scan abort on the first
distance-window miss; change them to continue scanning (or otherwise only break
on monotone/upper-bound-only failures) so a candidate that is initially
too-early for distance_min can still become feasible later as cumulative
distance grows. Concretely, replace the return-after-failure in calls to
node_t<i_t,f_t,REQUEST>::window_forward_feasible and
node_t<i_t,f_t,REQUEST>::window_backward_feasible (and the analogous checks at
the other mentioned sites) with logic that continues the loop unless the
feasibility failure is provably monotone (e.g., an upper-bound violation),
ensuring the scan does not prematurely stop. Ensure you update all occurrences
noted (the forward/backward checks around the delivery insertion scan) so they
follow the same continue-or-break-on-monotone rule.

Comment thread cpp/src/routing/node/distance_node.cuh
Comment thread cpp/src/routing/problem/problem.cu
Comment thread docs/cuopt/source/cuopt-python/routing/routing-examples.rst Outdated
Comment thread docs/cuopt/source/routing-features.rst Outdated
Comment thread python/cuopt_server/cuopt_server/utils/routing/solver.py
Comment thread python/cuopt/cuopt/routing/vehicle_routing.py
Comment thread python/cuopt/cuopt/routing/vehicle_routing.py Outdated
@rg20 rg20 added feature request New feature or request non-breaking Introduces a non-breaking change labels May 11, 2026
@rg20 rg20 added this to the 26.06 milestone May 11, 2026
@rg20

rg20 commented May 11, 2026

Copy link
Copy Markdown
Contributor

@np96 Thanks for the improvements. It will take a bit of time for me to review all the changes :).

Did this implementation improve the accuracy/quality of the results for your use cases?

@mlubin
mlubin requested review from rg20 and removed request for mlubin May 11, 2026 13:59
@rg20
rg20 removed the request for review from kaatish May 11, 2026 14:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
python/cuopt/cuopt/tests/routing/test_distance_breaks.py (1)

338-376: ⚡ Quick win

Add one lower-bound-violation / infeasible distance-window case.

All solver scenarios in this file are feasible. The new feature’s distinctive path is the lack of a distance-wait analogue, so an early break should violate min_range and flow through infeasibility cost. Without one case that forces that behavior, regressions in the new penalty plumbing can slip past the Python integration suite.

As per coding guidelines, "Flag missing coverage for edge cases (empty, infeasible, unbounded, degenerate) when adding new code paths".

Also applies to: 379-455

🤖 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/tests/routing/routing_test.cuh`:
- Around line 595-629: The helper check_distance_break_windows currently treats
min_range as a hard feasibility bound and fails when a BREAK cumulative distance
is below min_range; update the function check_distance_break_windows to remove
the lower-bound assertion (the ASSERT_GE that checks cumulative vs min_range)
while keeping the upper-bound ASSERT_LE against max_range and still incrementing
break_count for BREAK nodes (node_type_t::BREAK). Ensure no new hard-feasibility
check is introduced for min_range here—lower-bound behavior should be validated
via infeasibility/objective tests elsewhere.

In `@python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py`:
- Around line 214-219: The validator currently only checks n_cycles > 0 for each
vehicle_distance_breaks entry; add an upper bound to prevent explosion by either
(a) capping entry.n_cycles to a constant MAX_N_CYCLES (e.g. define MAX_N_CYCLES
near the top of validation_fleet_data.py) and rejecting values > MAX_N_CYCLES
with the same False/"vehicle_distance_breaks: n_cycles must be <= MAX_N_CYCLES"
style, or (b) compute the total generated breaks across all entries (sum of
entry.n_cycles or min(entry.n_cycles, MAX_N_CYCLES) per entry) and reject the
request if total > MAX_TOTAL_BREAKS, returning a clear error like
"vehicle_distance_breaks: total generated breaks exceeds limit"; update the
validation branch that currently references entry.n_cycles and n_cycles to
enforce the chosen cap and surface the appropriate error message.

In `@python/cuopt/cuopt/tests/routing/test_distance_breaks.py`:
- Around line 305-309: The test only checks properties of "Break" rows if they
exist, allowing regressions where no breaks or breaks for wrong vehicles still
pass; update the test that calls sol.get_route() and inspects routes to
explicitly assert the expected set/count of vehicles that have breaks (e.g.,
compute the set of vehicle identifiers from routes where type == "Break" and
compare to the expected vehicle set), and add a found_break guard (fail/assert
when no "Break" rows are present) before validating break locations against
break_loc_set; apply the same pattern to the other affected blocks (around lines
327-335, 444-455, 467-472) so each case verifies both presence and correctness
of break-bearing vehicles, not just properties of observed breaks.
- Around line 142-147: The test test_distance_break_invalid_vehicle_id currently
parametrizes invalid vehicle ids [-1, 4, 100] but omits the exact upper-bound
invalid case (fleet_size == 3); update the pytest.mark.parametrize for vid in
that test to include 3 so the list becomes [-1, 3, 4, 100] (or add 3 alongside
the existing values) to catch off-by-one acceptance issues when calling
model.add_distance_break(vid, ...).
🪄 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: 02c01d55-1910-4946-9805-00ddd80111b6

📥 Commits

Reviewing files that changed from the base of the PR and between cf56d55 and 22879af.

📒 Files selected for processing (19)
  • cpp/include/cuopt/routing/data_model_view.hpp
  • cpp/src/routing/data_model_view.cu
  • cpp/src/routing/node/distance_node.cuh
  • cpp/src/routing/problem/problem.cu
  • cpp/src/routing/problem/special_nodes.cuh
  • cpp/src/routing/route/distance_route.cuh
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/tests/routing/level1/l1_routing_test.cu
  • cpp/tests/routing/routing_test.cuh
  • cpp/tests/routing/unit_tests/distance_breaks.cu
  • docs/cuopt/source/cuopt-python/routing/examples/distance_break_example.py
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
  • docs/cuopt/source/routing-features.rst
  • python/cuopt/cuopt/routing/vehicle_routing.py
  • python/cuopt/cuopt/tests/routing/test_distance_breaks.py
  • python/cuopt_server/cuopt_server/utils/routing/data_definition.py
  • python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py
  • python/cuopt_server/cuopt_server/utils/routing/solver.py
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
✅ Files skipped from review due to trivial changes (1)
  • docs/cuopt/source/cuopt-python/routing/examples/distance_break_example.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • cpp/include/cuopt/routing/data_model_view.hpp
  • docs/cuopt/source/routing-features.rst
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/src/routing/problem/problem.cu
  • cpp/src/routing/problem/special_nodes.cuh
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
  • python/cuopt_server/cuopt_server/utils/routing/data_definition.py
  • cpp/src/routing/node/distance_node.cuh
  • cpp/src/routing/data_model_view.cu

Comment thread cpp/tests/routing/routing_test.cuh Outdated
Comment thread python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py Outdated
Comment thread python/cuopt/cuopt/tests/routing/test_distance_breaks.py Outdated
Comment thread python/cuopt/cuopt/tests/routing/test_distance_breaks.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@python/cuopt/cuopt/routing/vehicle_routing.py`:
- Around line 455-459: Validate vehicle_ids immediately after the scalar-to-list
conversion: if vehicle_ids is None or not a supported scalar/iterable type
(accept int/np.integer or any iterable like list/tuple/np.ndarray/cudf.Series),
raise a clear ValueError indicating "invalid/empty vehicle_ids" (or similar text
matching the docstring) when the sequence is empty; keep the existing branch
that converts an int/np.integer to [int(vehicle_ids)], then check that the
resulting container is non-empty before any iteration and raise the actionable
ValueError for unsupported types or empty containers so callers fail fast.
- Around line 415-419: The docstring in vehicle_routing.py currently claims
distance-window endpoints are strictly infeasible; update that paragraph to
state that distance-window violations are handled by applying an infeasibility
cost (penalized) rather than being impossible, while still noting there is no
"wait" analogue in the distance dimension so the cumulative distance can't be
delayed; adjust the wording to match the PR behavior. Also ensure the public API
docstring for the related class/function includes pydocstyle-compliant sections
(params/returns/raises) as required by the project guidelines so the docstring
is complete and unambiguous.
🪄 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: 38b8078d-df86-49cc-aa0d-4467b87af689

📥 Commits

Reviewing files that changed from the base of the PR and between 22879af and 094e2f7.

📒 Files selected for processing (9)
  • cpp/src/routing/node/distance_node.cuh
  • cpp/src/routing/route/distance_route.cuh
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/tests/routing/unit_tests/distance_breaks.cu
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
  • docs/cuopt/source/routing-features.rst
  • python/cuopt/cuopt/routing/vehicle_routing.py
  • python/cuopt/cuopt/tests/routing/test_distance_breaks.py
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
✅ Files skipped from review due to trivial changes (2)
  • docs/cuopt/source/routing-features.rst
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
🚧 Files skipped from review as they are similar to previous changes (3)
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
  • python/cuopt/cuopt/tests/routing/test_distance_breaks.py
  • cpp/src/routing/route/distance_route.cuh

Comment thread python/cuopt/cuopt/routing/vehicle_routing.py Outdated
Comment thread python/cuopt/cuopt/routing/vehicle_routing.py Outdated
@np96

np96 commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

@np96 Thanks for the improvements. It will take a bit of time for me to review all the changes :).

Did this implementation improve the accuracy/quality of the results for your use cases?

Hi @rg20!

Looks better and cleaner than forbidding local search moves. Though practically it currently has some limitations due to this.

Ideally I'd want to model this way: 1K delivery robo-EVs, may travel up to 300 km a day, has 2 required charging windows: [120..150] KM and [270..300] KM.
Current break dimensions logic will force each of them to travel at least 270 KM to visit second charge. I believe that's not what customers want. The same, as far as I understand, applies to time breaks.

Everything else LGTM. Hope to fix this and I am very happy with this feature and approach. Thanks 👍

@github-actions

Copy link
Copy Markdown

🔔 Hi @anandhkb, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you!

If this is an "epic" issue, then please add the "epic" label to this issue.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

@anandhkb anandhkb added the P1 label May 19, 2026
@ramakrishnap-nv
ramakrishnap-nv changed the base branch from main to release/26.06 May 20, 2026 17:43

@rg20 rg20 left a comment

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.

@np96

Thanks for the PR. You do not need to check for distance dimension feasibility everywhere. We do that for time dimension just to rule out a lot of moves. For distance breaks, this filteration does not help as it is mostly needed only for break nodes and also only when distance breaks are used. This is a very special scenario.

node_stack.s_route.vehicle_info()) &&
node_stack.delivery_node.distance_dim.forward_feasible(
node_stack.s_route.vehicle_info());
node_t<i_t, f_t, REQUEST>::window_forward_feasible(node_stack.delivery_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.

This change is not needed

Comment thread cpp/include/cuopt/routing/routing_structures.hpp Outdated
Comment thread cpp/src/routing/ges/compute_delivery_insertions.cuh Outdated
Comment thread cpp/src/routing/ges/squeeze.cuh Outdated
"Backward forward mismatch!");
"Time backward/forward mismatch!");
cuopt_assert(!(orginal_route.dimensions_info().has_dimension(dim_t::DIST) &&
orginal_route.dimensions_info().distance_dim.has_distance_window) ||

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.

Suggested change
orginal_route.dimensions_info().distance_dim.has_distance_window) ||
orginal_route.dimensions_info().distance_dim.has_constraints()) ||

distance can have max_distance constraint as well

Comment thread cpp/src/routing/local_search/breaks_insertion.cu
Comment thread cpp/src/routing/local_search/sliding_window.cu Outdated
Comment thread cpp/src/routing/local_search/sliding_window.cu Outdated
Comment thread cpp/src/routing/node/distance_node.cuh Outdated
return max(0., total_distance - vehicle_info.max_cost);
double arrival_window_f = prev.distance_window_forward + distance_between;
double upper_excess = max(0., arrival_window_f - next.distance_window_backward);
double lower_excess = max(0., next.distance_window_backward_min - arrival_window_f);

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.

I don't think we need lower_excess. Check the time dimension for how we do time combine

@np96 np96 Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi there.
I have read HY paper, so far I understand they don't treat early arrival as infeasible.
In case of time windows a vehcile may wait until time window opens. In distance case it is undesirable to arrive to early. If you ignore lower_excess, the vehicle may end up arriving at 20 KM when you want it to visit dist breaks in [60..80] km range.
I removed lower excess but in my opinion it is useful if you want to consider arriving to early as violating constraints.

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.

I think that should be okay, since the final objective is to minimize the distance, the solver will avoid such scenarios.

If we make this as a hard constraint, we may not find feasible solutions for some trivial cases.

Comment thread cpp/src/routing/node/distance_node.cuh Outdated
Comment thread cpp/src/routing/node/distance_node.cuh Outdated
@rg20 rg20 removed this from the 26.06 milestone May 22, 2026
@rg20

rg20 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Moving this out of 26.06 scope as this is in review process still.

@rg20
rg20 changed the base branch from release/26.06 to main May 22, 2026 18:10
@github-actions

Copy link
Copy Markdown

🔔 Hi @anandhkb, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you!

If this is an "epic" issue, then please add the "epic" label to this issue.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

1 similar comment
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

🔔 Hi @anandhkb, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you!

If this is an "epic" issue, then please add the "epic" label to this issue.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

@np96

np96 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Hi @rg20, thanks for review. I considered your comments. lower_excess was introduced to avoid distance waits which will happen if you consider the distance breaks in the same fashion as time breaks.
Currently, if a vehicle arrives to a time break to early, they may wait certain time. I believe it is not what you want for distances, since you don't want to allow it recharge too early (in worst case straight after previous charge).

Probably I am wrong somewhere, would be happy if you point out an error in my reasoning.

@np96
np96 force-pushed the feature/distance-breaks branch from 7b6e43d to ce2e403 Compare August 11, 2026 12:30
@np96

np96 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@rg20 @bbozkaya rebased and added DISTANCE_BREAK_COST as a soft constraint (default 1.0).

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
cpp/tests/routing/unit_tests/distance_breaks.cu (1)

250-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for has_distance_break_cost without has_distance_window.

Every node-level test sets has_distance_window and has_distance_break_cost together, here and at Lines 100-101 and Lines 132-133. No test exercises the break-cost flag on its own.

That combination is the one that reads an unallocated buffer in cpp/src/routing/route/distance_route.cuh (compute_cost, Line 207). A test that sets only has_distance_break_cost would pin the intended behavior, whether that is "the combination is impossible" or "the combination must produce zero cost".

🤖 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/tests/routing/unit_tests/distance_breaks.cu` around lines 250 - 253, Add
a node-level test covering cost_dimension_info_t with has_distance_break_cost
enabled and has_distance_window disabled, alongside the existing tests near the
referenced flag combinations. Assert the intended compute_cost behavior for this
configuration—either reject the impossible combination or return zero cost—and
ensure the test exercises distance_route.cuh’s compute_cost path without
requiring a distance-window buffer.

Source: Path instructions

python/cuopt_server/cuopt_server/tests/test_server.py (1)

887-890: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use pytest.mark.parametrize instead of an in-test loop.

The two scenarios run in one test function. If the first iteration fails, the second never runs, and the failure output does not show which iteration failed.

Parametrizing gives two independent test cases and self-describing failure output.

♻️ Proposed change
+@pytest.mark.parametrize(
+    "objectives,expected_cost",
+    [(None, 11.0), ({"distance_break_cost": 0}, 3.0)],
+)
 `@pytest.mark.usefixtures`("cuoptproc")
-def test_distance_break_cost_default():
+def test_distance_break_cost_default(objectives, expected_cost):
     cost_matrix = {0: [[0, 1, 1], [1, 0, 1], [1, 1, 0]]}

Then remove the for statement and unindent its body.

🤖 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 `@python/cuopt_server/cuopt_server/tests/test_server.py` around lines 887 -
890, Replace the in-test objectives/expected_cost loop in the affected server
test with pytest.mark.parametrize, naming both parameters and supplying the
existing two scenario tuples. Remove the for statement and unindent its body so
each scenario executes as an independent, self-describing test case.
cpp/src/routing/node/distance_node.cuh (1)

36-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Name the 1e18 unconstrained sentinel.

1e18 is the "no window" sentinel for distance_window_backward and window_end. The same literal is repeated in cpp/tests/routing/unit_tests/distance_breaks.cu (in make_route call sites and the header comment) and is implied by the buffer initialization in cpp/src/routing/route/distance_route.cuh. If the sentinel ever changes, the sites can diverge silently.

Define one constexpr double in this header and reference it everywhere.

♻️ Proposed change
 namespace detail {
 
+// Sentinel for an unconstrained distance-window upper bound.
+constexpr double unconstrained_distance = 1e18;
+
 // Distance dimension. Tracks the cumulative route distance (for vehicle.max_cost) and, when
-  double distance_window_backward = 1e18;
+  double distance_window_backward = unconstrained_distance;
   double window_start             = 0.0;
-  double window_end               = 1e18;
+  double window_end               = unconstrained_distance;
🤖 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/node/distance_node.cuh` around lines 36 - 45, Define a shared
constexpr double in distance_node.cuh for the unconstrained distance-window
sentinel, and replace the 1e18 initializers for distance_window_backward and
window_end with it. Update the distance_breaks.cu make_route call sites and
header comment, plus the corresponding initialization in distance_route.cuh, to
reference the same named constant instead of duplicating the literal.
🤖 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/route/distance_route.cuh`:
- Around line 201-216: Update distance_route.cuh in compute_cost so
distance_break_cost_forward is read only when both has_distance_window and
has_distance_break_cost are enabled, matching the allocation conditions. Add a
node-level test in cpp/tests/routing/unit_tests/distance_breaks.cu around lines
250-253 with has_distance_break_cost true and has_distance_window false to
verify this combination does not read the unallocated buffer.

In `@cpp/tests/routing/unit_tests/distance_breaks.cu`:
- Around line 295-307: Replace the namespace-scope mutable std::vector<float>
variables cost_matrix_3x3 and cost_matrix_5x5 with constexpr std::array
constants, adding the required array header include. Preserve their existing
dimensions and values so the cuopt::device_copy call sites continue accepting
them as contiguous ranges.

In `@python/cuopt_server/cuopt_server/utils/routing/data_definition.py`:
- Around line 134-191: Expand the VehicleDistanceBreak class docstring to
document all constructor fields, their validation constraints, and the
validation errors raised for invalid values. In
docs/cuopt/source/cuopt-python/routing/examples/distance_break_example.py lines
28-33, add complete type hints and substantive docstrings covering parameters,
returns, and raises for build_cost_matrix and main, or rename non-public helpers
with a leading underscore.

In `@python/cuopt/cuopt/routing/_serialize.py`:
- Around line 149-173: Update _distance_break to serialize each distance break
using the server restore schema: flat entries with min_range, max_range, and
n_cycles instead of distance_min, distance_max, and duration. Preserve the
existing vehicle grouping while ensuring appended breaks retain their input
cycle order.

In `@python/cuopt/cuopt/tests/routing/test_distance_breaks.py`:
- Around line 195-201: Extend test_invalid_n_cycles_wrong_type to include True
and ensure it raises ValueError. In DataModel.add_distance_break, explicitly
reject boolean n_cycles before the integer validation so True and False cannot
be treated as integers or passed to range().

---

Nitpick comments:
In `@cpp/src/routing/node/distance_node.cuh`:
- Around line 36-45: Define a shared constexpr double in distance_node.cuh for
the unconstrained distance-window sentinel, and replace the 1e18 initializers
for distance_window_backward and window_end with it. Update the
distance_breaks.cu make_route call sites and header comment, plus the
corresponding initialization in distance_route.cuh, to reference the same named
constant instead of duplicating the literal.

In `@cpp/tests/routing/unit_tests/distance_breaks.cu`:
- Around line 250-253: Add a node-level test covering cost_dimension_info_t with
has_distance_break_cost enabled and has_distance_window disabled, alongside the
existing tests near the referenced flag combinations. Assert the intended
compute_cost behavior for this configuration—either reject the impossible
combination or return zero cost—and ensure the test exercises
distance_route.cuh’s compute_cost path without requiring a distance-window
buffer.

In `@python/cuopt_server/cuopt_server/tests/test_server.py`:
- Around line 887-890: Replace the in-test objectives/expected_cost loop in the
affected server test with pytest.mark.parametrize, naming both parameters and
supplying the existing two scenario tuples. Remove the for statement and
unindent its body so each scenario executes as an independent, self-describing
test case.
🪄 Autofix

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: 8ac0a0dd-50ff-4a35-91c2-f5c2cb60f554

📥 Commits

Reviewing files that changed from the base of the PR and between f0ecadc and ce2e403.

📒 Files selected for processing (38)
  • cpp/include/cuopt/routing/data_model_view.hpp
  • cpp/include/cuopt/routing/routing_structures.hpp
  • cpp/src/routing/data_model_view.cu
  • cpp/src/routing/dimensions.cuh
  • cpp/src/routing/ges/squeeze.cuh
  • cpp/src/routing/local_search/breaks_insertion.cu
  • cpp/src/routing/node/distance_node.cuh
  • cpp/src/routing/node/node.cuh
  • cpp/src/routing/problem/problem.cu
  • cpp/src/routing/problem/special_nodes.cuh
  • cpp/src/routing/route/distance_route.cuh
  • cpp/src/routing/solution/solution.cuh
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/tests/routing/CMakeLists.txt
  • cpp/tests/routing/level1/l1_routing_test.cu
  • cpp/tests/routing/routing_test.cuh
  • cpp/tests/routing/unit_tests/distance_breaks.cu
  • docs/cuopt/source/cuopt-python/routing/examples/distance_break_example.py
  • docs/cuopt/source/cuopt-python/routing/routing-examples.rst
  • docs/cuopt/source/routing-features.rst
  • python/cuopt/cuopt/routing/_deferred.py
  • python/cuopt/cuopt/routing/_serialize.py
  • python/cuopt/cuopt/routing/structure/routing_utilities.pxd
  • python/cuopt/cuopt/routing/vehicle_routing.pxd
  • python/cuopt/cuopt/routing/vehicle_routing.py
  • python/cuopt/cuopt/routing/vehicle_routing_wrapper.pyx
  • python/cuopt/cuopt/tests/routing/test_data_model.py
  • python/cuopt/cuopt/tests/routing/test_distance_breaks.py
  • python/cuopt/cuopt/tests/routing/test_serialize.py
  • python/cuopt/cuopt/tests/routing/test_warnings_exceptions.py
  • python/cuopt_server/cuopt_server/tests/test_server.py
  • python/cuopt_server/cuopt_server/tests/test_set_solver_config.py
  • python/cuopt_server/cuopt_server/tests/utils/utils.py
  • python/cuopt_server/cuopt_server/utils/routing/data_definition.py
  • python/cuopt_server/cuopt_server/utils/routing/optimization_data_model.py
  • python/cuopt_server/cuopt_server/utils/routing/solver.py
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
  • python/cuopt_server/cuopt_server/utils/solver.py
🚧 Files skipped from review as they are similar to previous changes (15)
  • python/cuopt_server/cuopt_server/tests/utils/utils.py
  • cpp/src/routing/ges/squeeze.cuh
  • cpp/src/routing/local_search/breaks_insertion.cu
  • cpp/src/routing/solution/solution.cuh
  • python/cuopt_server/cuopt_server/utils/solver.py
  • cpp/src/routing/util_kernels/set_nodes_data.cuh
  • cpp/tests/routing/CMakeLists.txt
  • python/cuopt_server/cuopt_server/utils/routing/solver.py
  • cpp/src/routing/dimensions.cuh
  • python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py
  • python/cuopt/cuopt/routing/vehicle_routing_wrapper.pyx
  • cpp/src/routing/problem/special_nodes.cuh
  • cpp/src/routing/problem/problem.cu
  • cpp/src/routing/data_model_view.cu
  • python/cuopt/cuopt/routing/vehicle_routing.py

Comment thread cpp/src/routing/route/distance_route.cuh
Comment thread cpp/tests/routing/unit_tests/distance_breaks.cu Outdated
Comment thread python/cuopt_server/cuopt_server/utils/routing/data_definition.py
Comment thread python/cuopt/cuopt/routing/_serialize.py Outdated
Comment thread python/cuopt/cuopt/tests/routing/test_distance_breaks.py Outdated
@rg20

rg20 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

/ok to test 2560a85

@github-actions

Copy link
Copy Markdown

CI Test Summary

⏭️ All 5 test job(s) skipped.

Comment thread cpp/src/routing/node/node.cuh Outdated
@github-actions

Copy link
Copy Markdown

🔔 Hi @anandhkb, this pull request has had no activity for 7 days. Please update or let us know if it can be closed. Thank you!

If this is an "epic" issue, then please add the "epic" label to this issue.
If it is a PR and not ready for review, then please convert this to draft.
If you just want to switch off this notification, then use the "skip inactivity reminder" label.

Resolve modify/delete conflicts on the old host-export serialize path by
dropping it, and wire add_distance_break through the gRPC routing problem
so remote solves keep the new setter.

Signed-off-by: Rajesh Gandham <rgandham@nvidia.com>
@rg20
rg20 requested a review from a team as a code owner August 31, 2026 22:19
@rg20
rg20 requested review from aliceb-nv and chris-maes August 31, 2026 22:19
@rg20

rg20 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

/ok to test 7ac1f36

@ramakrishnap-nv ramakrishnap-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Awesome work @np96 , I have few comments on API structure and options, please let me know if you need help on any.

data_model.set_order_locations(cudf.Series([1, 2], dtype=np.int32))

data_model.add_distance_break(
vehicle_ids=0,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should it be just vehicle_id ?

Suggested change
vehicle_ids=0,
vehicle_id=0,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also we may need to support locations option with numpy and pandas input as well since we recently moved to supporting those input options as well.

default weight is ``1.0``; configure another positive weight to change the
tradeoff, or explicitly set it to ``0.0`` to disable the early-break penalty.

The example below sets up one vehicle, two customers, and a single break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would suggest moving the example details within the file so both are in one place.


Parameters
----------
vehicle_ids : int or sequence of int

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this be configured per vehicle at a time like we do for vehicle break? @rg20

rg20 added 2 commits September 1, 2026 10:45
Match the per-vehicle time-window break API (distance_min/distance_max
instead of earliest/latest) across Python, the service, gRPC, and C++.

Signed-off-by: Rajesh Gandham <rgandham@nvidia.com>
@rg20

rg20 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@ramakrishnap-nv I followed your recommendation and made the distance breaks consistent with per vehicle time window based breaks. Can you please re-review?

@np96 for viz!

@rg20

rg20 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

/ok to test 30cacd3

@ramakrishnap-nv ramakrishnap-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor doc suggestion rest looks good.

tradeoff, or explicitly set it to ``0.0`` to disable the early-break penalty.

The example below sets up one vehicle, two customers, and a single break
location. With ``distance_max=75``, the solver must insert one break within

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since these details are already covered as part of comment in example, may be we can remove it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi there. Fixed this one. Also updated the PR description.
Thanks.

Signed-off-by: Nikolai Poperechnyi <n.poperechnyi@gmail.com>
@ramakrishnap-nv

Copy link
Copy Markdown
Collaborator

/ok to test 4aebba5

rg20 added 2 commits September 2, 2026 13:43
Raise the CUDA 12 libcuopt compressed wheel allowance to 725 MiB and apply the repository formatters to the distance-break changes.

Signed-off-by: Rajesh Gandham <rgandham@nvidia.com>
@rg20

rg20 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

/ok to test 149b600

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

Labels

feature request New feature or request non-breaking Introduces a non-breaking change P1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants