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";

Comment thread
nguidotti marked this conversation as resolved.
} 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 thread
nguidotti marked this conversation as resolved.
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 thread
nguidotti marked this conversation as resolved.

// 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";
}

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();
Comment thread
nguidotti marked this conversation as resolved.
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