Add C api callbacks for getting and setting solutions#779
Add C api callbacks for getting and setting solutions#779rapids-bot[bot] merged 43 commits intoNVIDIA:release/26.02from
Conversation
📝 WalkthroughWalkthroughAdd 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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Important Action Needed: IP Allowlist UpdateIf your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:
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. Comment |
There was a problem hiding this comment.
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: Documentuser_dataownership/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
📒 Files selected for processing (16)
cpp/include/cuopt/linear_programming/cuopt_c.hcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/solver_settings.hppcpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/include/cuopt/linear_programming/utilities/internals.hppcpp/src/linear_programming/cuopt_c.cppcpp/src/math_optimization/solver_settings.cucpp/src/mip/diversity/diversity_manager.cucpp/src/mip/diversity/population.cucpp/src/mip/solver_settings.cucpp/tests/linear_programming/c_api_tests/c_api_test.ccpp/tests/mip/incumbent_callback_test.cupython/cuopt/cuopt/linear_programming/internals/internals.pyxpython/cuopt/cuopt/linear_programming/solver/solver.pxdpython/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyxpython/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hpppython/cuopt/cuopt/linear_programming/solver_settings/solver_settings.pycpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/src/mip/diversity/population.cucpp/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.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/src/mip/diversity/population.cucpp/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.hcpp/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.hcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/src/mip/diversity/diversity_manager.cucpp/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.hcpp/src/math_optimization/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/src/mip/diversity/population.cucpp/include/cuopt/linear_programming/utilities/callbacks_implems.hppcpp/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.hcpp/include/cuopt/linear_programming/solver_settings.hppcpp/src/math_optimization/solver_settings.cucpp/src/mip/solver_settings.cucpp/tests/mip/incumbent_callback_test.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/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.hppcpp/src/mip/solver_settings.cucpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/mip/diversity/population.cucpp/src/mip/diversity/diversity_manager.cucpp/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.cucpp/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.cucpp/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.
There was a problem hiding this comment.
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_tis a borrowed reference — noPy_INCREFis performed. The pointer remains valid only whileself(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
📒 Files selected for processing (2)
python/cuopt/cuopt/linear_programming/internals/internals.pyxpython/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 PyObjectis 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_dataparameter, 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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
And this should probably be a host pointer rather than a device pointer
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
I have enabled third party presolve and handled crushing/uncrushing of solutions.
| * @param[in] solution - Device pointer to incumbent solution values. | ||
| * @param[in] objective_value - Device pointer to incumbent objective value. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I agree. The rest of the API uses abstracts out the device. Probably we should provide a host pointer here.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I made all callbacks to input/output host pointers.
…acks are failing due to device host divide, pending decision
nguidotti
left a comment
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
new is used everywhere in C API implementation, i wanted to stay consistent. Maybe we can consider it for anotehr PR.
There was a problem hiding this comment.
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-6tolerance 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_SOLUTIONtype, casting, and invokinginvoke_get_solution_callbackis 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 existingevent_handler_tutility: record an event on the best-feasible stream and wait on it asynchronously fromrins_handle, or defer the synchronization outside the lock scope. Theevent_handler_tclass (incpp/src/utilities/event_handler.cuh) withrecord()andstream_wait()methods is already proven in the LP solver for this pattern.
| 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); |
There was a problem hiding this comment.
🧩 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 -C2Repository: 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 -C2Repository: 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 -20Repository: 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 -A2Repository: 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 -50Repository: 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" -A5Repository: NVIDIA/cuopt
Length of output: 38
🏁 Script executed:
# Look at mip/solver.cu for callback handling
cat -n cpp/src/mip/solver.cu | head -150Repository: 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 -C3Repository: 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 -C2Repository: 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 -A20Repository: 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.cuRepository: 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 -100Repository: 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 -A20Repository: 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 -B3Repository: 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" -A2Repository: 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.
There was a problem hiding this comment.
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: Avoidconst_castinpapilo_uncrush_assignment.If the presolve uncrush does not mutate
problem_t, make the callee accept aconst 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);
There was a problem hiding this comment.
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 | 🟠 MajorUse the settings handle in initial‑solution APIs.
After introducing
solver_settings_handle_t,cuOptSetInitialPrimalSolution,cuOptSetInitialDualSolution, andcuOptAddMIPStartstill castsettingsdirectly tosolver_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.
| 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; | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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 | 🔴 CriticalCritical: settings handle is still cast as solver_settings_t in initial-solution APIs.
cuOptSolverSettingsis now a handle wrapper, so the static_cast here is UB and can crash. Useget_settings_handle(settings)->settingslike 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; }
There was a problem hiding this comment.
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.
| // 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; | ||
| }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_scalingdefault changed tofalse.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.
There was a problem hiding this comment.
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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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()); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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-6at 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");
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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.
| 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.
|
/merge |
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
Behavior
Tests
Documentation