Skip to content

Commit 89ebcbb

Browse files
Merge pull request #689 from rgsl888prabhu/main-merge-release/25.12_1
Main merge release/25.12 1
2 parents 14ef730 + 5720893 commit 89ebcbb

File tree

15 files changed

+382
-59
lines changed

15 files changed

+382
-59
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/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/population.cu

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,11 @@ void population_t<i_t, f_t>::run_solution_callbacks(solution_t<i_t, f_t>& sol)
283283
context.scaling.unscale_solutions(temp_sol.assignment, dummy);
284284
// Need to get unscaled problem as well
285285
problem_t<i_t, f_t> n_problem(*sol.problem_ptr->original_problem_ptr);
286-
temp_sol.problem_ptr = &n_problem;
287-
temp_sol.resize_to_original_problem();
288-
temp_sol.compute_feasibility();
289-
if (!temp_sol.get_feasible()) {
286+
auto scaled_sol(temp_sol);
287+
scaled_sol.problem_ptr = &n_problem;
288+
scaled_sol.resize_to_original_problem();
289+
scaled_sol.compute_feasibility();
290+
if (!scaled_sol.get_feasible()) {
290291
CUOPT_LOG_DEBUG("Discard infeasible after unscaling");
291292
return;
292293
}

cpp/src/mip/feasibility_jump/feasibility_jump_kernels.cu

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ __device__ void compute_mtm_moves(typename fj_t<i_t, f_t>::climber_data_t::view_
975975
// related variable table couldn't be computed ahead of time, get related variables dynamically
976976
else if (fj.pb.related_variables.size() == 0) {
977977
compute_iteration_related_variables<i_t, f_t>(fj);
978+
__syncwarp();
978979
cg::this_grid().sync();
979980
split_begin = 0;
980981
split_end = fj.pb.n_variables;
@@ -1195,6 +1196,7 @@ DI thrust::tuple<i_t, f_t, typename fj_t<i_t, f_t>::move_score_t> gridwide_reduc
11951196

11961197
// grid-wide reduce
11971198
// will be replaced by a proper load balancing scheme
1199+
__syncwarp();
11981200
cg::this_grid().sync();
11991201

12001202
if (blockIdx.x == 0) {
@@ -1365,6 +1367,7 @@ __global__ void handle_local_minimum_kernel(typename fj_t<i_t, f_t>::climber_dat
13651367

13661368
// Pick the best move among the variables involved in a random violated constraint.
13671369
if (!fj.violated_constraints.empty()) {
1370+
__syncwarp();
13681371
cg::this_grid().sync();
13691372
thrust::tie(best_var, best_delta, best_score) =
13701373
best_random_mtm_move<i_t, f_t, TPB_localmin>(fj);
@@ -1377,6 +1380,7 @@ __global__ void handle_local_minimum_kernel(typename fj_t<i_t, f_t>::climber_dat
13771380
// also consider breakthrough moves
13781381
if (*fj.best_objective < std::numeric_limits<f_t>::infinity() &&
13791382
*fj.incumbent_objective > *fj.best_objective) {
1383+
__syncwarp();
13801384
cg::this_grid().sync();
13811385
auto [bm_best_var, bm_best_delta, bm_best_score] =
13821386
best_breakthrough_move_at_local_min<i_t, f_t, TPB_localmin>(fj);
@@ -1392,6 +1396,7 @@ __global__ void handle_local_minimum_kernel(typename fj_t<i_t, f_t>::climber_dat
13921396
}
13931397

13941398
if (FIRST_THREAD) *fj.selected_var = best_var;
1399+
__syncwarp();
13951400
cg::this_grid().sync();
13961401
// still nothing? try sat MTM moves if we are in the feasible region
13971402
// Attempt to find a valid move by going over MTM moves in valid constraints
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
/*
6+
* Simple QP C API Example
7+
*
8+
* This example demonstrates how to use the cuOpt C API for quadratic programming.
9+
*
10+
* Problem:
11+
* Minimize: x^2 + y^2
12+
* Subject to:
13+
* x + y >= 1
14+
* x, y >= 0
15+
*
16+
*
17+
* Build:
18+
* gcc -I $INCLUDE_PATH -L $LIBCUOPT_LIBRARY_PATH -o simple_qp_example simple_qp_example.c -lcuopt
19+
*
20+
* Run:
21+
* ./simple_qp_example
22+
*/
23+
24+
// Include the cuOpt linear programming solver header
25+
#include <cuopt/linear_programming/cuopt_c.h>
26+
#include <stdio.h>
27+
#include <stdlib.h>
28+
29+
// Convert termination status to string
30+
const char* termination_status_to_string(cuopt_int_t termination_status)
31+
{
32+
switch (termination_status) {
33+
case CUOPT_TERIMINATION_STATUS_OPTIMAL:
34+
return "Optimal";
35+
case CUOPT_TERIMINATION_STATUS_INFEASIBLE:
36+
return "Infeasible";
37+
case CUOPT_TERIMINATION_STATUS_UNBOUNDED:
38+
return "Unbounded";
39+
case CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT:
40+
return "Iteration limit";
41+
case CUOPT_TERIMINATION_STATUS_TIME_LIMIT:
42+
return "Time limit";
43+
case CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR:
44+
return "Numerical error";
45+
case CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE:
46+
return "Primal feasible";
47+
case CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND:
48+
return "Feasible found";
49+
default:
50+
return "Unknown";
51+
}
52+
}
53+
54+
// Test simple QP problem
55+
cuopt_int_t test_simple_qp()
56+
{
57+
cuOptOptimizationProblem problem = NULL;
58+
cuOptSolverSettings settings = NULL;
59+
cuOptSolution solution = NULL;
60+
61+
/* Solve the following QP:
62+
minimize x^2 + y^2
63+
subject to:
64+
x + y >= 1
65+
x, y >= 0
66+
*/
67+
68+
cuopt_int_t num_variables = 2;
69+
cuopt_int_t num_constraints = 1;
70+
cuopt_int_t nnz = 2;
71+
72+
// CSR format constraint matrix
73+
// https://docs.nvidia.com/nvpl/latest/sparse/storage_format/sparse_matrix.html#compressed-sparse-row-csr
74+
cuopt_int_t row_offsets[] = {0, 2};
75+
cuopt_int_t column_indices[] = {0, 1};
76+
cuopt_float_t values[] = {1.0, 1.0};
77+
78+
// Objective coefficients
79+
// From the objective function: minimize x^2 + y^2
80+
// 0 is the coefficient of the linear term on x
81+
// 0 is the coefficient of the linear term on y
82+
cuopt_float_t linear_objective_coefficients[] = {0.0, 0.0};
83+
84+
// Quadratic objective matrix
85+
// From the objective function: minimize x^2 + y^2
86+
// 1 is the coefficient of the quadratic term on x^2
87+
// 1 is the coefficient of the quadratic term on y^2
88+
cuopt_float_t quadratic_objective_matrix_values[] = {1.0, 1.0};
89+
cuopt_int_t quadratic_objective_matrix_row_offsets[] = {0, 1, 2};
90+
cuopt_int_t quadratic_objective_matrix_column_indices[] = {0, 1};
91+
92+
// Constraint bounds
93+
// From the constraints:
94+
// x + y >= 1
95+
cuopt_float_t constraint_rhs[] = {1.0};
96+
char constraint_sense[] = { CUOPT_GREATER_THAN };
97+
98+
99+
// Variable bounds
100+
// From the constraints:
101+
// x1, x2 >= 0
102+
cuopt_float_t var_lower_bounds[] = {0.0, 0.0};
103+
cuopt_float_t var_upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY};
104+
105+
// Variable types (continuous)
106+
// From the constraints:
107+
// x1, x2 >= 0
108+
char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS};
109+
110+
cuopt_int_t status;
111+
cuopt_float_t time;
112+
cuopt_int_t termination_status;
113+
cuopt_float_t objective_value;
114+
115+
printf("Creating and solving simple QP problem...\n");
116+
117+
// Create the problem
118+
status = cuOptCreateQuadraticProblem(num_constraints,
119+
num_variables,
120+
CUOPT_MINIMIZE,
121+
0.0, // objective offset
122+
linear_objective_coefficients,
123+
quadratic_objective_matrix_row_offsets,
124+
quadratic_objective_matrix_column_indices,
125+
quadratic_objective_matrix_values,
126+
row_offsets,
127+
column_indices,
128+
values,
129+
constraint_sense,
130+
constraint_rhs,
131+
var_lower_bounds,
132+
var_upper_bounds,
133+
variable_types,
134+
&problem);
135+
if (status != CUOPT_SUCCESS) {
136+
printf("Error creating problem: %d\n", status);
137+
goto DONE;
138+
}
139+
140+
// Create solver settings
141+
status = cuOptCreateSolverSettings(&settings);
142+
if (status != CUOPT_SUCCESS) {
143+
printf("Error creating solver settings: %d\n", status);
144+
goto DONE;
145+
}
146+
147+
// Solve the problem
148+
status = cuOptSolve(problem, settings, &solution);
149+
if (status != CUOPT_SUCCESS) {
150+
printf("Error solving problem: %d\n", status);
151+
goto DONE;
152+
}
153+
154+
// Get solution information
155+
status = cuOptGetSolveTime(solution, &time);
156+
if (status != CUOPT_SUCCESS) {
157+
printf("Error getting solve time: %d\n", status);
158+
goto DONE;
159+
}
160+
161+
status = cuOptGetTerminationStatus(solution, &termination_status);
162+
if (status != CUOPT_SUCCESS) {
163+
printf("Error getting termination status: %d\n", status);
164+
goto DONE;
165+
}
166+
167+
status = cuOptGetObjectiveValue(solution, &objective_value);
168+
if (status != CUOPT_SUCCESS) {
169+
printf("Error getting objective value: %d\n", status);
170+
goto DONE;
171+
}
172+
173+
// Print results
174+
printf("\nResults:\n");
175+
printf("--------\n");
176+
printf("Termination status: %s (%d)\n", termination_status_to_string(termination_status), termination_status);
177+
printf("Solve time: %f seconds\n", time);
178+
printf("Objective value: %f\n", objective_value);
179+
180+
// Get and print solution variables
181+
cuopt_float_t* solution_values = (cuopt_float_t*)malloc(num_variables * sizeof(cuopt_float_t));
182+
if (solution_values == NULL) {
183+
printf("Error allocating solution values\n");
184+
goto DONE;
185+
}
186+
status = cuOptGetPrimalSolution(solution, solution_values);
187+
if (status != CUOPT_SUCCESS) {
188+
printf("Error getting solution values: %d\n", status);
189+
free(solution_values);
190+
goto DONE;
191+
}
192+
193+
printf("\nPrimal Solution: Solution variables \n");
194+
for (cuopt_int_t i = 0; i < num_variables; i++) {
195+
printf("x%d = %f\n", i + 1, solution_values[i]);
196+
}
197+
free(solution_values);
198+
199+
DONE:
200+
cuOptDestroyProblem(&problem);
201+
cuOptDestroySolverSettings(&settings);
202+
cuOptDestroySolution(&solution);
203+
204+
return status;
205+
}
206+
207+
int main() {
208+
// Run the test
209+
cuopt_int_t status = test_simple_qp();
210+
211+
if (status == CUOPT_SUCCESS) {
212+
printf("\nTest completed successfully!\n");
213+
return 0;
214+
} else {
215+
printf("\nTest failed with status: %d\n", status);
216+
return 1;
217+
}
218+
}

0 commit comments

Comments
 (0)