Skip to content

Commit 6736e00

Browse files
authored
Merge branch 'main' into remove-alpha-specs
2 parents 3314098 + f44e3ec commit 6736e00

File tree

25 files changed

+443
-84
lines changed

25 files changed

+443
-84
lines changed

RELEASE-NOTES.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,46 @@
11
# Release Notes
22

3+
4+
5+
## Release Notes 25.12
6+
7+
### New Features (25.12)
8+
9+
- New quadratic programming solver using the barrier method (currently in beta).
10+
- Support for quadratic objectives added to the C and Python modeling APIs.
11+
- LP concurrent mode now supports multiple GPUs. PDLP and barrier can now be run on separate GPUs.
12+
- MIP root relaxation solves now use concurrent mode: PDLP, barrier, and dual simplex.
13+
14+
### Improvements (25.12)
15+
16+
- RINS heuristic adds a new improvement strategy in the MIP solver.
17+
- Basis factorizations are now reused in the branch and bound tree.
18+
- Improvement in propagating bounds from parent to child nodes in the branch and bound tree.
19+
- GMRES with Cholesky/LDL preconditioning is now used for iterative refinement on QPs.
20+
- Improved numerical stability of dual simplex when the basis is ill-conditioned.
21+
- Improved robustness in barrier and PDLP: fixed cuSPARSE related leaks and added RAII-style wrappers for cuSPARSE structures.
22+
- Papilo-based presolve carries over implied integer information from reductions, improving consistency of integrality handling.
23+
- Build and CI workflows improved through assertion-default changes, better handling of git-hash rebuilds, and fixes to the nightly build matrix.
24+
- Reduced package sizes by adjusting build outputs and test-only library linkage, and the TSP dataset download logic disables unneeded downloads.
25+
26+
### Bug Fixes (25.12)
27+
28+
- A crash in the incumbent test is resolved.
29+
- Fixed memory leaks in Barrier and PDLP's cuSPARSE usage.
30+
- The explored nodes in the MIP log now correctly reflects the actual nodes examined.
31+
- A compilation issue in the solve_MIP benchmarking executable is fixed, restoring benchmark builds.
32+
- A logger bug when log_to_console is false is fixed.
33+
- Routing fixes improve TSP behavior when order locations are set.
34+
- Nightly container testing and CI handling fix issues in the nightly container test suite and build jobs.
35+
- A cuDF build_column deprecation issue fixed to keep compatibility with newer cuDF versions.
36+
37+
### Documentation (25.12)
38+
39+
- Missing parameters added to the documentation for LP and MILP.
40+
- Release notes added to the main repository for easy access.
41+
- Examples in the documentation improved.
42+
- The openapi spec for the service showed the 'status' value for LP/MILP results as an int but it is actually a string.
43+
344
## Release Notes 25.10
445

546
### New Features (25.10)

cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ class pdlp_solver_settings_t {
212212
method_t method{method_t::Concurrent};
213213
bool inside_mip{false};
214214
// For concurrent termination
215-
volatile int* concurrent_halt{nullptr};
215+
std::atomic<int>* concurrent_halt{nullptr};
216216
static constexpr f_t minimal_absolute_tolerance = 1.0e-12;
217217

218218
private:

cpp/src/dual_simplex/branch_and_bound.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11351135
if (get_upper_bound() < start_node->node.lower_bound) { continue; }
11361136

11371137
bool recompute_bounds_and_basis = true;
1138+
i_t nodes_explored = 0;
11381139
search_tree_t<i_t, f_t> subtree(std::move(start_node->node));
11391140
std::deque<mip_node_t<i_t, f_t>*> stack;
11401141
stack.push_front(&subtree.root);
@@ -1152,6 +1153,8 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11521153

11531154
if (toc(exploration_stats_.start_time) > settings_.time_limit) { return; }
11541155

1156+
if (nodes_explored >= 1000) { break; }
1157+
11551158
node_solve_info_t status = solve_node(node_ptr,
11561159
subtree,
11571160
leaf_problem,
@@ -1165,6 +1168,8 @@ void branch_and_bound_t<i_t, f_t>::diving_thread(const csr_matrix_t<i_t, f_t>& A
11651168
start_node->upper,
11661169
log);
11671170

1171+
nodes_explored++;
1172+
11681173
recompute_bounds_and_basis = !has_children(status);
11691174

11701175
if (status == node_solve_info_t::TIME_LIMIT) {

cpp/src/dual_simplex/branch_and_bound.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class branch_and_bound_t {
113113
f_t get_lower_bound();
114114
i_t get_heap_size();
115115
bool enable_concurrent_lp_root_solve() const { return enable_concurrent_lp_root_solve_; }
116-
volatile int* get_root_concurrent_halt() { return &root_concurrent_halt_; }
116+
std::atomic<int>* get_root_concurrent_halt() { return &root_concurrent_halt_; }
117117
void set_root_concurrent_halt(int value) { root_concurrent_halt_ = value; }
118118
lp_status_t solve_root_relaxation(simplex_solver_settings_t<i_t, f_t> const& lp_settings);
119119

@@ -170,7 +170,7 @@ class branch_and_bound_t {
170170
std::vector<f_t> edge_norms_;
171171
std::atomic<bool> root_crossover_solution_set_{false};
172172
bool enable_concurrent_lp_root_solve_{false};
173-
volatile int root_concurrent_halt_{0};
173+
std::atomic<int> root_concurrent_halt_{0};
174174

175175
// Pseudocosts
176176
pseudo_costs_t<i_t, f_t> pc_;

cpp/src/dual_simplex/pseudo_costs.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ void strong_branch_helper(i_t start,
7070
iter,
7171
child_edge_norms);
7272

73-
f_t obj = std::numeric_limits<f_t>::infinity();
73+
f_t obj = std::numeric_limits<f_t>::quiet_NaN();
7474
if (status == dual::status_t::DUAL_UNBOUNDED) {
7575
// LP was infeasible
76+
obj = std::numeric_limits<f_t>::infinity();
7677
} else if (status == dual::status_t::OPTIMAL || status == dual::status_t::ITERATION_LIMIT) {
7778
obj = compute_objective(child_problem, solution.x);
7879
} else {
@@ -227,11 +228,17 @@ void pseudo_costs_t<i_t, f_t>::initialized(i_t& num_initialized_down,
227228
for (i_t j = 0; j < n; j++) {
228229
if (pseudo_cost_num_down[j] > 0) {
229230
num_initialized_down++;
230-
pseudo_cost_down_avg += pseudo_cost_sum_down[j] / pseudo_cost_num_down[j];
231+
if (std::isfinite(pseudo_cost_sum_down[j])) {
232+
pseudo_cost_down_avg += pseudo_cost_sum_down[j] / pseudo_cost_num_down[j];
233+
}
231234
}
235+
232236
if (pseudo_cost_num_up[j] > 0) {
233237
num_initialized_up++;
234-
pseudo_cost_up_avg += pseudo_cost_sum_up[j] / pseudo_cost_num_up[j];
238+
239+
if (std::isfinite(pseudo_cost_sum_up[j])) {
240+
pseudo_cost_up_avg += pseudo_cost_sum_up[j] / pseudo_cost_num_up[j];
241+
}
235242
}
236243
}
237244
if (num_initialized_down > 0) {
@@ -317,14 +324,16 @@ void pseudo_costs_t<i_t, f_t>::update_pseudo_costs_from_strong_branching(
317324
for (i_t k = 0; k < fractional.size(); k++) {
318325
const i_t j = fractional[k];
319326
for (i_t branch = 0; branch < 2; branch++) {
320-
const f_t frac = branch == 0 ? root_soln[j] - std::floor(root_soln[j])
321-
: std::ceil(root_soln[j]) - root_soln[j];
322327
if (branch == 0) {
323328
f_t change_in_obj = strong_branch_down[k];
329+
if (std::isnan(change_in_obj)) { continue; }
330+
f_t frac = root_soln[j] - std::floor(root_soln[j]);
324331
pseudo_cost_sum_down[j] += change_in_obj / frac;
325332
pseudo_cost_num_down[j]++;
326333
} else {
327334
f_t change_in_obj = strong_branch_up[k];
335+
if (std::isnan(change_in_obj)) { continue; }
336+
f_t frac = std::ceil(root_soln[j]) - root_soln[j];
328337
pseudo_cost_sum_up[j] += change_in_obj / frac;
329338
pseudo_cost_num_up[j]++;
330339
}

cpp/src/dual_simplex/simplex_solver_settings.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ struct simplex_solver_settings_t {
145145
std::function<void()> heuristic_preemption_callback;
146146
std::function<void(std::vector<f_t>&, std::vector<f_t>&, f_t)> set_simplex_solution_callback;
147147
mutable logger_t log;
148-
volatile int* concurrent_halt; // if nullptr ignored, if !nullptr, 0 if solver should
149-
// continue, 1 if solver should halt
148+
std::atomic<int>* concurrent_halt; // if nullptr ignored, if !nullptr, 0 if solver should
149+
// continue, 1 if solver should halt
150150
};
151151

152152
} // namespace cuopt::linear_programming::dual_simplex

cpp/src/linear_programming/solve.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ void setup_device_symbols(rmm::cuda_stream_view stream_view)
306306
detail::set_pdlp_hyper_parameters(stream_view);
307307
}
308308

309-
volatile int global_concurrent_halt;
309+
std::atomic<int> global_concurrent_halt{0};
310310

311311
template <typename i_t, typename f_t>
312312
optimization_problem_solution_t<i_t, f_t> convert_dual_simplex_sol(

cpp/src/linear_programming/translate.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
8282
if (model.var_names.size() > 0) {
8383
user_problem.col_names.resize(n);
8484
for (int j = 0; j < n; ++j) {
85-
user_problem.col_names[j] = model.var_names[j];
85+
if (j < (int)model.var_names.size()) {
86+
user_problem.col_names[j] = model.var_names[j];
87+
} else {
88+
user_problem.col_names[j] = "_CUOPT_x" + std::to_string(j);
89+
}
8690
}
8791
}
8892
user_problem.obj_constant = model.presolve_data.objective_offset;

cpp/src/mip/diversity/diversity_manager.cu

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,18 +357,24 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
357357
pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2;
358358
pdlp_settings.num_gpus = context.settings.num_gpus;
359359

360-
rmm::device_uvector<f_t> lp_optimal_solution_copy(lp_optimal_solution.size(),
361-
problem_ptr->handle_ptr->get_stream());
362360
timer_t lp_timer(lp_time_limit);
363361
auto lp_result = solve_lp_with_method<i_t, f_t>(*problem_ptr, pdlp_settings, lp_timer);
364362

365363
{
366364
std::lock_guard<std::mutex> guard(relaxed_solution_mutex);
367365
if (!simplex_solution_exists.load()) {
366+
cuopt_assert(lp_result.get_primal_solution().size() == lp_optimal_solution.size(),
367+
"LP optimal solution size mismatch");
368+
cuopt_assert(lp_result.get_dual_solution().size() == lp_dual_optimal_solution.size(),
369+
"LP dual optimal solution size mismatch");
368370
raft::copy(lp_optimal_solution.data(),
369-
lp_optimal_solution_copy.data(),
371+
lp_result.get_primal_solution().data(),
370372
lp_optimal_solution.size(),
371373
problem_ptr->handle_ptr->get_stream());
374+
raft::copy(lp_dual_optimal_solution.data(),
375+
lp_result.get_dual_solution().data(),
376+
lp_dual_optimal_solution.size(),
377+
problem_ptr->handle_ptr->get_stream());
372378
} else {
373379
// copy the lp state
374380
raft::copy(lp_state.prev_primal.data(),
@@ -382,6 +388,11 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
382388
}
383389
problem_ptr->handle_ptr->sync_stream();
384390
}
391+
cuopt_assert(thrust::all_of(problem_ptr->handle_ptr->get_thrust_policy(),
392+
lp_optimal_solution.begin(),
393+
lp_optimal_solution.end(),
394+
[] __host__ __device__(f_t val) { return std::isfinite(val); }),
395+
"LP optimal solution contains non-finite values");
385396
ls.lp_optimal_exists = true;
386397
if (lp_result.get_termination_status() == pdlp_termination_status_t::Optimal) {
387398
set_new_user_bound(lp_result.get_objective_value());

cpp/src/mip/diversity/diversity_manager.cuh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class diversity_manager_t {
9595
// mutex for the simplex solution update
9696
std::mutex relaxed_solution_mutex;
9797
// atomic for signalling pdlp to stop
98-
volatile int global_concurrent_halt{0};
98+
std::atomic<int> global_concurrent_halt{0};
9999

100100
rins_t<i_t, f_t> rins;
101101

0 commit comments

Comments
 (0)