From e95a752b9fde2fabd15864563d1b00dab5bc0548 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 16:43:07 +0000 Subject: [PATCH 1/3] Sync cuOpt options with NVIDIA/cuopt main (a100ee1) - Rename concurrent_nnz_cutoff to large_nnz_threshold (cuOpt #2067). The old name was removed upstream without an alias, so option files that set it fail with an unknown option. The threshold now also enables automatic PDLP column reduction where supported; -1 disables both. - Add pdlp_hyper_reduced_matrix (cuOpt #2067): -1 automatic, 0 off, 1 column reduction. - Add pdlp_hyper_project_initial_primal (cuOpt #2089): whether PDLP projects the initial primal solution (the GAMS levels) onto the variable bounds; -1 uses the pdlp_solver_mode preset. - Add regression model lp_pdlp.gms: solves a random LP with column reduction forced on, automatic and off, and checks that PDLP warm starts from levels and marginals with PSLP presolve on (cuOpt #2024 maps the initial solution into the presolved space), with Papilo (cuOpt skips LP presolve) and with both pdlp_hyper_project_initial_primal settings. all_options.gms picks up the new options from optcuopt.def. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ALHyZUW9bDK51TntcQG4cg --- README.md | 2 +- assets/optcuopt.def | 4 +- examples/models/regression_tests/lp_pdlp.gms | 98 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 examples/models/regression_tests/lp_pdlp.gms diff --git a/README.md b/README.md index 23e939e..2944b2d 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Various GAMS models can be found in subfolder `examples/models` and are used to ### Regression tests -The self-checking models in `examples/models/regression_tests` cover dual signs and reduced costs, QP/QCQP marginals, RMIQCP, option handling, error reporting, LP limit points, GMO handling (e.g. `=N=` rows, `requestMarginals=2`), rejection of unsupported features (SOS, semi-integer, MIQCP) and solve/model status mapping. Each model aborts if a result deviates from the reference values (obtained with CPLEX). Run them all against the GAMS system found in your `PATH` (it needs a GPU and the installed solver link): +The self-checking models in `examples/models/regression_tests` cover dual signs and reduced costs, QP/QCQP marginals, RMIQCP, option handling, error reporting, LP limit points, PDLP column reduction and warm starts through LP presolve, GMO handling (e.g. `=N=` rows, `requestMarginals=2`), rejection of unsupported features (SOS, semi-integer, MIQCP) and solve/model status mapping. Each model aborts if a result deviates from the reference values (obtained with CPLEX). Run them all against the GAMS system found in your `PATH` (it needs a GPU and the installed solver link): ``` examples/models/regression_tests/run_tests.sh diff --git a/assets/optcuopt.def b/assets/optcuopt.def index 8fc1b19..04e92b7 100644 --- a/assets/optcuopt.def +++ b/assets/optcuopt.def @@ -44,13 +44,15 @@ iteration_limit integer 0 maxint 0 maxint 1 2 Controls the iteration limit after infeasibility_detection boolean 0 0 1 2 Controls whether PDLP should detect infeasibility strict_infeasibility boolean 0 0 1 2 Controls the strict infeasibility mode in PDLP crossover boolean 0 0 1 2 Controls whether PDLP should crossover to a basic solution after an optimal solution is found -concurrent_nnz_cutoff integer 0 50000000 -1 maxint 1 2 Controls the reduced number of nonzeros at or above which barrier and dual simplex are skipped in concurrent mode (-1 disables the cutoff) +large_nnz_threshold integer 0 50000000 -1 maxint 1 2 Controls the reduced number of nonzeros at or above which barrier and dual simplex are skipped in concurrent mode and automatic PDLP column reduction is enabled where supported (-1 disables both) primal_simplex_pricing enumint 0 1 1 2 Controls the pricing rule of the primal simplex method 0 1 Dantzig pricing 1 1 Devex pricing (default) dual_simplex_initial_perturbation integer 0 -1 -1 1 1 2 Controls whether the simplex method perturbs the problem initially (-1 automatic, 0 off, 1 on) dual_simplex_remove_perturbation integer 0 -1 -1 1 1 2 Controls whether the simplex method removes the perturbation (-1 automatic, 0 off, 1 on) pdlp_hyper_enable_curtis_reid_scaling boolean 0 1 1 2 Controls whether Curtis-Reid prescaling runs before Ruiz/Pock-Chambolle scaling in PDLP +pdlp_hyper_reduced_matrix integer 0 -1 -1 1 1 2 Controls PDLP column reduction, which iterates on a reduced matrix with periodic full-problem refreshes (-1 automatic, at or above large_nnz_threshold, 0 off, 1 column reduction) +pdlp_hyper_project_initial_primal integer 0 -1 -1 1 1 2 Controls whether PDLP projects the initial primal solution (the GAMS levels) onto the variable bounds (-1 pdlp_solver_mode preset, 0 off, 1 on) save_best_primal_so_far boolean 0 0 1 2 Controls whether PDLP should save the best primal solution so far first_primal_feasible boolean 0 0 1 2 Controls whether PDLP should stop when the first primal feasible solution is found per_constraint_residual boolean 0 0 1 2 Controls whether PDLP should compute the primal & dual residual per constraint instead of globally diff --git a/examples/models/regression_tests/lp_pdlp.gms b/examples/models/regression_tests/lp_pdlp.gms new file mode 100644 index 0000000..40c93cf --- /dev/null +++ b/examples/models/regression_tests/lp_pdlp.gms @@ -0,0 +1,98 @@ +$Title PDLP column reduction and warm starts through LP presolve + +* Random feasible LP: min sum(j, c(j)*x(j)) s.t. A x >= b, 0 <= x <= 5 +Set i /r1*r3000/, j /c1*c4000/; +Parameter a(i,j), x0(j), b(i), c(j); +option seed = 1; +a(i,j)$(uniform(0,1) < 0.005) = uniform(1,10); +x0(j) = uniform(0,1); +b(i) = sum(j, a(i,j)*x0(j)) - 1; +c(j) = uniform(1,10); +Positive Variable x(j); +Free Variable z; +x.up(j) = 5; +Equations obj, con(i); +obj.. z =e= sum(j, c(j)*x(j)); +con(i).. sum(j$a(i,j), a(i,j)*x(j)) =g= b(i); +Model m /all/; +option lp = cuopt; + +* Column reduction (cuOpt #2067): forced on, automatic for every LP (threshold 0), and off. +* Concurrent mode with large_nnz_threshold 0 also skips barrier and dual simplex. +$onEcho > cuopt.opt +method 1 +pdlp_hyper_reduced_matrix 1 +$offEcho +$onEcho > cuopt.op2 +method 0 +large_nnz_threshold 0 +$offEcho +$onEcho > cuopt.op3 +method 1 +large_nnz_threshold -1 +pdlp_hyper_reduced_matrix 0 +$offEcho + +* Warm starts (cuOpt #2024, #2089). The link passes the levels and marginals as initial primal +* and dual solution. PSLP presolve maps them into the presolved space; with Papilo cuOpt skips +* LP presolve instead. Both must still use the warm start. +$onEcho > cuopt.op4 +method 1 +presolve 2 +$offEcho +$onEcho > cuopt.op5 +method 1 +presolve 1 +$offEcho +$onEcho > cuopt.op6 +method 1 +presolve 2 +pdlp_hyper_project_initial_primal 0 +$offEcho +$onEcho > cuopt.op7 +method 1 +presolve 2 +pdlp_hyper_project_initial_primal 1 +$offEcho + +* PDLP only reaches its default tolerance of 1e-4 (relative) +Set run 'option file' /o1*o7/, reduction(run) /o1*o3/, warmstart(run) /o4*o7/; +Scalar zref 'objective of the reference solve', tol / 1e-3 /, k 'option file number'; +Parameter iters(run,*) 'PDLP iterations'; + +$macro checkOptimal(label) \ + abort$(m.solvestat <> %solveStat.normalCompletion%) 'unexpected solve status', label, m.solvestat; \ + abort$(m.modelstat <> %modelStat.optimal%) 'unexpected model status', label, m.modelstat; \ + abort$(abs(z.l - zref) > tol*max(1, abs(zref))) 'wrong objective', label, z.l, zref; + +* bratio 1 discards the levels and marginals of the previous solve (cold start) +option bratio = 1; +m.optfile = 3; +Solve m minimizing z using lp; +zref = z.l; +checkOptimal('reference') + +loop(run$reduction(run), + k = ord(run); + m.optfile = k; + Solve m minimizing z using lp; + checkOptimal(k) + iters(run,'cold') = m.iterusd; +); + +* Each warm start (bratio 0 keeps the previous optimal levels and marginals) must need fewer +* iterations than a cold solve with the same options +loop(run$warmstart(run), + k = ord(run); + m.optfile = k; + option bratio = 1; + Solve m minimizing z using lp; + checkOptimal(k) + iters(run,'cold') = m.iterusd; + option bratio = 0; + Solve m minimizing z using lp; + checkOptimal(k) + iters(run,'warm') = m.iterusd; + abort$(iters(run,'warm') >= iters(run,'cold')) 'warm start was not used', k, iters; +); +display iters; From 378ba3035cf3b14da7c8f68cf3ec7d2eb12b23aa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 16:48:34 +0000 Subject: [PATCH 2/3] Add mip_hyper_heuristic_papilo_probing_max_badgesize option Caps Papilo's probing minbadgesize in MIP presolve (-1 derives it from the problem, 0 leaves it uncapped). cuOpt registers it, but it was the last registered option missing from optcuopt.def; with it, the def covers every option of NVIDIA/cuopt main (8eb20bd) except log_file and log_to_console, which the link sets itself, and sequence_solve. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ALHyZUW9bDK51TntcQG4cg --- assets/optcuopt.def | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/optcuopt.def b/assets/optcuopt.def index 04e92b7..c40c6e1 100644 --- a/assets/optcuopt.def +++ b/assets/optcuopt.def @@ -186,6 +186,7 @@ mip_hyper_submip_round_close_ratio double 0 0.8 0 1 1 3 Controls share of the st mip_hyper_block_bve boolean 0 1 1 3 Controls whether blocks of binaries are eliminated in cuOpt's MIP presolve (needs mip_probing) mip_hyper_presolve_indicator_strengthening boolean 0 1 1 3 Controls whether implied indicator rows are appended and capacity rows are lifted by their indicator before Papilo presolve (MIP only) mip_hyper_heuristic_presolve_max_rounds integer 0 -1 -1 maxint 1 3 Controls the Papilo presolve rounds cap in heuristics (<0 derives it from the problem, 0 keeps the Papilo default) +mip_hyper_heuristic_papilo_probing_max_badgesize integer 0 -1 -1 maxint 1 3 Controls the ceiling on Papilo's probing minbadgesize in MIP presolve (<0 derives it from the problem, 0 leaves it uncapped) mip_hyper_diving_show_type boolean 0 0 1 3 log diving heuristic type when it finds a new incumbent mip_semi_continuous_big_m double 0 1e10 1 maxdouble 1 3 Controls the Big-M coefficient used when linearizing semi-continuous variable constraints From ae97aae5841c037344d718e2ae78c35886d53934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Schnabel?= Date: Wed, 14 Oct 2026 21:57:45 +0200 Subject: [PATCH 3/3] Move lp_pdlp.gms regression test to a separate PR --- examples/models/regression_tests/lp_pdlp.gms | 98 -------------------- 1 file changed, 98 deletions(-) delete mode 100644 examples/models/regression_tests/lp_pdlp.gms diff --git a/examples/models/regression_tests/lp_pdlp.gms b/examples/models/regression_tests/lp_pdlp.gms deleted file mode 100644 index 40c93cf..0000000 --- a/examples/models/regression_tests/lp_pdlp.gms +++ /dev/null @@ -1,98 +0,0 @@ -$Title PDLP column reduction and warm starts through LP presolve - -* Random feasible LP: min sum(j, c(j)*x(j)) s.t. A x >= b, 0 <= x <= 5 -Set i /r1*r3000/, j /c1*c4000/; -Parameter a(i,j), x0(j), b(i), c(j); -option seed = 1; -a(i,j)$(uniform(0,1) < 0.005) = uniform(1,10); -x0(j) = uniform(0,1); -b(i) = sum(j, a(i,j)*x0(j)) - 1; -c(j) = uniform(1,10); -Positive Variable x(j); -Free Variable z; -x.up(j) = 5; -Equations obj, con(i); -obj.. z =e= sum(j, c(j)*x(j)); -con(i).. sum(j$a(i,j), a(i,j)*x(j)) =g= b(i); -Model m /all/; -option lp = cuopt; - -* Column reduction (cuOpt #2067): forced on, automatic for every LP (threshold 0), and off. -* Concurrent mode with large_nnz_threshold 0 also skips barrier and dual simplex. -$onEcho > cuopt.opt -method 1 -pdlp_hyper_reduced_matrix 1 -$offEcho -$onEcho > cuopt.op2 -method 0 -large_nnz_threshold 0 -$offEcho -$onEcho > cuopt.op3 -method 1 -large_nnz_threshold -1 -pdlp_hyper_reduced_matrix 0 -$offEcho - -* Warm starts (cuOpt #2024, #2089). The link passes the levels and marginals as initial primal -* and dual solution. PSLP presolve maps them into the presolved space; with Papilo cuOpt skips -* LP presolve instead. Both must still use the warm start. -$onEcho > cuopt.op4 -method 1 -presolve 2 -$offEcho -$onEcho > cuopt.op5 -method 1 -presolve 1 -$offEcho -$onEcho > cuopt.op6 -method 1 -presolve 2 -pdlp_hyper_project_initial_primal 0 -$offEcho -$onEcho > cuopt.op7 -method 1 -presolve 2 -pdlp_hyper_project_initial_primal 1 -$offEcho - -* PDLP only reaches its default tolerance of 1e-4 (relative) -Set run 'option file' /o1*o7/, reduction(run) /o1*o3/, warmstart(run) /o4*o7/; -Scalar zref 'objective of the reference solve', tol / 1e-3 /, k 'option file number'; -Parameter iters(run,*) 'PDLP iterations'; - -$macro checkOptimal(label) \ - abort$(m.solvestat <> %solveStat.normalCompletion%) 'unexpected solve status', label, m.solvestat; \ - abort$(m.modelstat <> %modelStat.optimal%) 'unexpected model status', label, m.modelstat; \ - abort$(abs(z.l - zref) > tol*max(1, abs(zref))) 'wrong objective', label, z.l, zref; - -* bratio 1 discards the levels and marginals of the previous solve (cold start) -option bratio = 1; -m.optfile = 3; -Solve m minimizing z using lp; -zref = z.l; -checkOptimal('reference') - -loop(run$reduction(run), - k = ord(run); - m.optfile = k; - Solve m minimizing z using lp; - checkOptimal(k) - iters(run,'cold') = m.iterusd; -); - -* Each warm start (bratio 0 keeps the previous optimal levels and marginals) must need fewer -* iterations than a cold solve with the same options -loop(run$warmstart(run), - k = ord(run); - m.optfile = k; - option bratio = 1; - Solve m minimizing z using lp; - checkOptimal(k) - iters(run,'cold') = m.iterusd; - option bratio = 0; - Solve m minimizing z using lp; - checkOptimal(k) - iters(run,'warm') = m.iterusd; - abort$(iters(run,'warm') >= iters(run,'cold')) 'warm start was not used', k, iters; -); -display iters;