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

    • Per-run threading control and explicit bidirectional conversion between user-space and solver-space objectives.
  • Optimizations

    • Presolve and preprocessing use configurable thread counts to align concurrency with instance settings.
  • Bug Fixes

    • Prevents unintended root-solution overrides and stabilizes root handling across concurrent solves.
  • Behavior Changes

    • Root-solution callbacks now receive solver timing plus both solver-space and user-space objective values.
  • Quality of Life

    • Reduced verbose solver logging during mixed-mode MIP runs.

✏️ 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

Treats PDLP objective as a user-space value and adds a user→solver objective conversion API; passes solver- and user-space objectives plus solve time to B&B callbacks; makes presolve threading configurable with OpenMP; records which solver solved the root; and suppresses LP solver logs during MIP solves via a conditional logging macro.

Changes

Cohort / File(s) Summary
Objective conversion & callback API
cpp/src/mip/problem/problem.cuh, cpp/src/mip/problem/problem.cu
Adds f_t get_solver_obj_from_user_obj(f_t user_obj) const to problem_t and updates set_root_relaxation_solution_callback type to accept an additional f_t so callbacks receive solver-space objective and/or solve_time alongside the user objective.
Diversity manager objective handling
cpp/src/mip/diversity/diversity_manager.cu
Treats PDLP objective as a user-space value, derives solver_obj via the new API, retrieves solve_time, and updates the B&B callback invocation to pass the solver-space objective, user-space objective, and solve_time with adjusted argument ordering and clarifying comments.
Branch-and-bound root tracking
cpp/src/dual_simplex/branch_and_bound.hpp, cpp/src/dual_simplex/branch_and_bound.cpp, cpp/src/mip/solver.cu
Adds enum class root_solver_type_t { NONE, CROSSOVER, DUAL_SIMPLEX } and std::atomic<root_solver_type_>; changes set_root_relaxation_solution to accept solve_time, record root solver type, and populate root data only once; updates B&B callback binding to include the extra placeholder.
Presolve threading / probing cache
cpp/src/mip/presolve/bounds_presolve.cuh, cpp/src/mip/presolve/probing_cache.cu
Adds i_t num_threads = -1 setting and #include <omp.h>; computes/clamps num_threads, sizes per-thread pools by num_threads, and uses #pragma omp parallel num_threads(num_threads) to bind concurrency to the configured value.
LP solver logging
cpp/src/linear_programming/solve.cu
Introduces CUOPT_LOG_CONDITIONAL_INFO(condition, ...) macro and replaces several CUOPT_LOG_INFO calls with it to suppress verbose solver logs inside MIP solves; updates copyright year.
Build manifest
CMakeLists.txt
Small CMake adjustments (few lines changed).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 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 'Several bug fixes' is vague and generic. While the PR does address multiple bug fixes, the title uses non-descriptive language that fails to convey the specific nature or scope of the changes to someone scanning the commit history. Consider using a more specific title that highlights the primary bug fix, such as 'Fix PDLP objective space mapping in B&B root relaxation' or naming the most critical issue 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
root_crossover_soln_.user_objective = user_objective;
root_crossover_soln_.iterations = iterations;
root_crossover_solution_set_.store(true, std::memory_order_release);
if (root_solver_type_ == root_solver_type_t::NONE) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this printing to inside solve_root_relaxation and only print when crossover is successful.

Otherwise we could tell the user we have found a solution with PDLP/Barrier. And then the user could wait a long time only to have us use the dual simplex solution because we either: a) failed in crossover, or b) crossover took a very long time.

class branch_and_bound_t {
public:
// Specify which solver was used for solving the root LP relaxation
enum class root_solver_type_t { NONE = 0, CROSSOVER = 1, DUAL_SIMPLEX = 2 };
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this? Can we just determine who won in solve_root_relaxation?


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

}

if (root_crossover_solution_set_.load(std::memory_order_acquire)) {
settings_.log.printf("\nRunning crossover\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.

I would remove this print. I don't think we want to interrupt the dual simplex logs for the root solve, unless we know that crossover was successful.

std::atomic<bool> root_crossover_solution_set_{false};
bool enable_concurrent_lp_root_solve_{false};
std::atomic<int> root_concurrent_halt_{0};
std::atomic<root_solver_type_t> root_solver_type_{root_solver_type_t::NONE};
Copy link
Contributor

@hlinsen hlinsen Jan 22, 2026

Choose a reason for hiding this comment

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

Does this need to be an atomic? I think we can never have a race condition because we write to this var only when one f the solve is optimal which means the other solver returned with halt status.

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