diff --git a/cpp/include/cuopt/mathematical_optimization/constants.h b/cpp/include/cuopt/mathematical_optimization/constants.h index 467aa7fce3..af8837617b 100644 --- a/cpp/include/cuopt/mathematical_optimization/constants.h +++ b/cpp/include/cuopt/mathematical_optimization/constants.h @@ -53,6 +53,7 @@ #define CUOPT_BARRIER_STEP_SCALE "barrier_step_scale" #define CUOPT_ELIMINATE_DENSE_COLUMNS "eliminate_dense_columns" #define CUOPT_CUDSS_DETERMINISTIC "cudss_deterministic" +#define CUOPT_CUDSS_HYPER_ND_NLEVELS "cudss_hyper_nd_nlevels" #define CUOPT_PRESOLVE "presolve" #define CUOPT_MIP_PROBING "mip_probing" #define CUOPT_DUAL_POSTSOLVE "dual_postsolve" @@ -238,8 +239,10 @@ #define CUOPT_MIP_SCALING_ON 1 #define CUOPT_MIP_SCALING_NO_OBJECTIVE 2 -#define CUOPT_BARRIER_ITERATIVE_REFINEMENT_OFF 0 -#define CUOPT_BARRIER_ITERATIVE_REFINEMENT_ON 1 +/* @brief Iterative refinement for barrier method */ +#define CUOPT_BARRIER_IR_OFF 0 +#define CUOPT_BARRIER_IR_GMRES 1 +#define CUOPT_BARRIER_IR_FIXED_POINT 2 /* @brief Scalar problem attribute selectors * Passed as cuopt_int_t; the valid set depends on the accessor's value type. */ diff --git a/cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp b/cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp index 0882f75e0f..1deb4e95fc 100644 --- a/cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp @@ -290,6 +290,7 @@ class pdlp_solver_settings_t { bool per_constraint_residual{false}; bool crossover{false}; bool cudss_deterministic{false}; + i_t cudss_nd_nlevels{-1}; i_t folding{-1}; i_t augmented{-1}; i_t dualize{-1}; @@ -303,7 +304,8 @@ class pdlp_solver_settings_t { i_t qcqp_ruiz_equilibration{-1}; bool eliminate_dense_columns{true}; pdlp_precision_t pdlp_precision{pdlp_precision_t::DefaultPrecision}; - bool barrier_iterative_refinement{true}; + // Iterative refinement for barrier method: 0: off, 1: gmres (default), 2: fixed_point + i_t barrier_iterative_refinement{barrier_iterative_refinement_t::GMRES}; i_t barrier_soc_threshold{100}; f_t barrier_step_scale{0.9}; bool save_best_primal_so_far{false}; diff --git a/cpp/include/cuopt/mathematical_optimization/utilities/internals.hpp b/cpp/include/cuopt/mathematical_optimization/utilities/internals.hpp index 25f3f34f7b..2e7a49ab21 100644 --- a/cpp/include/cuopt/mathematical_optimization/utilities/internals.hpp +++ b/cpp/include/cuopt/mathematical_optimization/utilities/internals.hpp @@ -142,5 +142,19 @@ enum presolver_t : int { PSLP = CUOPT_PRESOLVE_PSLP }; +/** + * @brief Enum representing the iterative refinement method used by the barrier + * solver after each solve. + * + * Off: Disable iterative refinement. + * GMRES: Use restarted GMRES (default). + * FixedPoint: Use a fixed-point residual-correction loop. + */ +enum barrier_iterative_refinement_t : int { + Off = CUOPT_BARRIER_IR_OFF, + GMRES = CUOPT_BARRIER_IR_GMRES, + FixedPoint = CUOPT_BARRIER_IR_FIXED_POINT +}; + } // namespace mathematical_optimization } // namespace cuopt diff --git a/cpp/src/barrier/barrier.cu b/cpp/src/barrier/barrier.cu index c164296a25..d967d7a96c 100644 --- a/cpp/src/barrier/barrier.cu +++ b/cpp/src/barrier/barrier.cu @@ -2278,9 +2278,13 @@ int barrier_solver_t::initial_point(iteration_data_t& data) } } op(data); - if (settings.barrier_iterative_refinement) { + if (settings.barrier_iterative_refinement != barrier_iterative_refinement_t::Off) { const f_t ir_tol = data.has_sparse_cones() ? f_t(1e-12) : f_t(1e-8); - iterative_refinement(op, rhs, soln, ir_tol); + + const i_t internal_method = + (settings.barrier_iterative_refinement == barrier_iterative_refinement_t::FixedPoint) ? 0 + : 1; + iterative_refinement(op, rhs, soln, ir_tol, internal_method); } for (i_t k = 0; k < lp.num_cols; k++) { @@ -2903,11 +2907,15 @@ i_t barrier_solver_t::gpu_compute_search_direction(iteration_data_tsolve(b, x); } } op(data); - if (settings.barrier_iterative_refinement) { + if (settings.barrier_iterative_refinement != barrier_iterative_refinement_t::Off) { raft::common::nvtx::range fun_scope("Barrier: iterative_refinement"); - const f_t ir_tol = data.has_sparse_cones() ? f_t(1e-12) : f_t(1e-8); + const f_t ir_tol = data.has_sparse_cones() ? f_t(1e-12) : f_t(1e-8); + + const i_t internal_method = + (settings.barrier_iterative_refinement == barrier_iterative_refinement_t::FixedPoint) ? 0 + : 1; const f_t solve_err = iterative_refinement( - op, data.d_augmented_rhs_, data.d_augmented_soln_, ir_tol); + op, data.d_augmented_rhs_, data.d_augmented_soln_, ir_tol, internal_method); if (solve_err > 1e-1) { settings.log.printf("|| Aug (dx, dy) - aug_rhs || %e after IR\n", solve_err); } @@ -2977,7 +2985,8 @@ i_t barrier_solver_t::gpu_compute_search_direction(iteration_data_t& data) : data_(data) {} iteration_data_t& data_; @@ -2993,8 +3002,12 @@ i_t barrier_solver_t::gpu_compute_search_direction(iteration_data_t(adat_op, data.d_h_, data.d_dy_); + + const i_t internal_method = + (settings.barrier_iterative_refinement == barrier_iterative_refinement_t::FixedPoint) ? 0 + : 1; + const f_t adat_solve_err = iterative_refinement( + adat_op, data.d_h_, data.d_dy_, f_t(1e-8), internal_method); if (adat_solve_err > 1e-1) { settings.log.printf("||ADAT*dy - h|| %e after IR\n", adat_solve_err); } diff --git a/cpp/src/barrier/iterative_refinement.hpp b/cpp/src/barrier/iterative_refinement.hpp index e9a6152f06..9ef957abd2 100644 --- a/cpp/src/barrier/iterative_refinement.hpp +++ b/cpp/src/barrier/iterative_refinement.hpp @@ -55,10 +55,10 @@ struct subtract_scaled_op { }; template -f_t iterative_refinement_simple(T& op, - const rmm::device_uvector& b, - rmm::device_uvector& x, - f_t tol = 1e-8) +f_t iterative_refinement_fixed_point(T& op, + const rmm::device_uvector& b, + rmm::device_uvector& x, + f_t tol = 1e-8) { rmm::device_uvector x_sav(x, x.stream()); @@ -362,16 +362,15 @@ f_t iterative_refinement_gmres(T& op, } template -f_t iterative_refinement(T& op, - const dense_vector_t& b, - dense_vector_t& x, - f_t tol = 1e-8) +f_t iterative_refinement( + T& op, const dense_vector_t& b, dense_vector_t& x, f_t tol, i_t method) { rmm::device_uvector d_b(b.size(), op.data_.handle_ptr->get_stream()); raft::copy(d_b.data(), b.data(), b.size(), op.data_.handle_ptr->get_stream()); rmm::device_uvector d_x(x.size(), op.data_.handle_ptr->get_stream()); raft::copy(d_x.data(), x.data(), x.size(), op.data_.handle_ptr->get_stream()); - auto err = iterative_refinement_gmres(op, d_b, d_x, tol); + auto err = (method == 0) ? iterative_refinement_fixed_point(op, d_b, d_x, tol) + : iterative_refinement_gmres(op, d_b, d_x, tol); raft::copy(x.data(), d_x.data(), x.size(), op.data_.handle_ptr->get_stream()); @@ -380,12 +379,11 @@ f_t iterative_refinement(T& op, } template -f_t iterative_refinement(T& op, - const rmm::device_uvector& b, - rmm::device_uvector& x, - f_t tol = 1e-8) +f_t iterative_refinement( + T& op, const rmm::device_uvector& b, rmm::device_uvector& x, f_t tol, i_t method) { - return iterative_refinement_gmres(op, b, x, tol); + return (method == 0) ? iterative_refinement_fixed_point(op, b, x, tol) + : iterative_refinement_gmres(op, b, x, tol); } } // namespace cuopt::mathematical_optimization::barrier diff --git a/cpp/src/barrier/sparse_cholesky.cuh b/cpp/src/barrier/sparse_cholesky.cuh index 01045847d1..9f8290b0ce 100644 --- a/cpp/src/barrier/sparse_cholesky.cuh +++ b/cpp/src/barrier/sparse_cholesky.cuh @@ -296,6 +296,16 @@ class sparse_cholesky_cudss_t : public sparse_cholesky_base_t { status, "cudssConfigSet for deterministic mode"); } + + if (settings_.cudss_nd_nlevels >= 0) { + settings_.log.printf("cuDSS ND levels : %d\n", settings_.cudss_nd_nlevels); + int32_t nd_nlevels = settings_.cudss_nd_nlevels; + CUDSS_CALL_AND_CHECK_EXIT( + cudssConfigSet(solverConfig, CUDSS_CONFIG_ND_NLEVELS, &nd_nlevels, sizeof(int32_t)), + status, + "cudssConfigSet for nd nlevels"); + } + #endif #if USE_ITERATIVE_REFINEMENT diff --git a/cpp/src/dual_simplex/simplex_solver_settings.hpp b/cpp/src/dual_simplex/simplex_solver_settings.hpp index c7fe06ed4a..b31c960b51 100644 --- a/cpp/src/dual_simplex/simplex_solver_settings.hpp +++ b/cpp/src/dual_simplex/simplex_solver_settings.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -66,10 +67,11 @@ struct simplex_solver_settings_t { print_presolve_stats(true), barrier_presolve(false), cudss_deterministic(false), + cudss_nd_nlevels(-1), deterministic(false), barrier(false), eliminate_dense_columns(true), - barrier_iterative_refinement(true), + barrier_iterative_refinement(barrier_iterative_refinement_t::GMRES), barrier_step_scale(0.9), barrier_soc_threshold(100), num_gpus(1), @@ -161,12 +163,13 @@ struct simplex_solver_settings_t { bool print_presolve_stats; // true to print presolve stats bool barrier_presolve; // true to use barrier presolve bool cudss_deterministic; // true to use cuDSS deterministic mode, false for non-deterministic + i_t cudss_nd_nlevels; // -1 automatic/unset, else METIS nested-dissection depth for cuDSS bool barrier; // true to use barrier method, false to use dual simplex method bool deterministic; // true to use B&B deterministic mode, false to use non-deterministic mode - bool eliminate_dense_columns; // true to eliminate dense columns from A*D*A^T - bool barrier_iterative_refinement; // true to use iterative refinement for barrier method - f_t barrier_step_scale; // step scale for barrier method - i_t barrier_soc_threshold; // SOC dimension above which rank-2 sparse scaling is used + bool eliminate_dense_columns; // true to eliminate dense columns from A*D*A^T + i_t barrier_iterative_refinement; // 0: off, 1: gmres (default), 2: fixed_point + f_t barrier_step_scale; // step scale for barrier method + i_t barrier_soc_threshold; // SOC dimension above which rank-2 sparse scaling is used int num_gpus; // Number of GPUs to use (maximum of 2 gpus are supported at the moment) i_t folding; // -1 automatic, 0 don't fold, 1 fold i_t augmented; // -1 automatic, 0 to solve with ADAT, 1 to solve with augmented system diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 2fb7896027..3719f038cd 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -496,8 +496,7 @@ pdlp_settings: - dual_postsolve: # C++ default is `true`; using proto3 `optional` so that a client that # omits this field gets the solver default rather than the proto3 zero - # (`false`). See also the matching `optional` markers on - # `barrier_iterative_refinement` (pdlp) and `probing` (mip). + # (`false`). See also the matching `optional` marker on `probing` (mip). field_num: 17 type: bool optional: true @@ -539,15 +538,6 @@ pdlp_settings: field_num: 27 type: bool optional: true - - barrier_iterative_refinement: - # Whether the barrier method runs iterative refinement after each solve - # (see cpp/src/barrier/barrier.cu). C++ default is `true`. Declared as - # proto3 `optional` so that a client which omits this field preserves - # the solver default; without `optional`, the proto3 wire zero (`false`) - # would silently overwrite the C++ default. - field_num: 31 - type: bool - optional: true - barrier_step_scale: # Step scale used by the barrier method primal/dual updates. Local-solve # binding restricts the range to [0.5, 0.9999] with default 0.9; see @@ -559,6 +549,19 @@ pdlp_settings: field_num: 33 type: int32 optional: true + - cudss_nd_nlevels: + field_num: 34 + type: int32 + optional: true + # field_num 35-37 previously used by cudss_hybrid_mode/cudss_hybrid_execute_mode/ + # cudss_host_nthreads, removed — do not reuse these numbers. + # field_num 31 previously held `barrier_iterative_refinement` as a bool; that field + # was retired and the name reused below (field_num 38) for the merged int method, + # so the exposed parameter name is unchanged even though the wire field_num moved. + - barrier_iterative_refinement: + field_num: 38 + type: int32 + optional: true - save_best_primal_so_far: field_num: 28 type: bool @@ -726,8 +729,7 @@ mip_settings: # C++ default is `true`; declared as proto3 `optional` so that a client # which omits this field preserves the solver default. Without # `optional`, the proto3 wire zero (`false`) would silently overwrite - # the C++ default. See also `dual_postsolve` and - # `barrier_iterative_refinement` in pdlp_settings. + # the C++ default. See also `dual_postsolve` in pdlp_settings. field_num: 29 type: bool optional: true diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 4b1e36d134..469e1ab960 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -191,9 +191,10 @@ message PDLPSolverSettings { bool save_best_primal_so_far = 28; bool first_primal_feasible = 29; optional int32 pdlp_precision = 30; - optional bool barrier_iterative_refinement = 31; optional double barrier_step_scale = 32; optional int32 postsolve_info = 33; + optional int32 cudss_nd_nlevels = 34; + optional int32 barrier_iterative_refinement = 38; PDLPWarmStartData warm_start_data = 50; } diff --git a/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc index eb88b192d9..1e78625f8c 100644 --- a/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc @@ -33,9 +33,10 @@ pb_settings->set_ordering(settings.ordering); pb_settings->set_barrier_dual_initial_point(settings.barrier_dual_initial_point); pb_settings->set_eliminate_dense_columns(settings.eliminate_dense_columns); - pb_settings->set_barrier_iterative_refinement(settings.barrier_iterative_refinement); pb_settings->set_barrier_step_scale(settings.barrier_step_scale); pb_settings->set_postsolve_info(settings.postsolve_info); + pb_settings->set_cudss_nd_nlevels(settings.cudss_nd_nlevels); + pb_settings->set_barrier_iterative_refinement(settings.barrier_iterative_refinement); pb_settings->set_save_best_primal_so_far(settings.save_best_primal_so_far); pb_settings->set_first_primal_feasible(settings.first_primal_feasible); pb_settings->set_pdlp_precision(static_cast(settings.pdlp_precision)); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc index 29e6c4cca2..d02c2139ea 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc @@ -73,15 +73,18 @@ if (pb_settings.has_eliminate_dense_columns()) { settings.eliminate_dense_columns = pb_settings.eliminate_dense_columns(); } - if (pb_settings.has_barrier_iterative_refinement()) { - settings.barrier_iterative_refinement = pb_settings.barrier_iterative_refinement(); - } if (pb_settings.has_barrier_step_scale()) { settings.barrier_step_scale = pb_settings.barrier_step_scale(); } if (pb_settings.has_postsolve_info()) { settings.postsolve_info = pb_settings.postsolve_info(); } + if (pb_settings.has_cudss_nd_nlevels()) { + settings.cudss_nd_nlevels = pb_settings.cudss_nd_nlevels(); + } + if (pb_settings.has_barrier_iterative_refinement()) { + settings.barrier_iterative_refinement = pb_settings.barrier_iterative_refinement(); + } settings.save_best_primal_so_far = pb_settings.save_best_primal_so_far(); settings.first_primal_feasible = pb_settings.first_primal_feasible(); if (pb_settings.has_pdlp_precision()) { diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 2a193cd70b..79cb3e858e 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -136,6 +136,7 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_FOLDING, &pdlp_settings.folding, -1, 1, -1}, {CUOPT_DUALIZE, &pdlp_settings.dualize, -1, 1, -1}, {CUOPT_ORDERING, &pdlp_settings.ordering, -1, 1, -1}, + {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, CUOPT_BARRIER_IR_OFF, CUOPT_BARRIER_IR_FIXED_POINT, CUOPT_BARRIER_IR_GMRES}, {CUOPT_BARRIER_DUAL_INITIAL_POINT, &pdlp_settings.barrier_dual_initial_point, -1, 1, -1}, {CUOPT_POSTSOLVE_INFO, &pdlp_settings.postsolve_info, -1, 1, -1}, {CUOPT_MIP_CUT_PASSES, &mip_settings.max_cut_passes, -1, std::numeric_limits::max(), 10}, @@ -190,6 +191,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_BARRIER_PRESOLVE_BOUND_FREE_VARIABLES, &pdlp_settings.barrier_presolve_bound_free_variables, -1, 1, -1, "Bound free variables during barrier presolve: -1 automatic (current default behavior), 0 disabled, 1 enabled"}, // QCQP (barrier) scaling hyper-parameter {CUOPT_QCQP_HYPER_RUIZ_EQUILIBRATION, &pdlp_settings.qcqp_ruiz_equilibration, -1, 1, -1, "Ruiz equilibration for QCQP barrier scaling: -1 automatic (row/column imbalance heuristic), 0 disabled, 1 enabled"}, + // cuDSS (barrier) reordering hyper-parameter + {CUOPT_CUDSS_HYPER_ND_NLEVELS, &pdlp_settings.cudss_nd_nlevels, -1, std::numeric_limits::max(), -1, "METIS nested-dissection depth for cuDSS: -1 unset (cuDSS default), else explicit depth"}, }; // Bool parameters @@ -206,7 +209,6 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_ELIMINATE_DENSE_COLUMNS, &pdlp_settings.eliminate_dense_columns, true}, {CUOPT_CUDSS_DETERMINISTIC, &pdlp_settings.cudss_deterministic, false}, {CUOPT_DUAL_POSTSOLVE, &pdlp_settings.dual_postsolve, true}, - {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, true}, {CUOPT_MIP_PROBING, &mip_settings.probing, true}, {CUOPT_USE_DISTRIBUTED_PDLP, &pdlp_settings.use_distributed_pdlp, false}, // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 80b3da2c18..3ae9f222c1 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -519,6 +519,7 @@ std::tuple, simplex::lp_status_t, f_t, f_t, f_t barrier_settings.barrier_step_scale = settings.barrier_step_scale; barrier_settings.qcqp_ruiz_equilibration = settings.qcqp_ruiz_equilibration; barrier_settings.cudss_deterministic = settings.cudss_deterministic; + barrier_settings.cudss_nd_nlevels = settings.cudss_nd_nlevels; barrier_settings.barrier_relaxed_feasibility_tol = settings.tolerances.relative_primal_tolerance; barrier_settings.barrier_relaxed_optimality_tol = settings.tolerances.relative_dual_tolerance; barrier_settings.barrier_relaxed_complementarity_tol = settings.tolerances.relative_gap_tolerance; @@ -690,27 +691,26 @@ static optimization_problem_solution_t run_pdlp_solver_in_fp32( static_cast(settings.tolerances.primal_infeasible_tolerance); fs.tolerances.dual_infeasible_tolerance = static_cast(settings.tolerances.dual_infeasible_tolerance); - fs.detect_infeasibility = settings.detect_infeasibility; - fs.strict_infeasibility = settings.strict_infeasibility; - fs.iteration_limit = settings.iteration_limit; - fs.time_limit = static_cast(settings.time_limit); - fs.pdlp_solver_mode = settings.pdlp_solver_mode; - fs.log_to_console = settings.log_to_console; - fs.log_file = settings.log_file; - fs.per_constraint_residual = settings.per_constraint_residual; - fs.save_best_primal_so_far = settings.save_best_primal_so_far; - fs.first_primal_feasible = settings.first_primal_feasible; - fs.all_primal_feasible = settings.all_primal_feasible; - fs.eliminate_dense_columns = settings.eliminate_dense_columns; - fs.barrier_iterative_refinement = settings.barrier_iterative_refinement; - fs.barrier_step_scale = settings.barrier_step_scale; - fs.pdlp_precision = pdlp_precision_t::DefaultPrecision; - fs.method = method_t::PDLP; - fs.inside_mip = settings.inside_mip; - fs.hyper_params = settings.hyper_params; - fs.presolver = settings.presolver; - fs.num_gpus = settings.num_gpus; - fs.concurrent_halt = settings.concurrent_halt; + fs.detect_infeasibility = settings.detect_infeasibility; + fs.strict_infeasibility = settings.strict_infeasibility; + fs.iteration_limit = settings.iteration_limit; + fs.time_limit = static_cast(settings.time_limit); + fs.pdlp_solver_mode = settings.pdlp_solver_mode; + fs.log_to_console = settings.log_to_console; + fs.log_file = settings.log_file; + fs.per_constraint_residual = settings.per_constraint_residual; + fs.save_best_primal_so_far = settings.save_best_primal_so_far; + fs.first_primal_feasible = settings.first_primal_feasible; + fs.all_primal_feasible = settings.all_primal_feasible; + fs.eliminate_dense_columns = settings.eliminate_dense_columns; + fs.barrier_step_scale = settings.barrier_step_scale; + fs.pdlp_precision = pdlp_precision_t::DefaultPrecision; + fs.method = method_t::PDLP; + fs.inside_mip = settings.inside_mip; + fs.hyper_params = settings.hyper_params; + fs.presolver = settings.presolver; + fs.num_gpus = settings.num_gpus; + fs.concurrent_halt = settings.concurrent_halt; pdlp::pdlp_solver_t solver(float_problem, fs, is_batch_mode); if (settings.inside_mip) { solver.set_inside_mip(true); } diff --git a/cpp/tests/linear_programming/grpc/grpc_client_test.cpp b/cpp/tests/linear_programming/grpc/grpc_client_test.cpp index f8fed6eee3..dfb661a241 100644 --- a/cpp/tests/linear_programming/grpc/grpc_client_test.cpp +++ b/cpp/tests/linear_programming/grpc/grpc_client_test.cpp @@ -2237,14 +2237,15 @@ TEST(MapperRoundtrip, PDLPSettingsAllFields) orig.num_gpus = 4; orig.per_constraint_residual = true; orig.cudss_deterministic = true; + orig.cudss_nd_nlevels = 8; orig.folding = 1; orig.augmented = 1; orig.dualize = 1; orig.ordering = 2; orig.barrier_dual_initial_point = 1; orig.eliminate_dense_columns = true; - orig.barrier_iterative_refinement = false; // not the default true, to detect overwrite-on-decode - orig.barrier_step_scale = 0.75; // not the default 0.9 + orig.barrier_iterative_refinement = 0; // not the default 1 (gmres) + orig.barrier_step_scale = 0.75; // not the default 0.9 orig.postsolve_info = 1; orig.pdlp_precision = pdlp_precision_t::MixedPrecision; orig.save_best_primal_so_far = true; @@ -2278,13 +2279,14 @@ TEST(MapperRoundtrip, PDLPSettingsAllFields) EXPECT_EQ(restored.num_gpus, 4); EXPECT_EQ(restored.per_constraint_residual, true); EXPECT_EQ(restored.cudss_deterministic, true); + EXPECT_EQ(restored.cudss_nd_nlevels, 8); EXPECT_EQ(restored.folding, 1); EXPECT_EQ(restored.augmented, 1); EXPECT_EQ(restored.dualize, 1); EXPECT_EQ(restored.ordering, 2); EXPECT_EQ(restored.barrier_dual_initial_point, 1); EXPECT_EQ(restored.eliminate_dense_columns, true); - EXPECT_EQ(restored.barrier_iterative_refinement, false); + EXPECT_EQ(restored.barrier_iterative_refinement, 0); EXPECT_DOUBLE_EQ(restored.barrier_step_scale, 0.75); EXPECT_EQ(restored.postsolve_info, 1); EXPECT_EQ(restored.pdlp_precision, pdlp_precision_t::MixedPrecision); @@ -2367,28 +2369,6 @@ TEST(MapperRoundtrip, PDLPSettingsDualPostsolveExplicitFalseRoundtrips) EXPECT_FALSE(restored.dual_postsolve); } -TEST(MapperRoundtrip, PDLPSettingsBarrierIterativeRefinementOmittedPreservesDefault) -{ - cuopt::remote::PDLPSolverSettings pb; - - pdlp_solver_settings_t fresh; - ASSERT_TRUE(fresh.barrier_iterative_refinement); - map_proto_to_pdlp_settings(pb, fresh); - EXPECT_TRUE(fresh.barrier_iterative_refinement) - << "Omitted optional bool must preserve the C++ default `true`"; -} - -TEST(MapperRoundtrip, PDLPSettingsBarrierIterativeRefinementExplicitFalseRoundtrips) -{ - cuopt::remote::PDLPSolverSettings pb; - pb.set_barrier_iterative_refinement(false); - ASSERT_TRUE(pb.has_barrier_iterative_refinement()); - - pdlp_solver_settings_t restored; - map_proto_to_pdlp_settings(pb, restored); - EXPECT_FALSE(restored.barrier_iterative_refinement); -} - // Wide-coverage sanity: a default-constructed proto (no fields touched on the // wire) must, after the mapper, leave every C++ scalar settings field at its // in-class default. Spot-checks a representative cross-section of the fields @@ -2427,13 +2407,14 @@ TEST(MapperRoundtrip, PDLPSettingsDefaultProtoPreservesAllCppDefaults) EXPECT_EQ(after.log_to_console, fresh.log_to_console); EXPECT_EQ(after.dual_postsolve, fresh.dual_postsolve); EXPECT_EQ(after.eliminate_dense_columns, fresh.eliminate_dense_columns); - EXPECT_EQ(after.barrier_iterative_refinement, fresh.barrier_iterative_refinement); // Numeric defaults != 0. EXPECT_EQ(after.num_gpus, fresh.num_gpus); EXPECT_EQ(after.folding, fresh.folding); EXPECT_EQ(after.augmented, fresh.augmented); EXPECT_EQ(after.dualize, fresh.dualize); EXPECT_EQ(after.ordering, fresh.ordering); + EXPECT_EQ(after.cudss_nd_nlevels, fresh.cudss_nd_nlevels); + EXPECT_EQ(after.barrier_iterative_refinement, fresh.barrier_iterative_refinement); EXPECT_EQ(after.barrier_dual_initial_point, fresh.barrier_dual_initial_point); EXPECT_DOUBLE_EQ(after.barrier_step_scale, fresh.barrier_step_scale); // Enum-int32 fields (post-decode clamping defends out-of-range; default `0` diff --git a/cpp/tests/socp/solve_barrier_socp.cu b/cpp/tests/socp/solve_barrier_socp.cu index 68e2cb2d31..291f8576ea 100644 --- a/cpp/tests/socp/solve_barrier_socp.cu +++ b/cpp/tests/socp/solve_barrier_socp.cu @@ -854,11 +854,10 @@ TEST(barrier, sparse_soc_expansion_solves_large_single_cone) user_problem.var_types.assign(n, variable_type_t::CONTINUOUS); simplex_solver_settings_t settings; - settings.barrier = true; - settings.barrier_presolve = true; - settings.dualize = 0; - settings.barrier_soc_threshold = 4; - settings.barrier_iterative_refinement = true; + settings.barrier = true; + settings.barrier_presolve = true; + settings.dualize = 0; + settings.barrier_soc_threshold = 4; lp_solution_t solution(m, n); auto status = solve_linear_program_with_barrier(user_problem, settings, solution); @@ -932,12 +931,11 @@ TEST(barrier, mixed_dense_and_sparse_soc_blocks) user_problem.var_types.assign(n, variable_type_t::CONTINUOUS); simplex_solver_settings_t settings; - settings.barrier = true; - settings.barrier_presolve = true; - settings.dualize = 0; - settings.scale_columns = true; - settings.barrier_soc_threshold = 4; - settings.barrier_iterative_refinement = true; + settings.barrier = true; + settings.barrier_presolve = true; + settings.dualize = 0; + settings.scale_columns = true; + settings.barrier_soc_threshold = 4; lp_solution_t solution(m, n); auto status = solve_linear_program_with_barrier(user_problem, settings, solution); @@ -1001,11 +999,10 @@ TEST(barrier, sparse_soc_expansion_solves_dim_500_cone) user_problem.var_types.assign(n, variable_type_t::CONTINUOUS); simplex_solver_settings_t settings; - settings.barrier = true; - settings.barrier_presolve = true; - settings.dualize = 0; - settings.barrier_soc_threshold = 5; - settings.barrier_iterative_refinement = true; + settings.barrier = true; + settings.barrier_presolve = true; + settings.dualize = 0; + settings.barrier_soc_threshold = 5; lp_solution_t solution(m, n); auto status = solve_linear_program_with_barrier(user_problem, settings, solution); diff --git a/docs/cuopt/source/convex-settings.rst b/docs/cuopt/source/convex-settings.rst index 7bff025e5d..6b7d237539 100644 --- a/docs/cuopt/source/convex-settings.rst +++ b/docs/cuopt/source/convex-settings.rst @@ -284,6 +284,16 @@ cuDSS Deterministic Mode .. note:: The default value is ``false``. Enable deterministic mode if reproducibility is more important than performance. +cuDSS Nested-Dissection Levels +""""""""""""""""""""""""""""""" + +``CUOPT_CUDSS_HYPER_ND_NLEVELS`` controls the METIS nested-dissection depth used by cuDSS during reordering. + +* ``-1``: Leave unset, cuDSS chooses (default) +* Non-negative value: Explicit nested-dissection depth + +.. note:: The default value is ``-1`` (unset). + Dual Initial Point """""""""""""""""" @@ -373,12 +383,13 @@ The duality gap is computed as follows:: Barrier Iterative Refinement ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -``CUOPT_BARRIER_ITERATIVE_REFINEMENT`` controls whether iterative refinement is applied after each barrier iteration to improve solution accuracy. +``CUOPT_BARRIER_ITERATIVE_REFINEMENT`` controls whether, and how, iterative refinement is applied after each barrier iteration to improve solution accuracy. -* ``0`` (``CUOPT_BARRIER_ITERATIVE_REFINEMENT_OFF``): Disable iterative refinement (default). -* ``1`` (``CUOPT_BARRIER_ITERATIVE_REFINEMENT_ON``): Enable iterative refinement. +* ``0`` (``CUOPT_BARRIER_IR_OFF``): Disable iterative refinement. +* ``1`` (``CUOPT_BARRIER_IR_GMRES``): Restarted GMRES (default). +* ``2`` (``CUOPT_BARRIER_IR_FIXED_POINT``): Fixed-point residual-correction. -.. note:: The default value is ``0`` (off). +.. note:: The default value is ``1`` (GMRES). Barrier Step Scale ^^^^^^^^^^^^^^^^^^^ diff --git a/docs/cuopt/source/cuopt-c/convex/convex-c-api.rst b/docs/cuopt/source/cuopt-c/convex/convex-c-api.rst index 31a4516ca0..630374922a 100644 --- a/docs/cuopt/source/cuopt-c/convex/convex-c-api.rst +++ b/docs/cuopt/source/cuopt-c/convex/convex-c-api.rst @@ -258,8 +258,9 @@ Barrier Iterative Refinement Constants These constants are used to configure `CUOPT_BARRIER_ITERATIVE_REFINEMENT` via :c:func:`cuOptSetIntegerParameter`. -.. doxygendefine:: CUOPT_BARRIER_ITERATIVE_REFINEMENT_OFF -.. doxygendefine:: CUOPT_BARRIER_ITERATIVE_REFINEMENT_ON +.. doxygendefine:: CUOPT_BARRIER_IR_OFF +.. doxygendefine:: CUOPT_BARRIER_IR_GMRES +.. doxygendefine:: CUOPT_BARRIER_IR_FIXED_POINT Warm Start diff --git a/python/cuopt_server/cuopt_server/tests/test_lp.py b/python/cuopt_server/cuopt_server/tests/test_lp.py index e3a683f8de..3febecc2f9 100644 --- a/python/cuopt_server/cuopt_server/tests/test_lp.py +++ b/python/cuopt_server/cuopt_server/tests/test_lp.py @@ -142,22 +142,22 @@ def test_sample_milp( ) @pytest.mark.parametrize( "folding, dualize, ordering, augmented, eliminate_dense, cudss_determ, " - "dual_initial_point", + "dual_initial_point, cudss_nd_nlevels, barrier_ir_method", [ # Test automatic settings (default) - (-1, -1, -1, -1, True, False, -1), + (-1, -1, -1, -1, True, False, -1, -1, 1), # Test folding off, no dualization, cuDSS default ordering, ADAT system - (0, 0, 0, 0, True, False, 0), + (0, 0, 0, 0, True, False, 0, -1, 1), # Test folding on, force dualization, AMD ordering, augmented system - (1, 1, 1, 1, True, True, 1), + (1, 1, 1, 1, True, True, 1, 8, 0), # Test mixed settings: automatic folding, no dualize, AMD, augmented - (-1, 0, 1, 1, False, False, 0), + (-1, 0, 1, 1, False, False, 0, 4, 0), # Test no folding, automatic dualize, cuDSS default, ADAT - (0, -1, 0, 0, True, True, -1), + (0, -1, 0, 0, True, True, -1, -1, 1), # Test dual initial point with Lustig-Marsten-Shanno - (-1, -1, -1, -1, True, False, 0), + (-1, -1, -1, -1, True, False, 0, -1, 1), # Test dual initial point with least squares - (-1, -1, -1, 1, True, False, 1), + (-1, -1, -1, 1, True, False, 1, -1, 0), ], ) def test_barrier_solver_options( @@ -169,6 +169,8 @@ def test_barrier_solver_options( eliminate_dense, cudss_determ, dual_initial_point, + cudss_nd_nlevels, + barrier_ir_method, ): """ Test the barrier solver (method=3) with various configuration options: @@ -181,6 +183,10 @@ def test_barrier_solver_options( nondeterministic - barrier_dual_initial_point: (-1) automatic, (0) Lustig-Marsten-Shanno, (1) dual least squares + - cudss_nd_nlevels: (-1) unset/automatic, else METIS nested-dissection + depth + - barrier_ir_method: (0) off, (1) restarted GMRES (default), (2) + fixed-point residual-correction """ data = get_std_data_for_lp() @@ -195,6 +201,8 @@ def test_barrier_solver_options( data["solver_config"]["eliminate_dense_columns"] = eliminate_dense data["solver_config"]["cudss_deterministic"] = cudss_determ data["solver_config"]["barrier_dual_initial_point"] = dual_initial_point + data["solver_config"]["cudss_nd_nlevels"] = cudss_nd_nlevels + data["solver_config"]["barrier_iterative_refinement"] = barrier_ir_method res = get_lp(client, data) @@ -205,6 +213,10 @@ def test_barrier_solver_options( print(f"augmented={augmented}, eliminate_dense={eliminate_dense}") print(f"cudss_deterministic={cudss_determ}") print(f"barrier_dual_initial_point={dual_initial_point}") + print( + f"cudss_nd_nlevels={cudss_nd_nlevels}, " + f"barrier_ir_method={barrier_ir_method}" + ) print(res.json()) validate_lp_result( diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py index 6cd8f7828a..ea57b58d6d 100644 --- a/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py +++ b/python/cuopt_server/cuopt_server/utils/linear_programming/data_definition.py @@ -514,6 +514,18 @@ class SolverConfig(BaseModel): description="Set if cuDSS should use deterministic mode. " "True to use deterministic mode, False to not use deterministic mode", ) + cudss_nd_nlevels: Optional[int] = Field( + default=-1, + description="Set the METIS nested-dissection depth used by cuDSS. " + "-1 to leave it unset (cuDSS default), or a non-negative depth", + ) + barrier_iterative_refinement: Optional[int] = Field( + default=1, + description="Set whether, and how, the barrier solver applies " + "iterative refinement after each solve. 0 to disable, 1 for " + "restarted GMRES (default), 2 for a fixed-point residual-correction " + "loop", + ) crossover: Optional[bool] = Field( default=False, description="Set True to use crossover, False to not use crossover.",