Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,11 @@ template <typename i_t, typename f_t>
lp_status_t branch_and_bound_t<i_t, f_t>::solve_root_relaxation(
simplex_solver_settings_t<i_t, f_t> const& lp_settings)
{
f_t start_time = tic();
f_t user_objective = 0;
i_t iter = 0;
std::string solver_name = "";

// Root node path
lp_status_t root_status;
std::future<lp_status_t> root_status_future;
Expand Down Expand Up @@ -1338,24 +1343,47 @@ lp_status_t branch_and_bound_t<i_t, f_t>::solve_root_relaxation(
root_crossover_soln_,
crossover_vstatus_);

if (crossover_status == crossover_status_t::OPTIMAL) {
settings_.log.printf("Crossover status: %d\n", crossover_status);
}

// Check if crossover was stopped by dual simplex
if (crossover_status == crossover_status_t::OPTIMAL) {
set_root_concurrent_halt(1); // Stop dual simplex
root_status = root_status_future.get();

// Override the root relaxation solution with the crossover solution
root_relax_soln_ = root_crossover_soln_;
root_vstatus_ = crossover_vstatus_;
root_status = lp_status_t::OPTIMAL;
user_objective = root_crossover_soln_.user_objective;
iter = root_crossover_soln_.iterations;
solver_name = "Barrier/PDLP and Crossover";

} else {
root_status = root_status_future.get();
root_status = root_status_future.get();
user_objective = root_relax_soln_.user_objective;
iter = root_relax_soln_.iterations;
solver_name = "Dual Simplex";
}
} else {
root_status = root_status_future.get();
root_status = root_status_future.get();
user_objective = root_relax_soln_.user_objective;
iter = root_relax_soln_.iterations;
solver_name = "Dual Simplex";
}

settings_.log.printf("\n");
if (root_status == lp_status_t::OPTIMAL) {
settings_.log.printf("Root relaxation solution found in %d iterations and %.2fs by %s\n",
iter,
toc(start_time),
solver_name.c_str());
settings_.log.printf("Root relaxation objective %+.8e\n", user_objective);
} else {
settings_.log.printf("Root relaxation returned status: %s\n",
lp_status_to_string(root_status).c_str());
}

settings_.log.printf("\n");
is_root_solution_set = true;

Comment on lines +1371 to +1386
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Search for the declaration of is_root_solution_set in header files
echo "=== Searching for is_root_solution_set declaration ==="
rg -n -C3 'is_root_solution_set' --type=cpp --type=hpp

echo ""
echo "=== Checking for std::atomic usage ==="
rg -n 'std::atomic.*is_root_solution|is_root_solution.*std::atomic' --type=cpp --type=hpp

echo ""
echo "=== Looking for class definition containing this member ==="
rg -n 'class.*BranchAndBound|class.*DualSimplex' --type=hpp -A 50 | head -100

Repository: NVIDIA/cuopt

Length of output: 312


🏁 Script executed:

#!/bin/bash

# First, find header files related to branch_and_bound
echo "=== Finding branch_and_bound related header files ==="
find . -name "*branch_and_bound*" -type f | grep -E '\.(h|hpp)$'

echo ""
echo "=== Searching for is_root_solution_set in all files ==="
grep -r "is_root_solution_set" --include="*.cpp" --include="*.h" --include="*.hpp" --include="*.cuh" --include="*.cu" -n

echo ""
echo "=== Looking at the branch_and_bound.cpp file structure ==="
head -50 cpp/src/dual_simplex/branch_and_bound.cpp | grep -E '#include|class|struct'

echo ""
echo "=== Checking if it's a member variable by looking at class definition ==="
grep -r "class.*BranchAndBound" --include="*.h" --include="*.hpp" -A 100 | head -200

Repository: NVIDIA/cuopt

Length of output: 1255


🏁 Script executed:

#!/bin/bash

# Check the context around line 87 in the header where it's read
echo "=== Context of is_root_solution_set check at line 87 ==="
sed -n '80,95p' cpp/src/dual_simplex/branch_and_bound.hpp

echo ""
echo "=== Full declaration context around line 167 ==="
sed -n '160,175p' cpp/src/dual_simplex/branch_and_bound.hpp

echo ""
echo "=== Check the full function where line 1385 assignment is ==="
sed -n '1350,1390p' cpp/src/dual_simplex/branch_and_bound.cpp

Repository: NVIDIA/cuopt

Length of output: 3037


Replace plain bool is_root_solution_set with std::atomic<bool> to prevent race condition.

The flag guards against concurrent PDLP/Barrier solvers overwriting the root solution when B&B is already running, as confirmed by the PR context. Currently, is_root_solution_set is declared as a plain bool at line 167 of the header without synchronization, while being read by set_root_relaxation_solution() (external solvers) and written by the B&B solve function at line 1385. This creates a data race between concurrent threads.

Change the declaration to std::atomic<bool> is_root_solution_set{false}; and use appropriate memory ordering (e.g., std::memory_order_acquire for read, std::memory_order_release for write), consistent with the nearby root_crossover_solution_set_ pattern already used in the class.

🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/branch_and_bound.cpp` around lines 1371 - 1386, Replace
the plain bool is_root_solution_set with an atomic to prevent a data race:
change the declaration to std::atomic<bool> is_root_solution_set{false};
(mirroring root_crossover_solution_set_). In the writer in
branch_and_bound::solve() (where is_root_solution_set is set) store using
std::memory_order_release, and in readers such as set_root_relaxation_solution()
load using std::memory_order_acquire. Update any direct reads/writes of
is_root_solution_set to use atomic load/store with those memory orders to ensure
safe synchronization.

return root_status;
}

Expand Down Expand Up @@ -1414,14 +1442,13 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut

root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols);

settings_.log.printf("Solving LP root relaxation\n");

lp_status_t root_status;
simplex_solver_settings_t lp_settings = settings_;
lp_settings.inside_mip = 1;
lp_settings.concurrent_halt = get_root_concurrent_halt();
// RINS/SUBMIP path
if (!enable_concurrent_lp_root_solve()) {
settings_.log.printf("\nSolving LP root relaxation with dual simplex\n");
root_status = solve_linear_program_advanced(original_lp_,
exploration_stats_.start_time,
lp_settings,
Expand All @@ -1430,6 +1457,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
edge_norms_);

} else {
settings_.log.printf("\nSolving LP root relaxation in concurrent mode\n");
root_status = solve_root_relaxation(lp_settings);
}

Expand Down Expand Up @@ -1540,7 +1568,7 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
original_lp_,
log);

settings_.log.printf("Exploring the B&B tree using %d threads (best-first = %d, diving = %d)\n",
settings_.log.printf("Exploring the B&B tree using %d threads (best-first = %d, diving = %d)\n\n",
settings_.num_threads,
settings_.num_bfs_workers,
settings_.num_threads - settings_.num_bfs_workers);
Expand Down
19 changes: 11 additions & 8 deletions cpp/src/dual_simplex/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,16 @@ class branch_and_bound_t {
f_t user_objective,
i_t iterations)
{
root_crossover_soln_.x = primal;
root_crossover_soln_.y = dual;
root_crossover_soln_.z = reduced_costs;
root_objective_ = objective;
root_crossover_soln_.objective = objective;
root_crossover_soln_.user_objective = user_objective;
root_crossover_soln_.iterations = iterations;
root_crossover_solution_set_.store(true, std::memory_order_release);
if (!is_root_solution_set) {
root_crossover_soln_.x = primal;
root_crossover_soln_.y = dual;
root_crossover_soln_.z = reduced_costs;
root_objective_ = objective;
root_crossover_soln_.objective = objective;
root_crossover_soln_.user_objective = user_objective;
root_crossover_soln_.iterations = iterations;
root_crossover_solution_set_.store(true, std::memory_order_release);
}
Comment on lines +87 to +96
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -path "*/dual_simplex/branch_and_bound.hpp" -type f

Repository: NVIDIA/cuopt

Length of output: 99


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.hpp | sed -n '80,105p'

Repository: NVIDIA/cuopt

Length of output: 1469


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.hpp | sed -n '160,175p'

Repository: NVIDIA/cuopt

Length of output: 659


🏁 Script executed:

rg -n "is_root_solution_set" cpp/src/dual_simplex/

Repository: NVIDIA/cuopt

Length of output: 293


🏁 Script executed:

rg -n "set_root_relaxation_solution" cpp/src/dual_simplex/ -A 20

Repository: NVIDIA/cuopt

Length of output: 2015


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.cpp | sed -n '1375,1395p'

Repository: NVIDIA/cuopt

Length of output: 892


🏁 Script executed:

rg -n "is_root_solution_set\s*=" cpp/src/dual_simplex/ -B 5 -A 2

Repository: NVIDIA/cuopt

Length of output: 654


🏁 Script executed:

rg -n "set_root_relaxation_solution" cpp/src/dual_simplex/branch_and_bound.cpp -B 5 -A 15

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

rg -n "set_root_relaxation_solution\(" cpp/src/dual_simplex/ -B 3 -A 3

Repository: NVIDIA/cuopt

Length of output: 758


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.cpp | sed -n '1320,1390p'

Repository: NVIDIA/cuopt

Length of output: 3543


🏁 Script executed:

rg -n "enable_concurrent_lp_root_solve" cpp/src/dual_simplex/ -B 2 -A 2

Repository: NVIDIA/cuopt

Length of output: 1974


🏁 Script executed:

rg -n "PDLP\|pdlp" cpp/src/dual_simplex/branch_and_bound.cpp -B 2 -A 2 | head -40

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.cpp | sed -n '1250,1290p'

Repository: NVIDIA/cuopt

Length of output: 1804


🏁 Script executed:

rg -n "set_root_relaxation_solution" cpp/src/ -B 2 -A 2

Repository: NVIDIA/cuopt

Length of output: 3247


🏁 Script executed:

cat -n cpp/src/dual_simplex/branch_and_bound.cpp | sed -n '1430,1470p'

Repository: NVIDIA/cuopt

Length of output: 2151


Fix data race on is_root_solution_set guard in concurrent root relaxation.

is_root_solution_set is a plain bool without atomic protection. When concurrent LP root solve is enabled (line 1450-1461), both PDLP (diversity_manager.cu:442) and dual simplex can invoke set_root_relaxation_solution concurrently, creating a data race on the guard check at line 87 and the write that occurs elsewhere. Use std::atomic<bool> with atomic exchange to ensure thread-safe guarding.

🐛 Proposed fix
-  bool is_root_solution_set{false};
+  std::atomic<bool> is_root_solution_set{false};

   void set_root_relaxation_solution(...){
-    if (!is_root_solution_set) {
+    if (!is_root_solution_set.exchange(true, std::memory_order_acq_rel)) {
       root_crossover_soln_.x              = primal;
       root_crossover_soln_.y              = dual;
       root_crossover_soln_.z              = reduced_costs;
       root_objective_                     = objective;
       root_crossover_soln_.objective      = objective;
       root_crossover_soln_.user_objective = user_objective;
       root_crossover_soln_.iterations     = iterations;
       root_crossover_solution_set_.store(true, std::memory_order_release);
     }
   }

As per coding guidelines, prevent thread-unsafe use of shared solver state.

Also applies to: 167

🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/branch_and_bound.hpp` around lines 87 - 96, Replace the
plain bool guard is_root_solution_set with a std::atomic<bool> and change the
check to an atomic exchange so only one thread proceeds to write the root
solution; specifically, in set_root_relaxation_solution use something like if
(!is_root_solution_set.exchange(true, std::memory_order_acq_rel)) { ... } (and
retain root_crossover_solution_set_.store as before). Add `#include` <atomic> and
apply the same atomic-guard fix to the other occurrence referenced (around the
second use at the other location).

}

// Set a solution based on the user problem during the course of the solve
Expand Down Expand Up @@ -162,6 +164,7 @@ class branch_and_bound_t {
std::atomic<bool> root_crossover_solution_set_{false};
bool enable_concurrent_lp_root_solve_{false};
std::atomic<int> root_concurrent_halt_{0};
bool is_root_solution_set{false};

// Pseudocosts
pseudo_costs_t<i_t, f_t> pc_;
Expand Down
6 changes: 0 additions & 6 deletions cpp/src/dual_simplex/phase2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2077,12 +2077,6 @@ void prepare_optimality(const lp_problem_t<i_t, f_t>& lp,
settings.log.printf("Primal infeasibility (abs): %.2e\n", primal_infeas);
settings.log.printf("Dual infeasibility (abs): %.2e\n", dual_infeas);
settings.log.printf("Perturbation: %.2e\n", perturbation);
} else {
settings.log.printf("\n");
settings.log.printf(
"Root relaxation solution found in %d iterations and %.2fs\n", iter, toc(start_time));
settings.log.printf("Root relaxation objective %+.8e\n", sol.user_objective);
settings.log.printf("\n");
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion cpp/src/dual_simplex/solve.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -30,6 +30,22 @@ enum class lp_status_t {
UNSET = 8
};

static std::string lp_status_to_string(lp_status_t status)
{
switch (status) {
case lp_status_t::OPTIMAL: return "OPTIMAL";
case lp_status_t::INFEASIBLE: return "INFEASIBLE";
case lp_status_t::UNBOUNDED: return "UNBOUNDED";
case lp_status_t::ITERATION_LIMIT: return "ITERATION_LIMIT";
case lp_status_t::TIME_LIMIT: return "TIME_LIMIT";
case lp_status_t::NUMERICAL_ISSUES: return "NUMERICAL_ISSUES";
case lp_status_t::CUTOFF: return "CUTOFF";
case lp_status_t::CONCURRENT_LIMIT: return "CONCURRENT_LIMIT";
case lp_status_t::UNSET: return "UNSET";
}
return "UNKNOWN";
}
Comment on lines +33 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "solve.hpp" -type f | head -20

Repository: NVIDIA/cuopt

Length of output: 175


🏁 Script executed:

cat -n cpp/src/dual_simplex/solve.hpp | head -60

Repository: NVIDIA/cuopt

Length of output: 2449


🏁 Script executed:

# Check if <string> is already included directly
grep -n "^#include <string>" cpp/src/dual_simplex/solve.hpp

Repository: NVIDIA/cuopt

Length of output: 38


🏁 Script executed:

# Check what headers are transitively including string
grep -r "include <string>" cpp/src/dual_simplex/*.hpp | head -20

Repository: NVIDIA/cuopt

Length of output: 218


🏁 Script executed:

# Check what types.hpp includes to see if it brings in <string>
cat cpp/src/dual_simplex/types.hpp | head -50

Repository: NVIDIA/cuopt

Length of output: 573


🏁 Script executed:

# Also check if any included header brings in <string>
for header in basis_updates initial_basis presolve simplex_solver_settings types; do
  echo "=== $header.hpp ==="
  grep -n "#include <string>" cpp/src/dual_simplex/${header}.hpp
done

Repository: NVIDIA/cuopt

Length of output: 182


🏁 Script executed:

# Check if std::vector is used and where it comes from
grep -n "std::vector" cpp/src/dual_simplex/solve.hpp

Repository: NVIDIA/cuopt

Length of output: 665


🏁 Script executed:

# Check if any of the included headers transitively includes <string> or <vector>
for header in basis_updates initial_basis presolve simplex_solver_settings; do
  echo "=== Checking $header.hpp ==="
  head -30 cpp/src/dual_simplex/${header}.hpp | grep -E "^#include"
done

Repository: NVIDIA/cuopt

Length of output: 1002


🏁 Script executed:

# Check if there's a common precompiled header or config header
grep -r "^#pragma once" cpp/src/dual_simplex/*.hpp | wc -l

Repository: NVIDIA/cuopt

Length of output: 58


🏁 Script executed:

# Let's check one of the included headers more thoroughly to see all its includes
head -40 cpp/src/dual_simplex/basis_updates.hpp

Repository: NVIDIA/cuopt

Length of output: 1262


Consider adding #include <string> for header self-containment, though it's currently available transitively.

lp_status_to_string returns std::string, but this header doesn't directly include <string>. While the type is available through the transitive include chain (presolve.hppuser_problem.hpp<string>), adding a direct include would make the header self-contained and more resilient to future refactoring of that chain.

🤖 Prompt for AI Agents
In `@cpp/src/dual_simplex/solve.hpp` around lines 33 - 47, Add a direct include
for the string header to make the header self-contained: include <string> at the
top of the file that defines lp_status_to_string so the return type std::string
is available without relying on transitive includes (update
cpp/src/dual_simplex/solve.hpp to add the include and keep the existing
lp_status_to_string function as-is).


template <typename i_t, typename f_t>
f_t compute_objective(const lp_problem_t<i_t, f_t>& problem, const std::vector<f_t>& x);

Expand Down
85 changes: 51 additions & 34 deletions cpp/src/linear_programming/solve.cu
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -42,6 +42,9 @@

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

#define CUOPT_LOG_CONDITIONAL_INFO(condition, ...) \
if ((condition)) { CUOPT_LOG_INFO(__VA_ARGS__); }

namespace cuopt::linear_programming {

// This serves as both a warm up but also a mandatory initial call to setup cuSparse and cuBLAS
Expand Down Expand Up @@ -417,7 +420,8 @@ run_barrier(dual_simplex::user_problem_t<i_t, f_t>& user_problem,
auto status = dual_simplex::solve_linear_program_with_barrier<i_t, f_t>(
user_problem, barrier_settings, solution);

CUOPT_LOG_INFO("Barrier finished in %.2f seconds", timer.elapsed_time());
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip, "Barrier finished in %.2f seconds", timer.elapsed_time());

if (settings.concurrent_halt != nullptr && (status == dual_simplex::lp_status_t::OPTIMAL ||
status == dual_simplex::lp_status_t::UNBOUNDED ||
Expand Down Expand Up @@ -489,9 +493,10 @@ run_dual_simplex(dual_simplex::user_problem_t<i_t, f_t>& user_problem,
auto status =
dual_simplex::solve_linear_program<i_t, f_t>(user_problem, dual_simplex_settings, solution);

CUOPT_LOG_INFO("Dual simplex finished in %.2f seconds, total time %.2f",
timer_dual_simplex.elapsed_time(),
timer.elapsed_time());
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip,
"Dual simplex finished in %.2f seconds, total time %.2f",
timer_dual_simplex.elapsed_time(),
timer.elapsed_time());

if (settings.concurrent_halt != nullptr && (status == dual_simplex::lp_status_t::OPTIMAL ||
status == dual_simplex::lp_status_t::UNBOUNDED ||
Expand Down Expand Up @@ -530,7 +535,9 @@ static optimization_problem_solution_t<i_t, f_t> run_pdlp_solver(
bool is_batch_mode)
{
if (problem.n_constraints == 0) {
CUOPT_LOG_INFO("No constraints in the problem: PDLP can't be run, use Dual Simplex instead.");
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip,
"No constraints in the problem: PDLP can't be run, use Dual Simplex instead.");
return optimization_problem_solution_t<i_t, f_t>{pdlp_termination_status_t::NumericalError,
problem.handle_ptr->get_stream()};
}
Expand All @@ -551,14 +558,16 @@ optimization_problem_solution_t<i_t, f_t> run_pdlp(detail::problem_t<i_t, f_t>&
auto sol = run_pdlp_solver(problem, settings, timer, is_batch_mode);
auto pdlp_solve_time = timer_pdlp.elapsed_time();
sol.set_solve_time(timer.elapsed_time());
CUOPT_LOG_INFO("PDLP finished");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "PDLP finished");
if (sol.get_termination_status() != pdlp_termination_status_t::ConcurrentLimit) {
CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs, Total time %.3fs",
sol.get_termination_status_string().c_str(),
sol.get_objective_value(),
sol.get_additional_termination_information().number_of_steps_taken,
pdlp_solve_time,
sol.get_solve_time());
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip,
"Status: %s Objective: %.8e Iterations: %d Time: %.3fs, Total time %.3fs",
sol.get_termination_status_string().c_str(),
sol.get_objective_value(),
sol.get_additional_termination_information().number_of_steps_taken,
pdlp_solve_time,
sol.get_solve_time());
}

const bool do_crossover = settings.crossover;
Expand Down Expand Up @@ -620,12 +629,13 @@ optimization_problem_solution_t<i_t, f_t> run_pdlp(detail::problem_t<i_t, f_t>&
info,
termination_status);
sol.copy_from(problem.handle_ptr, sol_crossover);
CUOPT_LOG_INFO("Crossover status %s", sol.get_termination_status_string().c_str());
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip, "Crossover status %s", sol.get_termination_status_string().c_str());
}
if (settings.method == method_t::Concurrent && settings.concurrent_halt != nullptr &&
crossover_info == 0 && sol.get_termination_status() == pdlp_termination_status_t::Optimal) {
// We finished. Tell dual simplex to stop if it is still running.
CUOPT_LOG_INFO("PDLP finished. Telling others to stop");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "PDLP finished. Telling others to stop");
*settings.concurrent_halt = 1;
}
return sol;
Expand Down Expand Up @@ -653,7 +663,7 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
const timer_t& timer,
bool is_batch_mode)
{
CUOPT_LOG_INFO("Running concurrent\n");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Running concurrent\n");
timer_t timer_concurrent(timer.remaining_time());

// Copy the settings so that we can set the concurrent halt pointer
Expand All @@ -668,7 +678,8 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(

if (settings.num_gpus > 1) {
int device_count = raft::device_setter::get_device_count();
CUOPT_LOG_INFO("Running PDLP and Barrier on %d GPUs", device_count);
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip, "Running PDLP and Barrier on %d GPUs", device_count);
cuopt_expects(
device_count > 1, error_type_t::RuntimeError, "Multi-GPU mode requires at least 2 GPUs");
}
Expand Down Expand Up @@ -752,41 +763,47 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
1);

f_t end_time = timer.elapsed_time();
CUOPT_LOG_INFO(
"Concurrent time: %.3fs, total time %.3fs", timer_concurrent.elapsed_time(), end_time);
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip,
"Concurrent time: %.3fs, total time %.3fs",
timer_concurrent.elapsed_time(),
end_time);
// Check status to see if we should return the pdlp solution or the dual simplex solution
if (!settings.inside_mip &&
(sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::Optimal ||
sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible ||
sol_dual_simplex.get_termination_status() == pdlp_termination_status_t::DualInfeasible)) {
CUOPT_LOG_INFO("Solved with dual simplex");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Solved with dual simplex");
sol_pdlp.copy_from(problem.handle_ptr, sol_dual_simplex);
sol_pdlp.set_solve_time(end_time);
CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs",
sol_pdlp.get_termination_status_string().c_str(),
sol_pdlp.get_objective_value(),
sol_pdlp.get_additional_termination_information().number_of_steps_taken,
end_time);
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip,
"Status: %s Objective: %.8e Iterations: %d Time: %.3fs",
sol_pdlp.get_termination_status_string().c_str(),
sol_pdlp.get_objective_value(),
sol_pdlp.get_additional_termination_information().number_of_steps_taken,
end_time);
return sol_pdlp;
} else if (sol_barrier.get_termination_status() == pdlp_termination_status_t::Optimal) {
CUOPT_LOG_INFO("Solved with barrier");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Solved with barrier");
sol_pdlp.copy_from(problem.handle_ptr, sol_barrier);
sol_pdlp.set_solve_time(end_time);
CUOPT_LOG_INFO("Status: %s Objective: %.8e Iterations: %d Time: %.3fs",
sol_pdlp.get_termination_status_string().c_str(),
sol_pdlp.get_objective_value(),
sol_pdlp.get_additional_termination_information().number_of_steps_taken,
end_time);
CUOPT_LOG_CONDITIONAL_INFO(
!settings.inside_mip,
"Status: %s Objective: %.8e Iterations: %d Time: %.3fs",
sol_pdlp.get_termination_status_string().c_str(),
sol_pdlp.get_objective_value(),
sol_pdlp.get_additional_termination_information().number_of_steps_taken,
end_time);
return sol_pdlp;
} else if (sol_pdlp.get_termination_status() == pdlp_termination_status_t::Optimal) {
CUOPT_LOG_INFO("Solved with PDLP");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Solved with PDLP");
return sol_pdlp;
} else if (!settings.inside_mip &&
sol_pdlp.get_termination_status() == pdlp_termination_status_t::ConcurrentLimit) {
CUOPT_LOG_INFO("Using dual simplex solve info");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Using dual simplex solve info");
return sol_dual_simplex;
} else {
CUOPT_LOG_INFO("Using PDLP solve info");
CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Using PDLP solve info");
return sol_pdlp;
}
}
Expand Down
16 changes: 7 additions & 9 deletions cpp/src/mip/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
// to bring variables within the bounds
}

// Send PDLP relaxed solution to branch and bound before it solves the root node
// Send PDLP relaxed solution to branch and bound
if (problem_ptr->set_root_relaxation_solution_callback != nullptr) {
auto& d_primal_solution = lp_result.get_primal_solution();
auto& d_dual_solution = lp_result.get_dual_solution();
Expand All @@ -434,15 +434,13 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
problem_ptr->handle_ptr->get_stream());
problem_ptr->handle_ptr->sync_stream();

auto user_obj = problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value());
// PDLP returns user-space objective (it applies objective_scaling_factor internally)
auto user_obj = lp_result.get_objective_value();
auto solver_obj = problem_ptr->get_solver_obj_from_user_obj(user_obj);
auto iterations = lp_result.get_additional_termination_information().number_of_steps_taken;
// Set for the B&B
problem_ptr->set_root_relaxation_solution_callback(host_primal,
host_dual,
host_reduced_costs,
lp_result.get_objective_value(),
user_obj,
iterations);
// Set for the B&B (param4 expects solver space, param5 expects user space)
problem_ptr->set_root_relaxation_solution_callback(
host_primal, host_dual, host_reduced_costs, solver_obj, user_obj, iterations);
}

// in case the pdlp returned var boudns that are out of bounds
Expand Down
Loading
Loading