Skip to content
Draft
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
21 changes: 11 additions & 10 deletions cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@
#define CUOPT_USER_PROBLEM_FILE "user_problem_file"

/* @brief LP/MIP termination status constants */
#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0
#define CUOPT_TERIMINATION_STATUS_OPTIMAL 1
#define CUOPT_TERIMINATION_STATUS_INFEASIBLE 2
#define CUOPT_TERIMINATION_STATUS_UNBOUNDED 3
#define CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT 4
#define CUOPT_TERIMINATION_STATUS_TIME_LIMIT 5
#define CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR 6
#define CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE 7
#define CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND 8
#define CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT 9
#define CUOPT_TERMINATION_STATUS_NO_TERMINATION 0
#define CUOPT_TERMINATION_STATUS_OPTIMAL 1
#define CUOPT_TERMINATION_STATUS_INFEASIBLE 2
#define CUOPT_TERMINATION_STATUS_UNBOUNDED 3
#define CUOPT_TERMINATION_STATUS_ITERATION_LIMIT 4
#define CUOPT_TERMINATION_STATUS_TIME_LIMIT 5
#define CUOPT_TERMINATION_STATUS_NUMERICAL_ERROR 6
#define CUOPT_TERMINATION_STATUS_PRIMAL_FEASIBLE 7
#define CUOPT_TERMINATION_STATUS_FEASIBLE_FOUND 8
#define CUOPT_TERMINATION_STATUS_CONCURRENT_LIMIT 9

/* @brief The objective sense constants */
#define CUOPT_MINIMIZE 1
Expand Down Expand Up @@ -107,6 +107,7 @@
#define CUOPT_METHOD_PDLP 1
#define CUOPT_METHOD_DUAL_SIMPLEX 2
#define CUOPT_METHOD_BARRIER 3
#define CUOPT_METHOD_UNSET 4

/* @brief File format constants for problem I/O */
#define CUOPT_FILE_FORMAT_MPS 0
Expand Down
14 changes: 7 additions & 7 deletions cpp/include/cuopt/linear_programming/mip/solver_solution.hpp
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 All @@ -24,12 +24,12 @@
namespace cuopt::linear_programming {

enum class mip_termination_status_t : int8_t {
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
FeasibleFound = CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND,
Infeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
Unbounded = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
NoTermination = CUOPT_TERMINATION_STATUS_NO_TERMINATION,
Optimal = CUOPT_TERMINATION_STATUS_OPTIMAL,
FeasibleFound = CUOPT_TERMINATION_STATUS_FEASIBLE_FOUND,
Infeasible = CUOPT_TERMINATION_STATUS_INFEASIBLE,
Unbounded = CUOPT_TERMINATION_STATUS_UNBOUNDED,
TimeLimit = CUOPT_TERMINATION_STATUS_TIME_LIMIT,
};

template <typename i_t, typename f_t>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,20 @@ enum pdlp_solver_mode_t : int {
* @brief Enum representing the different methods that can be used to solve the
* linear programming problem.
*
* Concurrent: Use both PDLP and DualSimplex in parallel.
* Concurrent: Use PDLP, Barrier and DualSimplex in parallel.
* PDLP: Use the PDLP method.
* DualSimplex: Use the dual simplex method.
* Barrier: Use the barrier method
* Unset: The value was not set.
*
* @note Default method is Concurrent.
*/
enum method_t : int {
Concurrent = CUOPT_METHOD_CONCURRENT,
PDLP = CUOPT_METHOD_PDLP,
DualSimplex = CUOPT_METHOD_DUAL_SIMPLEX,
Barrier = CUOPT_METHOD_BARRIER
Barrier = CUOPT_METHOD_BARRIER,
Unset = CUOPT_METHOD_UNSET
};

template <typename i_t, typename f_t>
Expand Down
33 changes: 22 additions & 11 deletions cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cuopt/linear_programming/constants.h>
#include <cuopt/error.hpp>
#include <cuopt/linear_programming/pdlp/pdlp_warm_start_data.hpp>
#include <cuopt/linear_programming/pdlp/solver_settings.hpp>
#include <cuopt/linear_programming/utilities/internals.hpp>

#include <rmm/cuda_stream_view.hpp>
Expand All @@ -25,17 +26,27 @@ namespace cuopt::linear_programming {

// Possible reasons for terminating
enum class pdlp_termination_status_t : int8_t {
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
NumericalError = CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
PrimalInfeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
DualInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
IterationLimit = CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
PrimalFeasible = CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE,
ConcurrentLimit = CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT
NoTermination = CUOPT_TERMINATION_STATUS_NO_TERMINATION,
NumericalError = CUOPT_TERMINATION_STATUS_NUMERICAL_ERROR,
Optimal = CUOPT_TERMINATION_STATUS_OPTIMAL,
PrimalInfeasible = CUOPT_TERMINATION_STATUS_INFEASIBLE,
DualInfeasible = CUOPT_TERMINATION_STATUS_UNBOUNDED,
IterationLimit = CUOPT_TERMINATION_STATUS_ITERATION_LIMIT,
TimeLimit = CUOPT_TERMINATION_STATUS_TIME_LIMIT,
PrimalFeasible = CUOPT_TERMINATION_STATUS_PRIMAL_FEASIBLE,
ConcurrentLimit = CUOPT_TERMINATION_STATUS_CONCURRENT_LIMIT
};

inline std::string method_to_string(method_t method)
{
switch (method) {
case method_t::DualSimplex: return "Dual Simplex";
case method_t::PDLP: return "PDLP";
case method_t::Barrier: return "Barrier";
default: return "Unset";
}
}

/**
* @brief A container of PDLP solver output
* @tparam i_t Integer type. Currently only int is supported.
Expand Down Expand Up @@ -88,8 +99,8 @@ class optimization_problem_solution_t : public base_solution_t {
/** Solve time in seconds */
double solve_time{std::numeric_limits<double>::signaling_NaN()};

/** Whether the problem was solved by PDLP or Dual Simplex */
bool solved_by_pdlp{false};
/** Whether the problem was solved by PDLP, Barrier or Dual Simplex */
method_t solved_by = method_t::Unset;
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -60,7 +60,7 @@ struct linear_programming_ret_t {
double gap_;
int nb_iterations_;
double solve_time_;
bool solved_by_pdlp_;
linear_programming::method_t solved_by_;
};

struct mip_ret_t {
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/dual_simplex/basis_solves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ i_t factorize_basis(const csc_matrix_t<i_t, f_t>& A,
SU,
S_perm_inv);
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return -1;
}
if (Srank != Sdim) {
Expand Down Expand Up @@ -581,7 +581,7 @@ i_t factorize_basis(const csc_matrix_t<i_t, f_t>& A,
}
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return -1;
}
if (verbose) {
Expand Down
6 changes: 3 additions & 3 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@ lp_status_t branch_and_bound_t<i_t, f_t>::solve_root_relaxation(
root_status = lp_status_t::OPTIMAL;
user_objective = root_crossover_soln_.user_objective;
iter = root_crossover_soln_.iterations;
solver_name = "Barrier/PDLP and Crossover";
solver_name = method_to_string(root_relax_solved_by);

} else {
root_status = root_status_future.get();
Expand All @@ -1381,13 +1381,13 @@ lp_status_t branch_and_bound_t<i_t, f_t>::solve_root_relaxation(

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",
settings_.log.printf("Root relaxation solution found in %d iterations and %.2fs with %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",
settings_.log.printf("Root relaxation returned: %s\n",
lp_status_to_string(root_status).c_str());
}

Expand Down
7 changes: 6 additions & 1 deletion cpp/src/dual_simplex/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#pragma once

#include <cuopt/linear_programming/pdlp/solver_solution.hpp>

#include <dual_simplex/diving_heuristics.hpp>
#include <dual_simplex/initial_basis.hpp>
#include <dual_simplex/mip_node.hpp>
Expand Down Expand Up @@ -83,7 +85,8 @@ class branch_and_bound_t {
const std::vector<f_t>& reduced_costs,
f_t objective,
f_t user_objective,
i_t iterations)
i_t iterations,
method_t method)
{
if (!is_root_solution_set) {
root_crossover_soln_.x = primal;
Expand All @@ -93,6 +96,7 @@ class branch_and_bound_t {
root_crossover_soln_.objective = objective;
root_crossover_soln_.user_objective = user_objective;
root_crossover_soln_.iterations = iterations;
root_relax_solved_by = method;
root_crossover_solution_set_.store(true, std::memory_order_release);
}
}
Expand Down Expand Up @@ -166,6 +170,7 @@ class branch_and_bound_t {
f_t root_objective_;
lp_solution_t<i_t, f_t> root_relax_soln_;
lp_solution_t<i_t, f_t> root_crossover_soln_;
method_t root_relax_solved_by;
std::vector<f_t> edge_norms_;
std::atomic<bool> root_crossover_solution_set_{false};
bool enable_concurrent_lp_root_solve_{false};
Expand Down
10 changes: 5 additions & 5 deletions cpp/src/dual_simplex/crossover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ i_t dual_push(const lp_problem_t<i_t, f_t>& lp,
return -1;
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return -2;
}
}
Expand Down Expand Up @@ -832,7 +832,7 @@ i_t primal_push(const lp_problem_t<i_t, f_t>& lp,
return -1;
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return -2;
}
}
Expand Down Expand Up @@ -1163,7 +1163,7 @@ crossover_status_t crossover(const lp_problem_t<i_t, f_t>& lp,
return crossover_status_t::TIME_LIMIT;
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return crossover_status_t::CONCURRENT_LIMIT;
}

Expand Down Expand Up @@ -1250,7 +1250,7 @@ crossover_status_t crossover(const lp_problem_t<i_t, f_t>& lp,
return crossover_status_t::TIME_LIMIT;
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return crossover_status_t::CONCURRENT_LIMIT;
}
primal_infeas = primal_infeasibility(lp, settings, vstatus, solution.x);
Expand Down Expand Up @@ -1386,7 +1386,7 @@ crossover_status_t crossover(const lp_problem_t<i_t, f_t>& lp,
return crossover_status_t::TIME_LIMIT;
}
if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return crossover_status_t::CONCURRENT_LIMIT;
}
solution.iterations += iter;
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/dual_simplex/pseudo_costs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
}

settings.log.printf(
"Batch PDLP strong branching took %.2f seconds. Solved %d/%d with max %d iterations\n",
"Strong branching took %.2f seconds with Batch PDLP. Solved %d/%d with max %d iterations\n",
duration.count(),
amount_done,
fractional.size() * 2,
Expand Down Expand Up @@ -351,7 +351,7 @@ void strong_branching(const user_problem_t<i_t, f_t>& original_problem,
}
std::chrono::steady_clock::time_point end_timea = std::chrono::steady_clock::now();
std::chrono::duration<f_t> duration = end_timea - start_timea;
settings.log.printf("Dual Simplex Strong branching took %.2f seconds\n", duration.count());
settings.log.printf("Strong branching took %.2f seconds with Dual Simplex\n", duration.count());
}

pc.update_pseudo_costs_from_strong_branching(fractional, root_soln);
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/dual_simplex/right_looking_lu.cpp
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 @@ -1114,7 +1114,7 @@ i_t right_looking_lu_row_permutation_only(const csc_matrix_t<i_t, f_t>& A,
}

if (settings.concurrent_halt != nullptr && *settings.concurrent_halt == 1) {
settings.log.printf("Concurrent halt\n");
if (!settings.inside_mip) { settings.log.printf("Concurrent halt\n"); }
return -2;
}
}
Expand Down
22 changes: 14 additions & 8 deletions cpp/src/linear_programming/pdlp.cu
Original file line number Diff line number Diff line change
Expand Up @@ -764,10 +764,13 @@ pdlp_solver_t<i_t, f_t>::check_batch_termination(const timer_t& timer)
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.total_number_of_attempted_steps = pdhg_solver_.get_total_pdhg_iterations();
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.solved_by_pdlp = (current_termination_strategy_.get_termination_status(i) !=
pdlp_termination_status_t::ConcurrentLimit);

if (current_termination_strategy_.get_termination_status(i) !=
pdlp_termination_status_t::ConcurrentLimit) {
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.solved_by = method_t::PDLP;
}
Comment on lines 768 to 773
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reset solved_by on ConcurrentLimit to avoid stale solver metadata.

When Line 768/839 skips assignment for ConcurrentLimit, the reused termination-info entry can retain a previous value and misreport the solver. Consider explicitly setting solved_by to lp_solver_type_t::Unset before the conditional.

🛠️ Suggested fix
-        if (current_termination_strategy_.get_termination_status(i) !=
-            pdlp_termination_status_t::ConcurrentLimit) {
-          batch_solution_to_return_
-            .get_additional_termination_informations()[climber_strategies_[i].original_index]
-            .solved_by = lp_solver_type_t::PDLP;
-        }
+        auto& term_info = batch_solution_to_return_
+          .get_additional_termination_informations()[climber_strategies_[i].original_index];
+        term_info.solved_by = lp_solver_type_t::Unset;
+        if (current_termination_strategy_.get_termination_status(i) !=
+            pdlp_termination_status_t::ConcurrentLimit) {
+          term_info.solved_by = lp_solver_type_t::PDLP;
+        }

Also applies to: 839-844

🤖 Prompt for AI Agents
In `@cpp/src/linear_programming/pdlp.cu` around lines 768 - 773, The
termination-info entry can retain stale solver metadata when the status is
ConcurrentLimit; before the existing conditional in the PDLP termination
handling, explicitly reset
batch_solution_to_return_.get_additional_termination_informations()[climber_strategies_[i].original_index].solved_by
to lp_solver_type_t::Unset, then keep the existing if
(current_termination_strategy_.get_termination_status(i) !=
pdlp_termination_status_t::ConcurrentLimit) assignment to set solved_by =
lp_solver_type_t::PDLP; do the same reset at the other location covering lines
839–844 to ensure no stale values remain.

}
current_termination_strategy_.fill_gpu_terms_stats(total_pdlp_iterations_);
RAFT_CUDA_TRY(cudaStreamSynchronize(stream_view_));
Expand Down Expand Up @@ -832,10 +835,13 @@ pdlp_solver_t<i_t, f_t>::check_batch_termination(const timer_t& timer)
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.total_number_of_attempted_steps = pdhg_solver_.get_total_pdhg_iterations();
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.solved_by_pdlp = (current_termination_strategy_.get_termination_status(i) !=
pdlp_termination_status_t::ConcurrentLimit);

if (current_termination_strategy_.get_termination_status(i) !=
pdlp_termination_status_t::ConcurrentLimit) {
batch_solution_to_return_
.get_additional_termination_informations()[climber_strategies_[i].original_index]
.solved_by = method_t::PDLP;
}
}
}
if (to_remove.size() > 0) {
Expand Down
Loading