Skip to content

Conversation

@nguidotti
Copy link
Contributor

@nguidotti nguidotti commented Jan 20, 2026

  • Fixed early termination in CMS750_4 due to an incorrect root objective from the PDLP. It provides an objective in user space and B&B expects the objective in the solver space.
  • Fixed setting the number of threads to a fixed value when running cuopt_cli. Now the number of threads in the probing cache can be controlled by the bounds_update::settings_t.
  • Inverted loop order in probing cache to avoid creating and deleting the OpenMP thread pool each iteration.
  • PDLP/Barrier no longer set the root objective and solution when the B&B is already running.
  • Improved the logs when solving the root relaxation using the concurrent mode.

Checklist

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

Summary by CodeRabbit

  • New Features

    • Added per-instance thread count configuration for presolve operations, with automatic scaling to system capabilities when not explicitly set.
  • Bug Fixes

    • Improved root relaxation solution handling for enhanced stability.
    • Refined logging output for clearer MIP solver operation reporting.

✏️ Tip: You can customize this high-level summary in your review settings.

@nguidotti nguidotti added this to the 26.02 milestone Jan 20, 2026
@nguidotti nguidotti self-assigned this Jan 20, 2026
@nguidotti nguidotti requested a review from a team as a code owner January 20, 2026 14:33
@nguidotti nguidotti added bug Something isn't working non-breaking Introduces a non-breaking change labels Jan 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

This pull request introduces bidirectional user-space to solver-space objective value conversion, configures dynamic threading for presolve operations, implements conditional logging to suppress verbose output during MIP solving, and adds idempotency guards to prevent overwriting cached root solutions.

Changes

Cohort / File(s) Summary
Objective value handling
cpp/src/mip/diversity/diversity_manager.cu, cpp/src/mip/problem/problem.cuh, cpp/src/mip/problem/problem.cu, cpp/src/dual_simplex/branch_and_bound.hpp, cpp/src/dual_simplex/branch_and_bound.cpp
Introduces user-space to solver-space objective conversion via new get_solver_obj_from_user_obj() method. Updates B&B callback to pass both solver-space and user-space objectives as separate parameters. Adds is_root_solution_set flag to guard against overwriting root solution data. Updates set_root_relaxation_solution_callback signature to accept additional objective parameter.
Threading configuration
cpp/src/mip/presolve/bounds_presolve.cuh, cpp/src/mip/presolve/probing_cache.cu
Replaces hard-coded thread configuration with configurable num_threads field (defaults to -1, computed as 0.2 × omp_get_max_threads() or clamped to [1, 8]). Adds OpenMP support headers. Updates per-thread pool sizing and parallel region configuration to use computed thread count.
Conditional logging
cpp/src/linear_programming/solve.cu, cpp/src/dual_simplex/phase2.cpp
Adds CUOPT_LOG_CONDITIONAL_INFO macro to suppress verbose logging inside MIP contexts. Removes duplicated root relaxation statistics logging from phase2. Updates copyright year to 2022-2026.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive phrasing that doesn't convey specific information about the actual changes made. Replace with a more specific title highlighting the primary fix, such as 'Fix root objective handling in PDLP to Branch & Bound conversion' or summarize the main bug being addressed.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

…ed concurrent solution when the B&B is already running. moved number of threads in the probing cache to the setting struct.
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/mip/presolve/probing_cache.cu (1)

859-910: Add defensive validation for num_threads before pool sizing and parallel launch

The code assumes settings.num_threads is always positive, but since settings_t is a public struct, external code can set num_threads to 0 (or modify it between initialization and use). If 0 is assigned:

  • modification_vector_pool and substitution_vector_pool would have size 0
  • #pragma omp parallel num_threads(0) produces undefined behavior (OpenMP spec)
  • If threads spawn despite 0, thread_idx from omp_get_thread_num() will exceed pool bounds, causing out-of-bounds access

Add a defensive clamp before sizing the pools:

Proposed fix
-  const size_t num_threads = bound_presolve.settings.num_threads;
+  const i_t requested_threads = bound_presolve.settings.num_threads;
+  size_t num_threads =
+    requested_threads > 0 ? static_cast<size_t>(requested_threads)
+                          : static_cast<size_t>(omp_get_max_threads());
+  if (num_threads == 0) { num_threads = 1; }

Aligns with multithreading safety guidelines to prevent resource exhaustion and thread safety violations.


#include <thread> // For std::thread

#define CUOPT_LP_SOLVER_LOG_INFO(...) \
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we undef the previous macro and still use CUOPT_LOG_INFO 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.

I think it is better to add another macro, since there are some methods where we do not pass the setting as argument, hence they need to use the old version.

Copy link
Contributor Author

@nguidotti nguidotti Jan 22, 2026

Choose a reason for hiding this comment

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

Ideally, we should add logger_t to the settings rather than CUOPT_LOG_INFO or rapids::logger_t.

@rgsl888prabhu rgsl888prabhu added the do not merge Do not merge if this flag is set label Jan 22, 2026
@rg20 rg20 added do not merge Do not merge if this flag is set and removed do not merge Do not merge if this flag is set labels Jan 22, 2026
@rgsl888prabhu rgsl888prabhu changed the base branch from main to release/26.02 January 22, 2026 16:39
@nguidotti nguidotti changed the title Fixed early termination in CMS750_4 + Fixed hard-coded number of threads Several bug fixes Jan 22, 2026

// Check if crossover was stopped by dual simplex
if (crossover_status == crossover_status_t::OPTIMAL) {
settings_.log.printf("\nCrossover found an optimal solution for the root relaxation\n\n");
Copy link
Contributor

Choose a reason for hiding this comment

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

This is where we should indicate that PDLP/Barrier won. Can you use the is_pdlp_solution to determine which of them won and print it here?

Copy link
Contributor Author

@nguidotti nguidotti Jan 22, 2026

Choose a reason for hiding this comment

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

I did not find a way to get the winner solver from the optimization_problem_solution_t unless we change the API (#787). This object is user-facing, so it needs to be reflect on the Python side as well.

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/dual_simplex/branch_and_bound.hpp`:
- Around line 87-97: The plain bool is_root_solution_set is accessed
concurrently (read in set_root_relaxation_solution and written in
solve_root_relaxation), causing a data race; change the declaration of
is_root_solution_set to std::atomic<bool> and update all reads/writes in
functions such as set_root_relaxation_solution and solve_root_relaxation to use
.load()/.store() with appropriate memory orders (e.g., memory_order_acquire for
reads and memory_order_release for writes) to match the existing
root_crossover_solution_set_ usage, ensuring thread-safe checks and updates.

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

Labels

bug Something isn't working do not merge Do not merge if this flag is set non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants