Skip to content

Add C api callbacks for getting and setting solutions#779

Merged
rapids-bot[bot] merged 43 commits intoNVIDIA:release/26.02from
akifcorduk:c_api_callbacks
Feb 4, 2026
Merged

Add C api callbacks for getting and setting solutions#779
rapids-bot[bot] merged 43 commits intoNVIDIA:release/26.02from
akifcorduk:c_api_callbacks

Conversation

@akifcorduk
Copy link
Contributor

@akifcorduk akifcorduk commented Jan 15, 2026

This PR adds callbacks to C API. Users can now use C API to implement get_solution and set_solution callbacks.

This PR is a breaking PR that changes callback input/output pointers from device memory to host memory. We are also adding a user_data pointer for user to be more flexible inside the callbacks as well as dual bound for the B&B tree.

Previously callbacks were not enabled when third party presolved was enabled. Now we are able to handle uncrushing of solutions when third party presolve is enabled on get_solution callbacks. Registering a set_solution callback disables presolve as there is no way of crushing a solution in original space to reduced space when using third party presolve.

The examples and tests are modified to reflect the change.

Summary by CodeRabbit

  • New Features

    • MIP incumbent get/set callbacks with optional user_data and registration; server/client flag to enable set-callback behavior.
  • Behavior

    • Incumbent records now include bound and user_data; solution-bound handling made thread-safe. Registering a set-solution callback may disable presolve and enables host-side incumbent injection.
  • Tests

    • Added tests for get-only and get-and-set flows, user_data propagation, bound reporting, and presolve interactions.
  • Documentation

    • Examples and docs updated to show callbacks, user_data, bound reporting, and set-callback usage.

@akifcorduk akifcorduk added this to the 26.02 milestone Jan 15, 2026
@akifcorduk akifcorduk requested a review from a team as a code owner January 15, 2026 14:31
@akifcorduk akifcorduk added the non-breaking Introduces a non-breaking change label Jan 15, 2026
@akifcorduk akifcorduk requested a review from chris-maes January 15, 2026 14:31
@akifcorduk akifcorduk added the improvement Improves an existing functionality label Jan 15, 2026
@akifcorduk akifcorduk requested a review from nguidotti January 15, 2026 14:31
@akifcorduk akifcorduk added the mip label Jan 15, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

Add user_data support end-to-end for MIP incumbent get/set callbacks (C API → C++ internals → Cython → Python → server); expand presolve/Papilo mapping and presolve_data helpers; make solution_bound atomic with accessors; update callback signatures, invocation sites, tests, docs, and job/web-server wiring. Duplicate insertions present in some C test/header blocks.

Changes

Cohort / File(s) Summary
C API & wrappers
cpp/include/cuopt/linear_programming/cuopt_c.h, cpp/src/linear_programming/cuopt_c.cpp
Add C typedefs and registration functions for MIP get/set callbacks with user_data; add C→C++ adapter wrappers and solver_settings_handle to own callback lifetimes. (Duplicate declarations observed in header.)
Callback interfaces & default impls
cpp/include/.../utilities/internals.hpp, cpp/include/.../utilities/callbacks_implems.hpp, cpp/include/.../mip/solver_settings.hpp, cpp/include/.../solver_settings.hpp
Add void* user_data storage/accessors to base callbacks; extend get_solution/set_solution signatures to include solution_bound and user_data; update Python marshalling paths and default callback implementations; change mip_scaling default.
Solver settings lifecycle & registration
cpp/src/linear_programming/cuopt_c.cpp, cpp/src/mip/solver_settings.cu, cpp/src/math_optimization/solver_settings.cu, cpp/include/.../cuopt_c.h
Introduce solver_settings_handle_t to own settings and callback wrappers; update create/destroy APIs; propagate user_data through set_mip_callback storing it with callbacks.
Callback invocation & diversity population
cpp/src/mip/diversity/population.cu, cpp/src/mip/diversity/population.cuh
Add invoke_get_solution_callback, rework GET/SET flows to pass (solution, objective, bound, user_data); perform host↔device syncs, host-side SET handling, and add diagnostics and consistency checks.
Presolve / Papilo mapping & presolve_data
cpp/src/mip/presolve/third_party_presolve.hpp, .../third_party_presolve.cpp, cpp/src/mip/problem/presolve_data.cuh, .../presolve_data.cu, cpp/src/mip/problem/problem.cu, .../problem.cuh
Add reduced↔original mapping containers and accessors; implement Papilo presolve data storage, pre/post-process assignment/solution helpers, uncrush_primal_solution, and problem wrappers to set/propagate Papilo data.
Solve flow, context & preemption
cpp/src/mip/solve.cu, cpp/src/mip/solver.cu, cpp/src/mip/solver_context.cuh, cpp/src/mip/solver_solution.cu
Wire diversity manager into solver context for callback invocation and B&B preemption; persist presolve results and propagate Papilo mappings; disable presolve when SET callbacks exist; use stats accessors for solution bound.
Solver stats & user-bound callback
cpp/include/.../mip/solver_stats.hpp, cpp/src/dual_simplex/branch_and_bound.hpp, cpp/src/dual_simplex/branch_and_bound.cpp, cpp/src/mip/diversity/diversity_manager.cu
Make solution_bound an std::atomic with ctor/copy/assignment and add get_solution_bound/set_solution_bound; add user-bound callback setter and propagate lower bounds into that callback.
Presolve propagation in solve plumbing
cpp/src/mip/solve.cu, cpp/src/mip/problem/problem.cu
Persist presolve_result, construct reduced problems from presolve outcome, forward implied-integers and Papilo mappings into downstream flows and post-processing.
Tests & utilities (C/C++/Python)
cpp/tests/linear_programming/c_api_tests/*, cpp/tests/mip/incumbent_callback_test.cu, cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh, cpp/tests/mip/mip_utils.cuh, python/.../tests/*, python/cuopt_server/.../tests/test_incumbents.py
Add/adjust tests for get-only and get+set callback flows with user_data; adapt test utilities to accept host std::vector/Python lists and validate bound; note duplicate insertion in a C test file.
Python/Cython bindings & high-level API
python/cuopt/.../internals/internals.pyx, python/.../solver/solver.pxd, python/.../solver/solver_wrapper.pyx, python/.../solver_settings/solver_settings.py
Expose _user_data/user_data/get_user_data_ptr on PyCallback; update pxd/wrappers to pass callback user_data pointers into C++; update high-level set_mip_callback to accept and propagate user_data.
Server, job-queue & webserver wiring
python/cuopt_server/.../linear_programming/solver.py, .../job_queue.py, .../solver.py, .../webserver.py, .../data_definition.py
Thread incumbent_set_solutions flag through job creation and solve calls; register callbacks with request id as user_data; optionally register SetSolutionCallback on server; include bound in intermediate responses and IncumbentSolution model.
Docs & examples
docs/.../incumbent_solutions_example.py, docs/.../incumbent_callback_example.py
Update example callback constructors and signatures to accept user_data; include bound and user_data in incumbent records and pass user_data to set_mip_callback.
Local search, LNS & feasibility jump
cpp/src/mip/local_search/*, cpp/src/mip/feasibility_jump/feasibility_jump.cu, cpp/src/mip/diversity/lns/rins.cu
Add diversity-manager preemption checks into local searches and feasibility pump; periodic injection of external solutions in feasibility_jump; ensure stream syncs in RINS/LNS for safe host↔device updates.
Build, packaging & CI housekeeping
python/libcuopt/pyproject.toml, ci/run_cuopt_pytests.sh, ci/run_cuopt_server_pytests.sh, conda/recipes/libcuopt/recipe.yaml
SPDX year bumps; add pytest -s flag; increase package size limit and augment conda BUILD_EXTRA_FLAGS for PR builds.
Misc test/util adjustments
cpp/tests/mip/mip_utils.cuh, cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
Add overloads to accept host std::vector solutions and bounds; add host-side objective/bounds checks to support new solution representations.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add C api callbacks for getting and setting solutions' clearly and concisely summarizes the main objective of the changeset, which is to add C API callback functionality for solution management.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@akifcorduk akifcorduk requested a review from a team as a code owner January 16, 2026 15:12
Copy link

@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.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@cpp/include/cuopt/linear_programming/cuopt_c.h`:
- Around line 706-728: The Doxygen for cuOptSetMipGetSolutionCallback and
cuOptSetMipSetSolutionCallback is missing documentation for the new user_data
parameter; update both comment blocks to add a `@param`[in] user_data description
(e.g., "User-defined pointer passed through to the callback") and mention that
it will be forwarded to the respective
cuOptMipGetSolutionCallback/cuOptMipSetSolutionCallback when invoked so the
public API documents the parameter contract.

In `@cpp/include/cuopt/linear_programming/mip/solver_settings.hpp`:
- Around line 37-41: Update the Doxygen block for set_mip_callback to document
the new user_data parameter: add a brief `@param` description for user_data
explaining it is an opaque pointer passed to the callback (e.g., "pointer to
user-defined data forwarded to the callback"), and ensure the existing `@param`
for callback remains accurate; modify the comment above the function declaration
in solver_settings.hpp (the set_mip_callback declaration) so the public API docs
enumerate both callback and user_data.

In `@cpp/include/cuopt/linear_programming/utilities/internals.hpp`:
- Around line 34-36: Add brief Doxygen comments above the public accessor
methods set_user_data and get_user_data describing their purpose: annotate
set_user_data with "Store user-defined context data for callback invocation."
and get_user_data with "Retrieve user-defined context data passed to callbacks."
Place these comments immediately above the corresponding method declarations in
internals.hpp so the public solver callback interface methods are documented per
the header-file documentation guideline.

In `@cpp/src/mip/diversity/diversity_manager.cu`:
- Around line 446-450: There is an unconditional exit(0) that aborts the solver
flow and prevents LP solution handling; remove the exit(0) invocation so
execution continues into the subsequent LP handling (the if
(ls.lp_optimal_exists) block) after calling clamp_within_var_bounds, ensuring
normal callback and MIP behavior; search for the symbols
clamp_within_var_bounds, exit(0), and ls.lp_optimal_exists to locate and delete
the exit call so control can proceed.

In `@cpp/src/mip/solver_settings.cu`:
- Around line 27-31: The set_mip_callback function currently pushes a nullptr
into mip_callbacks_ because it only guards the set_user_data call; update
mip_solver_settings_t<i_t, f_t>::set_mip_callback to first check if callback is
nullptr and if so do not push it (either return early or ignore the call),
otherwise call callback->set_user_data(user_data) and then push_back(callback)
so mip_callbacks_ never contains null entries; this ensures later dereferences
of entries in mip_callbacks_ (e.g., where callbacks are invoked) are safe.

In `@python/cuopt/cuopt/linear_programming/internals/internals.pyx`:
- Around line 70-76: The runtime AttributeError happens because _user_data is
assigned dynamically in the cdef classes; declare the attribute in both callback
extension types (e.g., in the class body of GetSolutionCallback and
SetSolutionCallback) as a Cython Python-object attribute like "cdef object
_user_data" (or "cdef public object _user_data" if you need external access),
then keep the existing __init__ assignment and the get_user_data_ptr cast that
uses self._user_data; ensure the declaration appears in both classes so lines
assigning/reading _user_data no longer raise.
🧹 Nitpick comments (1)
cpp/include/cuopt/linear_programming/solver_settings.hpp (1)

84-85: Document user_data ownership/lifetime in the public API.

Adding a brief Doxygen note will clarify that the pointer must remain valid for the duration of callback usage and that it’s passed through verbatim.

✍️ Suggested doc addition
-  void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
-                        void* user_data                               = nullptr);
+  /**
+   * Register a MIP solution callback.
+   * `@param` callback Callback instance (must outlive solver usage).
+   * `@param` user_data Opaque pointer passed back to the callback; must remain valid
+   *                  while the callback can be invoked.
+   */
+  void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
+                        void* user_data                               = nullptr);

As per coding guidelines, public headers should keep docs in sync with API changes.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 943b632 and 41a3b68.

📒 Files selected for processing (16)
  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
  • cpp/src/linear_programming/cuopt_c.cpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/src/mip/diversity/population.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
  • cpp/tests/mip/incumbent_callback_test.cu
  • python/cuopt/cuopt/linear_programming/internals/internals.pyx
  • python/cuopt/cuopt/linear_programming/solver/solver.pxd
  • python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx
  • python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/linear_programming/cuopt_c.cpp
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{cu,cuh,cpp,hpp,h}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

**/*.{cu,cuh,cpp,hpp,h}: Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events
Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results
Check numerical stability: prevent overflow/underflow, precision loss, division by zero/near-zero, and use epsilon comparisons for floating-point equality checks
Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)
Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations
For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle
Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution
Assess algorithmic complexity for large-scale problems (millions of variables/constraints); ensure O(n log n) or better complexity, not O(n²) or worse
Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems
Identify assertions with overly strict numerical tolerances that fail on legitimate degenerate/edge cases (near-zero pivots, singular matrices, empty problems)
Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state
Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication
Check that hard-coded GPU de...

Files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
**/*.{h,hpp,py}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

Verify C API does not break ABI stability (no struct layout changes, field reordering); maintain backward compatibility in Python and server APIs with deprecation warnings

Files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
**/*.{cpp,hpp,h}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

**/*.{cpp,hpp,h}: Check for unclosed file handles when reading MPS/QPS problem files; ensure RAII patterns or proper cleanup in exception paths
Validate input sanitization to prevent buffer overflows and resource exhaustion attacks; avoid unsafe deserialization of problem files
Prevent thread-unsafe use of global and static variables; use proper mutex/synchronization in server code accessing shared solver state

Files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
**/*.{cu,cpp,hpp,h}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code

Files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
cpp/include/cuopt/**/*

⚙️ CodeRabbit configuration file

cpp/include/cuopt/**/*: For public header files (C++ API):

  • Check if new public functions/classes have documentation comments (Doxygen format)
  • Flag API changes that may need corresponding docs/ updates
  • Verify parameter descriptions match actual types/behavior
  • Suggest documenting thread-safety, GPU requirements, and numerical behavior
  • For breaking changes, recommend updating docs and migration guides

Files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/include/cuopt/linear_programming/utilities/internals.hpp
**/*.{cu,cuh}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

**/*.{cu,cuh}: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification
Avoid reinventing functionality already available in Thrust, CCCL, or RMM libraries; prefer standard library utilities over custom implementations

Files:

  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/src/mip/diversity/population.cu
  • cpp/src/mip/diversity/diversity_manager.cu
**/*.cu

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

**/*.cu: Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations
Detect inefficient GPU kernel launches with low occupancy or poor memory access patterns; optimize for coalesced memory access and minimize warp divergence in hot paths

Files:

  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/src/mip/diversity/population.cu
  • cpp/src/mip/diversity/diversity_manager.cu
**/*test*.{cpp,cu,py}

📄 CodeRabbit inference engine (.github/.coderabbit_review_guide.md)

**/*test*.{cpp,cu,py}: Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)
Ensure test isolation: prevent GPU state, cached memory, and global variables from leaking between test cases; verify each test independently initializes its environment
Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover
Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations
Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling

Files:

  • cpp/tests/mip/incumbent_callback_test.cu
🧠 Learnings (23)
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate algorithm correctness in optimization logic: simplex pivots, branch-and-bound decisions, routing heuristics, and constraint/objective handling must produce correct results

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Check that hard-coded GPU device IDs and resource limits are made configurable; abstract multi-backend support for different CUDA versions

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Track GPU device memory allocations and deallocations to prevent memory leaks; ensure cudaMalloc/cudaFree balance and cleanup of streams/events

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure race conditions are absent in multi-GPU code and multi-threaded server implementations; verify proper synchronization of shared state

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cpp,hpp,h} : Avoid inappropriate use of exceptions in performance-critical GPU operation paths; prefer error codes or CUDA error checking for latency-sensitive code

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Refactor code duplication in solver components (3+ occurrences) into shared utilities; for GPU kernels, use templated device functions to avoid duplication

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify error propagation from CUDA to user-facing APIs is complete; ensure CUDA errors are caught and mapped to meaningful user error codes

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.cu : Verify race conditions and correctness of GPU kernel shared memory, atomics, and warp-level operations

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Verify correct problem size checks before expensive GPU/CPU operations; prevent resource exhaustion on oversized problems

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Eliminate unnecessary host-device synchronization (cudaDeviceSynchronize) in hot paths that blocks GPU pipeline; use streams and events for async execution

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : For concurrent CUDA operations (barriers, async operations), explicitly create and manage dedicated streams instead of reusing the default stream; document stream lifecycle

Applied to files:

  • cpp/include/cuopt/linear_programming/cuopt_c.h
  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip/solver_settings.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Ensure test isolation: prevent GPU state, cached memory, and global variables from leaking between test cases; verify each test independently initializes its environment

Applied to files:

  • cpp/include/cuopt/linear_programming/solver_settings.hpp
  • cpp/src/mip/solver_settings.cu
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/mip/diversity/population.cu
  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)

Applied to files:

  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2026-01-15T20:41:51.616Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 697
File: cpp/src/dual_simplex/diving_heuristics.cpp:200-228
Timestamp: 2026-01-15T20:41:51.616Z
Learning: In cpp/src/dual_simplex/diving_heuristics.cpp, the calculate_variable_locks function incorrectly indexes lp_problem.lower[nz_row] and lp_problem.upper[nz_row] where nz_row is a row index (0 to num_rows-1), but lower and upper are variable bounds of size num_cols indexed by column. This causes semantic errors and potential out-of-bounds access when num_rows > num_cols. Since lp_problem_t constraints are always equalities (A*x = rhs) after presolve, lock counting should treat each nonzero as contributing equally to both up_lock and down_lock, rather than checking constraint bounds.

Applied to files:

  • cpp/src/mip/diversity/diversity_manager.cu
📚 Learning: 2025-12-04T20:09:09.264Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 602
File: cpp/src/linear_programming/solve.cu:732-742
Timestamp: 2025-12-04T20:09:09.264Z
Learning: In cpp/src/linear_programming/solve.cu, the barrier solver does not currently return INFEASIBLE or UNBOUNDED status. It only returns OPTIMAL, TIME_LIMIT, NUMERICAL_ISSUES, or CONCURRENT_LIMIT.

Applied to files:

  • cpp/src/mip/diversity/diversity_manager.cu
  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for algorithm phase transitions: verify correct initialization of bounds and state when transitioning from presolve to simplex to diving to crossover

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Test with free variables, singleton problems, and extreme problem dimensions near resource limits to validate edge case handling

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*test*.{cpp,cu,py} : Write tests validating numerical correctness of optimization results (not just 'runs without error'); test degenerate cases (infeasible, unbounded, empty, singleton problems)

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-11-25T10:20:49.822Z
Learnt from: CR
Repo: NVIDIA/cuopt PR: 0
File: .github/.coderabbit_review_guide.md:0-0
Timestamp: 2025-11-25T10:20:49.822Z
Learning: Applies to **/*.{cu,cuh,cpp,hpp,h} : Ensure variables and constraints are accessed from the correct problem context (original vs presolve vs folded vs postsolve); verify index mapping consistency across problem transformations

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-12-06T00:22:48.638Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/tests/linear_programming/c_api_tests/c_api_test.c:1033-1048
Timestamp: 2025-12-06T00:22:48.638Z
Learning: In cuOPT's quadratic programming API, when a user provides a quadratic objective matrix Q via set_quadratic_objective_matrix or the C API functions cuOptCreateQuadraticProblem/cuOptCreateQuadraticRangedProblem, the API internally computes Q_symmetric = Q + Q^T and the barrier solver uses 0.5 * x^T * Q_symmetric * x. From the user's perspective, the convention is x^T Q x. For a diagonal Q with values [q1, q2, ...], the resulting quadratic terms are q1*x1^2 + q2*x2^2 + ...

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
📚 Learning: 2025-12-04T04:11:12.640Z
Learnt from: chris-maes
Repo: NVIDIA/cuopt PR: 500
File: cpp/src/dual_simplex/scaling.cpp:68-76
Timestamp: 2025-12-04T04:11:12.640Z
Learning: In the cuOPT dual simplex solver, CSR/CSC matrices (including the quadratic objective matrix Q) are required to have valid dimensions and indices by construction. Runtime bounds checking in performance-critical paths like matrix scaling is avoided to prevent slowdowns. Validation is performed via debug-only check_matrix() calls wrapped in `#ifdef` CHECK_MATRIX.

Applied to files:

  • cpp/tests/linear_programming/c_api_tests/c_api_test.c
🧬 Code graph analysis (7)
cpp/include/cuopt/linear_programming/cuopt_c.h (1)
cpp/src/linear_programming/cuopt_c.cpp (4)
  • cuOptSetMipGetSolutionCallback (753-764)
  • cuOptSetMipGetSolutionCallback (753-755)
  • cuOptSetMipSetSolutionCallback (766-777)
  • cuOptSetMipSetSolutionCallback (766-768)
cpp/include/cuopt/linear_programming/solver_settings.hpp (2)
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)
  • callback (40-41)
cpp/include/cuopt/linear_programming/utilities/internals.hpp (1)
  • user_data (35-35)
cpp/src/math_optimization/solver_settings.cu (5)
cpp/src/mip/solver_settings.cu (2)
  • set_mip_callback (27-32)
  • set_mip_callback (27-28)
python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py (1)
  • set_mip_callback (248-305)
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)
  • callback (40-41)
cpp/include/cuopt/linear_programming/solver_settings.hpp (1)
  • callback (84-85)
cpp/include/cuopt/linear_programming/utilities/internals.hpp (1)
  • user_data (35-35)
cpp/src/mip/solver_settings.cu (3)
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)
  • callback (40-41)
cpp/include/cuopt/linear_programming/solver_settings.hpp (1)
  • callback (84-85)
cpp/include/cuopt/linear_programming/utilities/internals.hpp (1)
  • user_data (35-35)
cpp/tests/mip/incumbent_callback_test.cu (3)
cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp (12)
  • data (20-29)
  • data (20-20)
  • data (31-39)
  • data (31-31)
  • data (41-56)
  • data (41-41)
  • data (63-72)
  • data (63-63)
  • data (74-82)
  • data (74-74)
  • data (84-99)
  • data (84-84)
cpp/include/cuopt/linear_programming/utilities/internals.hpp (3)
  • data (47-47)
  • data (56-56)
  • user_data (35-35)
cpp/src/linear_programming/cuopt_c.cpp (2)
  • data (57-63)
  • data (57-57)
cpp/include/cuopt/linear_programming/utilities/internals.hpp (2)
cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp (12)
  • data (20-29)
  • data (20-20)
  • data (31-39)
  • data (31-31)
  • data (41-56)
  • data (41-41)
  • data (63-72)
  • data (63-63)
  • data (74-82)
  • data (74-74)
  • data (84-99)
  • data (84-84)
cpp/src/linear_programming/cuopt_c.cpp (4)
  • data (57-63)
  • data (57-57)
  • data (73-78)
  • data (73-73)
cpp/tests/linear_programming/c_api_tests/c_api_test.c (1)
cpp/src/linear_programming/cuopt_c.cpp (16)
  • cuOptCreateProblem (140-195)
  • cuOptCreateProblem (140-153)
  • cuOptCreateSolverSettings (619-625)
  • cuOptCreateSolverSettings (619-619)
  • cuOptSetMipGetSolutionCallback (753-764)
  • cuOptSetMipGetSolutionCallback (753-755)
  • cuOptSetMipSetSolutionCallback (766-777)
  • cuOptSetMipSetSolutionCallback (766-768)
  • cuOptSolve (792-840)
  • cuOptSolve (792-794)
  • cuOptDestroyProblem (387-393)
  • cuOptDestroyProblem (387-387)
  • cuOptDestroySolverSettings (627-632)
  • cuOptDestroySolverSettings (627-627)
  • cuOptDestroySolution (842-861)
  • cuOptDestroySolution (842-842)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.12, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.13, arm64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.11, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-mps-parser / 13.1.0, 3.10, amd64, rockylinux8
  • GitHub Check: wheel-build-cuopt-sh-client / 13.1.0, 3.10, amd64, rockylinux8
🔇 Additional comments (16)
python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx (1)

185-198: LGTM — user_data is forwarded to the native callback.

Nice, this preserves backward compatibility while wiring the user context into set_mip_callback.

python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py (1)

248-305: LGTM — user_data is preserved on the callback.

This keeps the user context alive and aligns with the new callback plumbing.

cpp/src/math_optimization/solver_settings.cu (2)

3-3: No review comment needed.


385-388: LGTM: user_data is forwarded to MIP settings.

Clean passthrough that aligns with the new callback API.

cpp/include/cuopt/linear_programming/solver_settings.hpp (1)

3-3: No review comment needed.

cpp/src/mip/solver_settings.cu (1)

3-3: No review comment needed.

python/cuopt/cuopt/linear_programming/solver/solver.pxd (2)

1-1: No review comment needed.


80-82: LGTM: Cython declaration matches the updated C++ API.

The user_data parameter is correctly surfaced here.

cpp/src/mip/diversity/population.cu (2)

3-3: No review comment needed.


302-303: LGTM: user_data is forwarded to both GET and SET callbacks.

This keeps the callback context intact through the MIP solution pipeline.

Also applies to: 324-326

python/cuopt/cuopt/linear_programming/internals/internals.pyx (1)

24-32: Signature alignment with native callbacks looks correct.

cpp/tests/mip/incumbent_callback_test.cu (2)

43-45: LGTM for the updated set_solution signature.


68-70: LGTM for the updated get_solution signature.

cpp/tests/linear_programming/c_api_tests/c_api_test.c (2)

12-12: No issues with the CUDA runtime include.


135-296: Good callback test coverage and cleanup flow.

cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp (1)

41-55: Backward-compatible user_data handling looks good.

Also applies to: 84-98

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@python/cuopt/cuopt/linear_programming/internals/internals.pyx`:
- Around line 86-97: The _user_data attribute and get_user_data_ptr() are
duplicated and lack a setter in GetSolutionCallback/SetSolutionCallback; move
_user_data, its initialization and the get_user_data_ptr() implementation into
the PyCallback base class (add initialization in PyCallback.__init__ and expose
a setter there), remove the duplicated _user_data and get_user_data_ptr() from
GetSolutionCallback and SetSolutionCallback, and ensure subclasses call
super().__init__() (or otherwise inherit) so the native_callback.pyCallbackClass
assignment in their __init__ still references the correct self.
- Around line 66-77: The Cython cdef attribute _user_data is currently C-level
only so Python assigns a shadow attribute (making user_data passed to
set_mip_callback ignored); expose it to Python by adding a Python-accessible
property or declaring it cdef public (e.g., provide a user_data property with
getter/setter that stores the C-level _user_data and returns/accepts a Python
object) in the cdef class (adjust __init__ to use the new property if needed),
ensure get_user_data_ptr() reads the same backing field, and update caller sites
(solver_settings.set_mip_callback and the analogous SetSolutionCallback usage)
to set callback.user_data = user_data instead of callback._user_data = user_data
so the Python-assigned value is visible to get_user_data_ptr().
🧹 Nitpick comments (1)
python/cuopt/cuopt/linear_programming/internals/internals.pyx (1)

72-77: Document the lifetime contract for the returned pointer.

The returned uintptr_t is a borrowed reference — no Py_INCREF is performed. The pointer remains valid only while self (the callback object) is alive. If C code caches this pointer beyond the callback's lifetime, it risks use-after-free.

Consider adding a docstring clarifying that the caller must keep the callback object alive while using the pointer:

cpdef uintptr_t get_user_data_ptr(self):
    """Return raw pointer to user_data. Valid only while this callback is alive."""
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fda3f4f and 4e50db4.

📒 Files selected for processing (2)
  • python/cuopt/cuopt/linear_programming/internals/internals.pyx
  • python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx
🔇 Additional comments (2)
python/cuopt/cuopt/linear_programming/internals/internals.pyx (2)

11-11: LGTM!

Using cpython.ref cimport PyObject is the idiomatic Cython approach, cleaner than a manual extern declaration.


21-29: LGTM!

The extern declarations correctly extend the callback signatures to include the user_data parameter, enabling per-callback user context propagation.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

/**
* @brief Callback for injecting MIP solutions with user context.
*
* @param[out] solution - Device pointer to solution values to set.
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably should mention the dimension of the solution_values (i.e. that it needs to be equal to the number of variables in the original problem).

Also, do we crush the solution from the original variables into the presolve variables when MIP presolve is turned on?

Copy link
Contributor

Choose a reason for hiding this comment

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

And this should probably be a host pointer rather than a device pointer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@chris-maes Good catch! All callbacks are disabled if 3rd party presolve(Papilo) is enabled. Our own presolve changes are handled though. I believe this is a blocker for primal integral experiments. I will give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have enabled third party presolve and handled crushing/uncrushing of solutions.

Comment on lines 687 to 688
* @param[in] solution - Device pointer to incumbent solution values.
* @param[in] objective_value - Device pointer to incumbent objective value.
Copy link
Contributor

@aliceb-nv aliceb-nv Jan 21, 2026

Choose a reason for hiding this comment

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

I wonder if these should be host pointers instead, in terms of API design. So far the API has been abstracting away the reality of the CUDA implementation (such as the corresponding context, the streams used...) away from the user, so this may represent a break in the philosophy. Plus we don't expose the GPU problem structure either, so it is likely the user will have to copy it back to the host either way to do any useful work
Maybe device-space callbacks could be implemented later on if need be? We might have to discuss this

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. The rest of the API uses abstracts out the device. Probably we should provide a host pointer here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are agnostic to device/host memory in all APIs AFAIK. I can modify it to "Host/Device" pointer? The only caveat of supporting device pointers officialy is that the copies will be done by internal stream.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made all callbacks to input/output host pointers.

@rgsl888prabhu rgsl888prabhu changed the base branch from main to release/26.02 January 22, 2026 16:39
Copy link
Contributor

@nguidotti nguidotti left a comment

Choose a reason for hiding this comment

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

Thanks for the hard work, Akif. Looks good to me!

solver_settings_t<cuopt_int_t, cuopt_float_t>* settings =
new solver_settings_t<cuopt_int_t, cuopt_float_t>();
*settings_ptr = static_cast<cuOptSolverSettings>(settings);
solver_settings_handle_t* settings_handle = new solver_settings_handle_t();
Copy link
Contributor

Choose a reason for hiding this comment

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

It is not better to use an unique_ptr with make_unique and reset here, then the new/delete? Just to avoid memory leaks if the user forget to call cuOptDestroySolverSettings.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

new is used everywhere in C API implementation, i wanted to stay consistent. Maybe we can consider it for anotehr PR.

Copy link

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cpp/src/mip/diversity/population.cu`:
- Around line 322-327: The SET_SOLUTION callback allocates incumbent_assignment
using callback_num_variables from
problem_ptr->original_problem_ptr->get_n_variables() without accounting for
Papilo presolve; update the logic where callback_num_variables is set (near
set_sol_callback and incumbent_assignment) to mirror the GET_SOLUTION handling:
if problem_ptr->has_papilo_presolve_data() then use
problem_ptr->get_papilo_original_num_variables() instead of
original_problem_ptr->get_n_variables(), so the buffer size matches the
callback's expected pre-Papilo variable count.
🧹 Nitpick comments (3)
cpp/src/mip/diversity/population.cu (1)

364-372: Consider relaxing the assertion tolerance or making it configurable.

The assertion at line 371-372 uses a hard-coded 1e-6 tolerance for objective mismatch validation. While the debug logging at lines 364-370 handles the case gracefully, the assertion could trigger in production for legitimate numerical precision differences, especially after scaling/unscaling operations.

💡 Consider using a relative tolerance check
-      cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= 1e-6,
+      f_t max_abs = std::max(std::abs(outside_sol.get_user_objective()), std::abs(outside_sol_objective));
+      f_t tol = std::max(1e-6, 1e-9 * max_abs);  // relative + absolute tolerance
+      cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= tol,
                    "External solution objective mismatch");
cpp/src/mip/solver.cu (1)

109-114: Consider extracting duplicated callback invocation logic.

The pattern of iterating over callbacks, checking for GET_SOLUTION type, casting, and invoking invoke_get_solution_callback is repeated three times (lines 109-114, 131-136, 160-166). This could be consolidated into a helper function to reduce duplication and improve maintainability.

♻️ Proposed helper function
// Helper to invoke GET_SOLUTION callbacks
template <typename i_t, typename f_t>
void invoke_get_solution_callbacks(mip_solver_context_t<i_t, f_t>& context,
                                   diversity_manager_t<i_t, f_t>& dm,
                                   solution_t<i_t, f_t>& sol)
{
  for (auto callback : context.settings.get_mip_callbacks()) {
    if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
      auto get_sol_callback = static_cast<internals::get_solution_callback_t*>(callback);
      dm.population.invoke_get_solution_callback(sol, get_sol_callback);
    }
  }
}

Then replace the three blocks with:

invoke_get_solution_callbacks(context, dm, sol);

Based on learnings: "Refactor code duplication in solver components (3+ occurrences) into shared utilities."

Also applies to: 131-136, 160-166

cpp/src/mip/diversity/lns/rins.cu (1)

125-125: Use event-based stream synchronization to avoid blocking with mutex held.

At Line 125, sync_stream() blocks the host while the population write mutex is held, increasing contention in the RINS hot path. Replace with the existing event_handler_t utility: record an event on the best-feasible stream and wait on it asynchronously from rins_handle, or defer the synchronization outside the lock scope. The event_handler_t class (in cpp/src/utilities/event_handler.cuh) with record() and stream_wait() methods is already proven in the LP solver for this pattern.

Comment on lines +322 to 327
auto set_sol_callback = static_cast<internals::set_solution_callback_t*>(callback);
f_t user_bound = context.stats.get_solution_bound();
auto callback_num_variables = problem_ptr->original_problem_ptr->get_n_variables();
rmm::device_uvector<f_t> incumbent_assignment(callback_num_variables,
sol.handle_ptr->get_stream());
solution_t<i_t, f_t> outside_sol(sol);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the SET_SOLUTION callback setup in population.cu
cat -n cpp/src/mip/diversity/population.cu | sed -n '315,335p'

Repository: NVIDIA/cuopt

Length of output: 1484


🏁 Script executed:

# Find the GET_SOLUTION callback setup in solver.cu mentioned in the review
cat -n cpp/src/linear_programming/solver.cu | sed -n '85,100p'

Repository: NVIDIA/cuopt

Length of output: 124


🏁 Script executed:

# Search for get_papilo_original_num_variables usage
rg -n "get_papilo_original_num_variables" --type=cpp -C2

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Search for original_problem_ptr->get_n_variables() usage
rg -n "original_problem_ptr.*get_n_variables" --type=cpp -C2

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Find solver files in the repository
find . -type f -name "solver.cu" -o -name "solver.cpp" | head -20

Repository: NVIDIA/cuopt

Length of output: 107


🏁 Script executed:

# Search for SET_SOLUTION callback handling across the codebase
rg -n "SET_SOLUTION|GET_SOLUTION" --type=cpp --type=cu -B2 -A2

Repository: NVIDIA/cuopt

Length of output: 82


🏁 Script executed:

# Search for Papilo presolve variable handling
rg -n "papilo|Papilo|PAPILO" --type=cpp --type=cu | grep -i "variable\|num\|count"

Repository: NVIDIA/cuopt

Length of output: 82


🏁 Script executed:

# Look at the actual problem_ptr and original_problem_ptr structure
rg -n "original_problem_ptr" --type=cpp --type=cu -B1 -A1 | head -50

Repository: NVIDIA/cuopt

Length of output: 82


🏁 Script executed:

# Search for callback type checking and setup across all files
rg -n "callback.*type.*SET_SOLUTION\|callback.*type.*GET_SOLUTION" -A5

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Look at mip/solver.cu for callback handling
cat -n cpp/src/mip/solver.cu | head -150

Repository: NVIDIA/cuopt

Length of output: 7141


🏁 Script executed:

# Search for Papilo presolve in population.cu context
rg -n "papilo\|Papilo\|presolve" cpp/src/mip/diversity/population.cu -i -C3

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Find where original_problem_ptr is set
rg -n "original_problem_ptr\s*=" cpp/src/mip/diversity/population.cu -C2

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Find the context where SET_SOLUTION callback is invoked in population.cu
rg -n "SET_SOLUTION" cpp/src/mip/diversity/population.cu -B10 -A20

Repository: NVIDIA/cuopt

Length of output: 2022


🏁 Script executed:

# Check if problem_ptr in population.cu context has Papilo handling methods
rg -n "has_papilo_presolve_data|get_papilo_original_num_variables" cpp/src/mip/diversity/population.cu

Repository: NVIDIA/cuopt

Length of output: 108


🏁 Script executed:

# Search for problem_ptr type definition in population.cu header
cat -n cpp/src/mip/diversity/population.cuh | head -100

Repository: NVIDIA/cuopt

Length of output: 4020


🏁 Script executed:

# Verify if diversity_manager or population works on original vs preprocessed problem
rg -n "class population_t|struct population_t" cpp/src/mip/diversity/population.cuh -A20

Repository: NVIDIA/cuopt

Length of output: 926


🏁 Script executed:

# Check how callback_num_variables is used in SET_SOLUTION callback
cat -n cpp/src/mip/diversity/population.cu | sed -n '320,350p'

Repository: NVIDIA/cuopt

Length of output: 2275


🏁 Script executed:

# Verify if the method exists and is consistent across problem objects
rg -n "get_papilo_original_num_variables" -A3 -B3

Repository: NVIDIA/cuopt

Length of output: 2186


🏁 Script executed:

# Check the structure/type of problem_ptr to confirm it has the Papilo methods
rg -n "problem_t.*has_papilo_presolve_data|problem_t.*get_papilo_original_num_variables" -A2

Repository: NVIDIA/cuopt

Length of output: 38


Apply Papilo presolve adjustment to SET_SOLUTION callback variable count.

The SET_SOLUTION callback setup at line 324 uses problem_ptr->original_problem_ptr->get_n_variables() but does not check for Papilo presolve data. This is inconsistent with the GET_SOLUTION callback setup in mip/solver.cu (lines 89-91), which applies the Papilo adjustment when has_papilo_presolve_data() is true:

auto callback_num_variables = problem_ptr->original_problem_ptr->get_n_variables();
if (problem_ptr->has_papilo_presolve_data()) {
  callback_num_variables = problem_ptr->get_papilo_original_num_variables();
}

When Papilo presolve reduces the problem, the callback should use the original (pre-Papilo) variable count for the incumbent assignment buffer to match what the callback expects.

🤖 Prompt for AI Agents
In `@cpp/src/mip/diversity/population.cu` around lines 322 - 327, The SET_SOLUTION
callback allocates incumbent_assignment using callback_num_variables from
problem_ptr->original_problem_ptr->get_n_variables() without accounting for
Papilo presolve; update the logic where callback_num_variables is set (near
set_sol_callback and incumbent_assignment) to mirror the GET_SOLUTION handling:
if problem_ptr->has_papilo_presolve_data() then use
problem_ptr->get_papilo_original_num_variables() instead of
original_problem_ptr->get_n_variables(), so the buffer size matches the
callback's expected pre-Papilo variable count.

Copy link

@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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cpp/src/mip/problem/problem.cu`:
- Around line 205-257: The copy constructor problem_t(const problem_t<i_t,f_t>&,
const raft::handle_t*) dereferences handle_ptr_ (via handle_ptr->get_stream())
in many member initializers and must guard against a null handle_ptr_; update
the constructor to validate handle_ptr_ at the start (or before any use) and
either throw a std::invalid_argument or set handle_ptr to a safe default (e.g.,
use problem_.handle_ptr or obtain a global/default raft::handle_t) so that
subsequent initializers like integer_fixed_variable_map(...,
handle_ptr->get_stream()), presolve_data(..., handle_ptr->get_stream()),
coefficients(..., handle_ptr->get_stream()), variables(...,
handle_ptr->get_stream()), lp_state(..., handle_ptr) and fixing_helpers(...,
handle_ptr) do not dereference a nullptr; locate and modify the constructor body
for problem_t and the handle_ptr/handle_ptr_ symbols to perform this null check
and handling before any get_stream() calls.

In `@cpp/src/mip/relaxed_lp/lp_state.cuh`:
- Around line 41-45: The copy constructor lp_state_t(const lp_state_t<i_t, f_t>&
other, const raft::handle_t* handle_ptr) dereferences handle_ptr in the
initializer list (prev_primal(..., handle_ptr->get_stream()), prev_dual(...,
handle_ptr->get_stream())) and can crash if passed null; change the signature to
take a const raft::handle_t& (e.g., const raft::handle_t& handle) and update the
initializer list to use handle.get_stream() for prev_primal and prev_dual to
make the non-null requirement explicit and avoid null dereference.
🧹 Nitpick comments (1)
cpp/src/mip/problem/problem.cu (1)

1819-1855: Avoid const_cast in papilo_uncrush_assignment.

If the presolve uncrush does not mutate problem_t, make the callee accept a const problem_t& and remove the const_cast for const‑correctness and safer APIs.

♻️ Proposed change (requires presolve_data signature update)
-  presolve_data.papilo_uncrush_assignment(const_cast<problem_t&>(*this), assignment);
+  presolve_data.papilo_uncrush_assignment(*this, assignment);

Copy link

@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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
cpp/src/linear_programming/cuopt_c.cpp (1)

808-859: ⚠️ Potential issue | 🟠 Major

Use the settings handle in initial‑solution APIs.

After introducing solver_settings_handle_t, cuOptSetInitialPrimalSolution, cuOptSetInitialDualSolution, and cuOptAddMIPStart still cast settings directly to solver_settings_t. That will dereference the wrong type and can crash.

🔧 Proposed fix
-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
🤖 Fix all issues with AI agents
In `@cpp/src/linear_programming/cuopt_c.cpp`:
- Around line 796-806: The function cuOptSetMIPSetSolutionCallback currently has
paths that fall off without returning a value; update it to return CUOPT_SUCCESS
on successful completion. Specifically, in cuOptSetMIPSetSolutionCallback after
pushing the callback_wrapper into settings_handle->callbacks (and after calling
settings_handle->settings->set_mip_callback), add a return CUOPT_SUCCESS; ensure
both early error checks still return CUOPT_INVALID_ARGUMENT and that the
successful path returns CUOPT_SUCCESS so the function always returns a defined
cuopt_int_t value.
- Around line 29-38: Add a destructor to struct problem_and_stream_view_t that
deletes op_problem if it's non-null and sets it to nullptr to avoid leaking the
optimization_problem_t allocated by cuOptCreateProblem/cuOptReadProblem; update
problem_and_stream_view_t's definition (the constructor/fields block) to include
~problem_and_stream_view_t() { if (op_problem) { delete op_problem; op_problem =
nullptr; } } so the wrapper frees the owned
cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>*
properly.

Comment on lines 29 to 38
struct problem_and_stream_view_t {
problem_and_stream_view_t()
: op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view)
{
}
raft::handle_t* get_handle_ptr() { return &handle; }
cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem;
rmm::cuda_stream_view stream_view;
raft::handle_t handle;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Free op_problem in problem_and_stream_view_t to avoid leaks.

cuOptCreateProblem/cuOptReadProblem allocate op_problem, but cuOptDestroyProblem only deletes the wrapper. Add a destructor to delete op_problem.

🔧 Proposed fix
 struct problem_and_stream_view_t {
   problem_and_stream_view_t()
     : op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view)
   {
   }
+  ~problem_and_stream_view_t() { delete op_problem; }
   raft::handle_t* get_handle_ptr() { return &handle; }
   cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem;
   rmm::cuda_stream_view stream_view;
   raft::handle_t handle;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct problem_and_stream_view_t {
problem_and_stream_view_t()
: op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view)
{
}
raft::handle_t* get_handle_ptr() { return &handle; }
cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem;
rmm::cuda_stream_view stream_view;
raft::handle_t handle;
};
struct problem_and_stream_view_t {
problem_and_stream_view_t()
: op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view)
{
}
~problem_and_stream_view_t() { delete op_problem; }
raft::handle_t* get_handle_ptr() { return &handle; }
cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem;
rmm::cuda_stream_view stream_view;
raft::handle_t handle;
};
🤖 Prompt for AI Agents
In `@cpp/src/linear_programming/cuopt_c.cpp` around lines 29 - 38, Add a
destructor to struct problem_and_stream_view_t that deletes op_problem if it's
non-null and sets it to nullptr to avoid leaking the optimization_problem_t
allocated by cuOptCreateProblem/cuOptReadProblem; update
problem_and_stream_view_t's definition (the constructor/fields block) to include
~problem_and_stream_view_t() { if (op_problem) { delete op_problem; op_problem =
nullptr; } } so the wrapper frees the owned
cuopt::linear_programming::optimization_problem_t<cuopt_int_t, cuopt_float_t>*
properly.

Copy link

@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.

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
cpp/src/linear_programming/cuopt_c.cpp (1)

784-835: ⚠️ Potential issue | 🔴 Critical

Critical: settings handle is still cast as solver_settings_t in initial-solution APIs.

cuOptSolverSettings is now a handle wrapper, so the static_cast here is UB and can crash. Use get_settings_handle(settings)->settings like the other updated APIs.

🛠️ Proposed fix
 cuopt_int_t cuOptSetInitialPrimalSolution(cuOptSolverSettings settings,
                                           const cuopt_float_t* primal_solution,
                                           cuopt_int_t num_variables)
 {
   if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (primal_solution == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (num_variables <= 0) { return CUOPT_INVALID_ARGUMENT; }

-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
   try {
     solver_settings->set_initial_pdlp_primal_solution(primal_solution, num_variables);
   } catch (const std::exception& e) {
     return CUOPT_INVALID_ARGUMENT;
   }
   return CUOPT_SUCCESS;
 }
@@
 cuopt_int_t cuOptSetInitialDualSolution(cuOptSolverSettings settings,
                                         const cuopt_float_t* dual_solution,
                                         cuopt_int_t num_constraints)
 {
   if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (dual_solution == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (num_constraints <= 0) { return CUOPT_INVALID_ARGUMENT; }

-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
   try {
     solver_settings->set_initial_pdlp_dual_solution(dual_solution, num_constraints);
   } catch (const std::exception& e) {
     return CUOPT_INVALID_ARGUMENT;
   }
   return CUOPT_SUCCESS;
 }
@@
 cuopt_int_t cuOptAddMIPStart(cuOptSolverSettings settings,
                              const cuopt_float_t* solution,
                              cuopt_int_t num_variables)
 {
   if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (num_variables <= 0) { return CUOPT_INVALID_ARGUMENT; }

-  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
-    static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
+  solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
+    get_settings_handle(settings)->settings;
   try {
     solver_settings->get_mip_settings().add_initial_solution(solution, num_variables);
   } catch (const std::exception& e) {
     return CUOPT_INVALID_ARGUMENT;
   }
   return CUOPT_SUCCESS;
 }

Copy link

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cpp/src/linear_programming/cuopt_c.cpp`:
- Around line 69-75: The callbacks vector in solver_settings_handle_t
accumulates stale wrapper objects because each cuOptSetMIPGetSolutionCallback /
cuOptSetMIPSetSolutionCallback pushes a new wrapper while set_mip_callback only
uses the latest one; modify those setter functions to replace or clear the
existing wrapper instead of always pushing: locate
solver_settings_handle_t::callbacks and the functions
cuOptSetMIPGetSolutionCallback / cuOptSetMIPSetSolutionCallback and either clear
or overwrite the previous unique_ptr (e.g., replace the element at a dedicated
slot or erase the old wrapper before emplacing the new one) so repeated
registrations do not leak memory or grow the vector unnecessarily, and ensure
set_mip_callback continues to reference the updated wrapper.

Comment on lines +69 to +75
// Owns solver settings and C callback wrappers for C API lifetime.
struct solver_settings_handle_t {
solver_settings_handle_t() : settings(new solver_settings_t<cuopt_int_t, cuopt_float_t>()) {}
~solver_settings_handle_t() { delete settings; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* settings;
std::vector<std::unique_ptr<cuopt::internals::base_solution_callback_t>> callbacks;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid accumulating stale callback wrappers on re-registration.

Each call to cuOptSetMIPGetSolutionCallback / cuOptSetMIPSetSolutionCallback pushes a new wrapper into callbacks, but only the latest pointer is used by set_mip_callback. Repeated registrations will grow memory for the lifetime of the settings handle.

Consider clearing/replacing the existing wrapper before pushing the new one (or use dedicated slots if you intend to support both callbacks simultaneously).

🔧 Minimal fix (replace the old wrapper)
 cuopt_int_t cuOptSetMIPGetSolutionCallback(cuOptSolverSettings settings,
                                            cuOptMIPGetSolutionCallback callback,
                                            void* user_data)
 {
   if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (callback == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   solver_settings_handle_t* settings_handle = get_settings_handle(settings);
   auto callback_wrapper                     = std::make_unique<c_get_solution_callback_t>(callback);
   settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data);
+  settings_handle->callbacks.clear();
   settings_handle->callbacks.push_back(std::move(callback_wrapper));
   return CUOPT_SUCCESS;
 }

 cuopt_int_t cuOptSetMIPSetSolutionCallback(cuOptSolverSettings settings,
                                            cuOptMIPSetSolutionCallback callback,
                                            void* user_data)
 {
   if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   if (callback == nullptr) { return CUOPT_INVALID_ARGUMENT; }
   solver_settings_handle_t* settings_handle = get_settings_handle(settings);
   auto callback_wrapper                     = std::make_unique<c_set_solution_callback_t>(callback);
   settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data);
+  settings_handle->callbacks.clear();
   settings_handle->callbacks.push_back(std::move(callback_wrapper));
   return CUOPT_SUCCESS;
 }

Also applies to: 758-782

🤖 Prompt for AI Agents
In `@cpp/src/linear_programming/cuopt_c.cpp` around lines 69 - 75, The callbacks
vector in solver_settings_handle_t accumulates stale wrapper objects because
each cuOptSetMIPGetSolutionCallback / cuOptSetMIPSetSolutionCallback pushes a
new wrapper while set_mip_callback only uses the latest one; modify those setter
functions to replace or clear the existing wrapper instead of always pushing:
locate solver_settings_handle_t::callbacks and the functions
cuOptSetMIPGetSolutionCallback / cuOptSetMIPSetSolutionCallback and either clear
or overwrite the previous unique_ptr (e.g., replace the element at a dedicated
slot or erase the old wrapper before emplacing the new one) so repeated
registrations do not leak memory or grow the vector unnecessarily, and ensure
set_mip_callback continues to reference the updated wrapper.

Copy link

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cpp/src/mip/solve.cu`:
- Around line 93-115: The host buffer user_assignment_vec is allocated using
solution.assignment.size() before papilo_uncrush_assignment may expand
temp_sol.assignment, causing a buffer overflow in raft::copy; fix by sizing
user_assignment_vec (and any related host buffers) after any call to
problem.papilo_uncrush_assignment so its capacity matches
temp_sol.assignment.size() (or query the callback/ problem for the expected
output size) — update the block around temp_sol, papilo_uncrush_assignment,
user_assignment_vec, raft::copy and get_solution_callback->get_solution to
allocate/resize user_assignment_vec =
std::vector<f_t>(temp_sol.assignment.size()) (and adjust stream sync order)
before performing raft::copy and invoking get_solution.
🧹 Nitpick comments (1)
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)

98-98: Breaking behavior change: mip_scaling default changed to false.

This changes the default solver behavior for all users. Since this is a public setting, consider adding a brief inline comment explaining why scaling is now disabled by default (e.g., numerical stability concerns), which would help users understand the change when reading the header.

Copy link

@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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cpp/src/mip/solve.cu`:
- Around line 66-74: The loop that calls callback->template setup<f_t>(...) on
each element from settings.get_mip_callbacks() must guard against null pointers;
modify the loop in the block that computes callback_num_variables to check each
callback for nullptr (e.g., if (callback != nullptr) { callback->template
setup<f_t>(callback_num_variables); }) to match the null-check pattern used
elsewhere when invoking callback methods.
- Around line 93-115: The loop over settings.get_mip_callbacks() dereferences
callback without null-check (callback->get_type()); before calling
callback->get_type() in the for loop, guard against nullptr (e.g., if (callback
== nullptr) continue) so you skip null entries safely; apply this check at the
start of the loop that handles
internals::base_solution_callback_type::GET_SOLUTION and reference the same
callback pointer used for static_cast to internals::get_solution_callback_t* and
subsequent get_solution calls.

Comment on lines +66 to +74
if (settings.get_mip_callbacks().size() > 0) {
auto callback_num_variables = problem.original_problem_ptr->get_n_variables();
if (problem.has_papilo_presolve_data()) {
callback_num_variables = problem.get_papilo_original_num_variables();
}
for (auto callback : settings.get_mip_callbacks()) {
callback->template setup<f_t>(callback_num_variables);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing null check for callback before method invocation.

Line 71-73 iterates callbacks and calls callback->template setup<f_t>(...) without a null check, whereas lines 223-228 explicitly check callback != nullptr before accessing callback methods. This inconsistency could lead to a null pointer dereference if a null callback exists in the collection.

🛡️ Proposed fix: add null check for consistency
   for (auto callback : settings.get_mip_callbacks()) {
+    if (callback == nullptr) { continue; }
     callback->template setup<f_t>(callback_num_variables);
   }
🤖 Prompt for AI Agents
In `@cpp/src/mip/solve.cu` around lines 66 - 74, The loop that calls
callback->template setup<f_t>(...) on each element from
settings.get_mip_callbacks() must guard against null pointers; modify the loop
in the block that computes callback_num_variables to check each callback for
nullptr (e.g., if (callback != nullptr) { callback->template
setup<f_t>(callback_num_variables); }) to match the null-check pattern used
elsewhere when invoking callback methods.

Comment on lines +93 to +115
for (auto callback : settings.get_mip_callbacks()) {
if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
auto temp_sol(solution);
auto get_sol_callback = static_cast<internals::get_solution_callback_t*>(callback);
std::vector<f_t> user_objective_vec(1);
std::vector<f_t> user_bound_vec(1);
user_objective_vec[0] = solution.get_user_objective();
user_bound_vec[0] = stats.get_solution_bound();
if (problem.has_papilo_presolve_data()) {
problem.papilo_uncrush_assignment(temp_sol.assignment);
}
std::vector<f_t> user_assignment_vec(temp_sol.assignment.size());
raft::copy(user_assignment_vec.data(),
temp_sol.assignment.data(),
temp_sol.assignment.size(),
temp_sol.handle_ptr->get_stream());
solution.handle_ptr->sync_stream();
get_sol_callback->get_solution(user_assignment_vec.data(),
user_objective_vec.data(),
user_bound_vec.data(),
get_sol_callback->get_user_data());
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Null check missing; buffer overflow fix looks good.

Same null check inconsistency as above—line 94 calls callback->get_type() without verifying callback != nullptr.

The previous buffer overflow concern has been addressed: user_assignment_vec is now correctly allocated at line 104 after papilo_uncrush_assignment potentially resizes temp_sol.assignment.

🛡️ Proposed fix
   for (auto callback : settings.get_mip_callbacks()) {
+    if (callback == nullptr) { continue; }
     if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
🤖 Prompt for AI Agents
In `@cpp/src/mip/solve.cu` around lines 93 - 115, The loop over
settings.get_mip_callbacks() dereferences callback without null-check
(callback->get_type()); before calling callback->get_type() in the for loop,
guard against nullptr (e.g., if (callback == nullptr) continue) so you skip null
entries safely; apply this check at the start of the loop that handles
internals::base_solution_callback_type::GET_SOLUTION and reference the same
callback pointer used for static_cast to internals::get_solution_callback_t* and
subsequent get_solution calls.

Copy link

@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.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@cpp/src/mip/diversity/population.cu`:
- Around line 308-313: The loop over user_callbacks accesses
callback->get_type() without a null check; update the loop to skip null entries
by checking callback != nullptr before calling get_type(), then proceed to
static_cast to internals::get_solution_callback_t* and call
invoke_get_solution_callback(sol, get_sol_callback) only when callback is
non-null and its type equals
internals::base_solution_callback_type::GET_SOLUTION; this mirrors the
null-check pattern used in solve.cu and prevents dereferencing null pointers.
- Around line 321-327: Add a null check before dereferencing callbacks in the
loop over user_callbacks: ensure callback is not null before calling
callback->get_type() and before the static_cast to
internals::set_solution_callback_t; if null, skip that iteration. Also
defensively handle papilo presolve: when computing callback_num_variables use
problem_ptr->original_problem_ptr->get_n_variables() only if problem_ptr and
original_problem_ptr are non-null and/or has_papilo_presolve_data() is false;
otherwise fall back to a safe variable count or skip SET_SOLUTION handling.
Update the loop that references user_callbacks, callback,
set_solution_callback_t, and the call sites to
context.stats.get_solution_bound() and get_n_variables() accordingly.

In `@python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py`:
- Around line 540-544: The final call to self._get_incumbents(reqId,
incumbent_callback) can race with the incumbents polling thread (inc_t) and
cause concurrent user callbacks; change the flow so the "final incumbent fetch"
happens only after stop_threads() has been called (or skip the fetch if inc_t is
still running) to ensure only one thread invokes incumbent_callback at a time —
locate the block that checks incumbent_callback and reqId and move or gate the
_get_incumbents(reqId, incumbent_callback) call to run after stop_threads()
returns (or add a check of inc_t/is_running) so callbacks are serialized.
🧹 Nitpick comments (1)
cpp/src/mip/diversity/population.cu (1)

365-373: Consider relative tolerance for objective mismatch check.

The absolute tolerance of 1e-6 at line 372 may be too strict for problems with large objective values. Consider using a relative tolerance or a combined check.

🔧 Proposed fix with relative tolerance
-      if (std::abs(outside_sol.get_user_objective() - outside_sol_objective) > 1e-6) {
+      f_t obj_diff = std::abs(outside_sol.get_user_objective() - outside_sol_objective);
+      f_t obj_tol = std::max(1e-6, 1e-9 * std::abs(outside_sol_objective));
+      if (obj_diff > obj_tol) {
         cuopt_func_call(
           CUOPT_LOG_DEBUG("External solution objective mismatch: outside_sol.get_user_objective() "
                           "= %g, outside_sol_objective = %g",
                           outside_sol.get_user_objective(),
                           outside_sol_objective));
       }
-      cuopt_assert(std::abs(outside_sol.get_user_objective() - outside_sol_objective) <= 1e-6,
+      cuopt_assert(obj_diff <= obj_tol,
                    "External solution objective mismatch");

Comment on lines 308 to 313
for (auto callback : user_callbacks) {
if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
auto get_sol_callback = static_cast<internals::get_solution_callback_t*>(callback);
solution_t<i_t, f_t> temp_sol(sol);
problem_ptr->post_process_assignment(temp_sol.assignment);
rmm::device_uvector<f_t> dummy(0, temp_sol.handle_ptr->get_stream());
if (context.settings.mip_scaling) {
context.scaling.unscale_solutions(temp_sol.assignment, dummy);
// Need to get unscaled problem as well
problem_t<i_t, f_t> n_problem(*sol.problem_ptr->original_problem_ptr);
auto scaled_sol(temp_sol);
scaled_sol.problem_ptr = &n_problem;
scaled_sol.resize_to_original_problem();
scaled_sol.compute_feasibility();
if (!scaled_sol.get_feasible()) {
CUOPT_LOG_DEBUG("Discard infeasible after unscaling");
return;
}
}

rmm::device_uvector<f_t> user_objective_vec(1, temp_sol.handle_ptr->get_stream());

f_t user_objective =
temp_sol.problem_ptr->get_user_obj_from_solver_obj(temp_sol.get_objective());
user_objective_vec.set_element_async(0, user_objective, temp_sol.handle_ptr->get_stream());
CUOPT_LOG_DEBUG("Returning incumbent solution with objective %g", user_objective);
get_sol_callback->get_solution(temp_sol.assignment.data(), user_objective_vec.data());
invoke_get_solution_callback(sol, get_sol_callback);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing null check before callback method access.

Line 309 calls callback->get_type() without verifying callback != nullptr. For consistency with the null-check pattern in solve.cu (lines 225-228), add a guard.

🛡️ Proposed fix
     for (auto callback : user_callbacks) {
+      if (callback == nullptr) { continue; }
       if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (auto callback : user_callbacks) {
if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
auto get_sol_callback = static_cast<internals::get_solution_callback_t*>(callback);
solution_t<i_t, f_t> temp_sol(sol);
problem_ptr->post_process_assignment(temp_sol.assignment);
rmm::device_uvector<f_t> dummy(0, temp_sol.handle_ptr->get_stream());
if (context.settings.mip_scaling) {
context.scaling.unscale_solutions(temp_sol.assignment, dummy);
// Need to get unscaled problem as well
problem_t<i_t, f_t> n_problem(*sol.problem_ptr->original_problem_ptr);
auto scaled_sol(temp_sol);
scaled_sol.problem_ptr = &n_problem;
scaled_sol.resize_to_original_problem();
scaled_sol.compute_feasibility();
if (!scaled_sol.get_feasible()) {
CUOPT_LOG_DEBUG("Discard infeasible after unscaling");
return;
}
}
rmm::device_uvector<f_t> user_objective_vec(1, temp_sol.handle_ptr->get_stream());
f_t user_objective =
temp_sol.problem_ptr->get_user_obj_from_solver_obj(temp_sol.get_objective());
user_objective_vec.set_element_async(0, user_objective, temp_sol.handle_ptr->get_stream());
CUOPT_LOG_DEBUG("Returning incumbent solution with objective %g", user_objective);
get_sol_callback->get_solution(temp_sol.assignment.data(), user_objective_vec.data());
invoke_get_solution_callback(sol, get_sol_callback);
}
}
for (auto callback : user_callbacks) {
if (callback == nullptr) { continue; }
if (callback->get_type() == internals::base_solution_callback_type::GET_SOLUTION) {
auto get_sol_callback = static_cast<internals::get_solution_callback_t*>(callback);
invoke_get_solution_callback(sol, get_sol_callback);
}
}
🤖 Prompt for AI Agents
In `@cpp/src/mip/diversity/population.cu` around lines 308 - 313, The loop over
user_callbacks accesses callback->get_type() without a null check; update the
loop to skip null entries by checking callback != nullptr before calling
get_type(), then proceed to static_cast to internals::get_solution_callback_t*
and call invoke_get_solution_callback(sol, get_sol_callback) only when callback
is non-null and its type equals
internals::base_solution_callback_type::GET_SOLUTION; this mirrors the
null-check pattern used in solve.cu and prevents dereferencing null pointers.

Copy link

@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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cpp/src/mip/problem/presolve_data.cu`:
- Line 170: The stream synchronization call problem.handle_ptr->sync_stream()
lacks post-synchronization CUDA error checking; after that call, add
verification using the project's CUDA_CHECK macro or by calling
cudaGetLastError()/cudaStreamSynchronize and handling non-success results so
asynchronous kernel/memory errors are caught; update the block containing
problem.handle_ptr->sync_stream() to invoke CUDA_CHECK or equivalent and
log/propagate any error.
- Line 245: Add CUDA error checking after the stream synchronization call: after
the call to problem.handle_ptr->sync_stream() (in presolve_data.cu) wrap or
follow it with the project’s CUDA error verification macro (e.g., CUDA_CHECK or
equivalent) so any failure from stream synchronization is caught; locate the
call to problem.handle_ptr->sync_stream() and insert a call to the existing CUDA
error-checking helper immediately after it (or ensure sync_stream() returns a
status that you verify), mirroring the pattern used around the earlier checks at
the other sites.

post_process_assignment(problem, solution.assignment);
// this is for resizing other fields such as excess, slack so that we can compute the feasibility
solution.resize_to_original_problem();
problem.handle_ptr->sync_stream();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add CUDA error checking after stream synchronization.

Similar to the issue flagged at line 81, this sync_stream() call should be followed by error verification to catch any asynchronous CUDA errors before proceeding.

🛡️ Proposed fix
   problem.handle_ptr->sync_stream();
+  RAFT_CHECK_CUDA(problem.handle_ptr->get_stream());
   solution.post_process_completed = true;

As per coding guidelines: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
problem.handle_ptr->sync_stream();
problem.handle_ptr->sync_stream();
RAFT_CHECK_CUDA(problem.handle_ptr->get_stream());
solution.post_process_completed = true;
🤖 Prompt for AI Agents
In `@cpp/src/mip/problem/presolve_data.cu` at line 170, The stream synchronization
call problem.handle_ptr->sync_stream() lacks post-synchronization CUDA error
checking; after that call, add verification using the project's CUDA_CHECK macro
or by calling cudaGetLastError()/cudaStreamSynchronize and handling non-success
results so asynchronous kernel/memory errors are caught; update the block
containing problem.handle_ptr->sync_stream() to invoke CUDA_CHECK or equivalent
and log/propagate any error.

full_assignment.data(),
full_assignment.size(),
problem.handle_ptr->get_stream());
problem.handle_ptr->sync_stream();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add CUDA error checking after stream synchronization.

Consistent with the pattern at lines 81 and 170, add error verification after sync_stream().

🛡️ Proposed fix
   problem.handle_ptr->sync_stream();
+  RAFT_CHECK_CUDA(problem.handle_ptr->get_stream());
 }

As per coding guidelines: Every CUDA kernel launch and memory operation must have error checking with CUDA_CHECK or equivalent verification.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
problem.handle_ptr->sync_stream();
problem.handle_ptr->sync_stream();
RAFT_CHECK_CUDA(problem.handle_ptr->get_stream());
}
🤖 Prompt for AI Agents
In `@cpp/src/mip/problem/presolve_data.cu` at line 245, Add CUDA error checking
after the stream synchronization call: after the call to
problem.handle_ptr->sync_stream() (in presolve_data.cu) wrap or follow it with
the project’s CUDA error verification macro (e.g., CUDA_CHECK or equivalent) so
any failure from stream synchronization is caught; locate the call to
problem.handle_ptr->sync_stream() and insert a call to the existing CUDA
error-checking helper immediately after it (or ensure sync_stream() returns a
status that you verify), mirroring the pattern used around the earlier checks at
the other sites.

@akifcorduk
Copy link
Contributor Author

/merge

@rapids-bot rapids-bot bot merged commit 7197a0c into NVIDIA:release/26.02 Feb 4, 2026
195 of 197 checks passed
This was referenced Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Introduces a breaking change improvement Improves an existing functionality mip

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

Comments