Skip to content
Merged

RENS #1719

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d1564fa
allow RINS and CPU FJ to run during the cut passes. fixed objective f…
nguidotti Aug 10, 2026
e0bc53b
silent postsolve messages for submip
nguidotti Aug 11, 2026
ea154a9
address coderabbit comments
nguidotti Aug 11, 2026
d4a8bd5
address more review comments
nguidotti Aug 11, 2026
9ed4d28
code cleanup
nguidotti Aug 12, 2026
7580888
wrap debug submip debug logs in a macro
nguidotti Aug 12, 2026
a8e7912
replaced oldest workers when all threads were exhausted. stop all roo…
nguidotti Aug 12, 2026
6f568c1
stop root heuristics before strong branching
nguidotti Aug 13, 2026
f41eca5
addressed a few of the reviewer comments
nguidotti Aug 19, 2026
fd3e327
do not limit the work unit in the CPU FJ during the cut passes. repla…
nguidotti Aug 19, 2026
1c79fdf
replaced halt mechanism for the sub-MIP
nguidotti Aug 19, 2026
6fec971
fixed inverted logic
nguidotti Aug 20, 2026
f318fba
fixed race conditions. added more halt checks.
nguidotti Aug 20, 2026
d7ba47b
code cleanup
nguidotti Aug 21, 2026
e7d2ba0
revert changes to the halt mechanism for sub-MIP (it was causing cras…
nguidotti Aug 21, 2026
c3a7b29
added missing halt after revert
nguidotti Aug 21, 2026
2dd6573
increase time limit for the node limit test
nguidotti Aug 21, 2026
66dc954
use shared_ptr to tie the lifetime of the object to the task. this al…
nguidotti Aug 22, 2026
83c72a7
re-introduced the is_root_heuristic flag to prevent crashes during th…
nguidotti Aug 24, 2026
31b3b87
first implementation of the RENS heuristic
nguidotti Aug 11, 2026
357295e
increase the number of simplex iterations allowed at the first sub-MIP
nguidotti Aug 12, 2026
b5d0f97
track success/failure separately for RINS and RENS
nguidotti Aug 14, 2026
339b3c5
shuffle the integer and fractional list to give an equal chance for a…
nguidotti Aug 14, 2026
7983659
modified RENS to construct the neighbourhood over multiple rounds.
nguidotti Aug 17, 2026
880ece4
propagate simplex iteration count to submip solve
nguidotti Aug 17, 2026
d790c5c
fixed stale fixrate. added more debug info.
nguidotti Aug 17, 2026
fff48b2
calculate the fixrate after bound propagation. better and more debug …
nguidotti Aug 18, 2026
3c70bb3
fixed incorrect trigger for extension. refine progress tracking durin…
nguidotti Aug 18, 2026
006395a
fixed default value mismatch. fixed some minor bugs.
nguidotti Aug 19, 2026
bbb48e5
Merge remote-tracking branch 'cuopt/main' into rens
nguidotti Aug 25, 2026
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
19 changes: 11 additions & 8 deletions cpp/include/cuopt/mathematical_optimization/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
#define CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS "mip_strong_chvatal_gomory_cuts"
#define CUOPT_MIP_REDUCED_COST_STRENGTHENING "mip_reduced_cost_strengthening"
#define CUOPT_MIP_RINS "mip_rins"
#define CUOPT_MIP_RENS "mip_rens"
#define CUOPT_MIP_OBJECTIVE_STEP "mip_objective_step"
#define CUOPT_MIP_CUT_CHANGE_THRESHOLD "mip_cut_change_threshold"
#define CUOPT_MIP_CUT_MIN_ORTHOGONALITY "mip_cut_min_orthogonality"
Expand Down Expand Up @@ -137,14 +138,16 @@
#define CUOPT_MIP_HYPER_DIVING_SHOW_TYPE "mip_hyper_diving_show_type"

/* @brief Recursive sub-MIP (RINS) hyper-parameters */
#define CUOPT_MIP_HYPER_SUBMIP_BASE_TARGET_FIXRATE "mip_hyper_submip_base_target_fixrate"
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE "mip_hyper_submip_min_fixrate"
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP "mip_hyper_submip_min_fixrate_cap"
#define CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP "mip_hyper_submip_target_mip_gap"
#define CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_BASE "mip_hyper_submip_node_limit_base"
#define CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL "mip_hyper_submip_max_level"
#define CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO "mip_hyper_submip_iteration_limit_ratio"
#define CUOPT_MIP_HYPER_SUBMIP_ENABLE_CPUFJ "mip_hyper_submip_enable_cpufj"
#define CUOPT_MIP_HYPER_SUBMIP_BASE_TARGET_FIXRATE "mip_hyper_submip_base_target_fixrate"
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE "mip_hyper_submip_min_fixrate"
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP "mip_hyper_submip_min_fixrate_cap"
#define CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP "mip_hyper_submip_target_mip_gap"
#define CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_OFFSET "mip_hyper_submip_node_limit_offset"
#define CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_OFFSET "mip_hyper_submip_iteration_limit_offset"
#define CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL "mip_hyper_submip_max_level"
#define CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO "mip_hyper_submip_iteration_limit_ratio"
#define CUOPT_MIP_HYPER_SUBMIP_ROUND_CLOSE_RATIO "mip_hyper_submip_round_close_ratio"
#define CUOPT_MIP_HYPER_SUBMIP_ENABLE_CPUFJ "mip_hyper_submip_enable_cpufj"

/* @brief Block bounded-variable-elimination step of cuOpt's internal MIP presolve */
#define CUOPT_MIP_HYPER_BLOCK_BVE "mip_hyper_block_bve"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
*/
template <typename i_t, typename f_t>
struct mip_submip_hyper_params_t {
// Enable or disable (recursive) RINS: -1 automatic, 0 disabled, 1 enabled
// Enable or disable (recursive) RINS/RENS: -1 automatic, 0 disabled, 1 enabled
i_t rins = -1;
i_t rens = -1;

// Base for calculating the target fix rate for the neighbourhood. Actual target value is
// determined automatically according to the success and infeasible rate.
Expand All @@ -29,7 +30,10 @@ struct mip_submip_hyper_params_t {
f_t target_mip_gap = 0.01;

// The base node limit for the sub-MIP
i_t node_limit_base = 200;
i_t node_limit_offset = 200;

// The base iteration limit for the sub-MIP
i_t iteration_limit_offset = 10000;

// The current level in the recursion. This is an internal parameter and will set automatically.
i_t level = 0;
Expand All @@ -46,6 +50,10 @@ struct mip_submip_hyper_params_t {
// the maximum number of nodes allow for backtracking.
i_t dfs_max_backtrack = 5;

// How many variables a single round can fix. Set in terms of ratio of
// (1 - current fixrate).
f_t round_close_ratio = 0.8;

// Run CPU FJ over the sub-MIP
bool enable_cpufj = true;
};
605 changes: 368 additions & 237 deletions cpp/src/branch_and_bound/branch_and_bound.cpp

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions cpp/src/branch_and_bound/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,9 @@ class branch_and_bound_t {
diving_worker_pool_t<i_t, f_t> diving_worker_pool_;

// Worker pool dedicated to recursive RINS
diving_worker_pool_t<i_t, f_t> rins_worker_pool_;
diving_worker_pool_t<i_t, f_t> submip_worker_pool_;
submip_stats_t rins_stats_;
submip_stats_t rens_stats_;

// Global status of the solver.
omp_atomic_t<mip_status_t> solver_status_;
Expand Down Expand Up @@ -369,27 +370,28 @@ class branch_and_bound_t {
void dive_with(diving_worker_t<i_t, f_t>* worker, i_t backtrack_limit);

// Launch a new RINS worker
bool launch_rins_worker(const std::vector<f_t>& sol);
bool launch_submip_worker(const std::vector<f_t>& sol);
void set_solution_from_submip(const simplex::lp_problem_t<i_t, f_t>& lp,
const std::vector<f_t>& solution,
const third_party_presolve_t<i_t, f_t>& presolver,
f_t fixrate);
submip_stats_t& submip_stats,
f_t fixrate,
std::string_view log_prefix);

// Solve the RINS sub-MIP
void solve_submip(diving_worker_t<i_t, f_t>* worker,
const std::vector<f_t>& current_incumbent,
const std::vector<simplex::variable_type_t>& var_types,
i_t num_var_fixed,
i_t num_integers,
i_t submip_level,
std::string_view log_prefix,
submip_stats_t& submip_stats,
f_t fixrate,
i_t simplex_iter_used,
bool is_root_heuristic = false);

// Creates and solves the RINS sub-MIP
void rins(diving_worker_t<i_t, f_t>* worker,
const std::vector<f_t>& current_incumbent,
const std::vector<simplex::variable_type_t>& var_types,
bool is_root_heuristic = false);
void recursive_submip(diving_worker_t<i_t, f_t>* worker,
const std::vector<f_t>& current_incumbent,
const std::vector<simplex::variable_type_t>& var_types,
bool is_root_heuristic = false);

void launch_root_heuristics(const simplex::lp_problem_t<i_t, f_t>& lp,
const std::vector<f_t>& sol,
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/branch_and_bound/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ enum class heuristics_origin_t {
// [3] E. Danna, E. Rothberg, and C. L. Pape, “Exploring relaxation induced neighborhoods to
// improve MIP solutions,” Math. Program., vol. 102, no. 1, pp. 71–90, Jan. 2005,
// doi: 10.1007/s10107-004-0518-7.
// [4] T. Berthold, “RENS: The optimal rounding,” Math. Prog. Comp., vol. 6, no. 1,
// pp. 33–54, Mar. 2014, doi: 10.1007/s12532-013-0060-9.
enum class search_strategy_t : int {
BEST_FIRST = 0, // Best-First + Plunging.
PSEUDOCOST_DIVING = 1, // Pseudocost diving [1, Section 9.2.5]
Expand All @@ -34,6 +36,7 @@ enum class search_strategy_t : int {
FARKAS_DIVING = 5, // Farkas Diving (see [2])
VECTOR_LENGTH_DIVING = 6, // Vector Length Diving [1, Section 9.2.6]
RINS = 7, // RINS (see [3])
RENS = 8 // RENS (see [1, Section 9.1.1], [4])
};

enum class branch_direction_t { NONE = -1, DOWN = 0, UP = 1 };
Expand All @@ -49,6 +52,7 @@ inline const char* search_strategy_to_string(search_strategy_t search_strategy)
case search_strategy_t::FARKAS_DIVING: return "FARKAS_DIVING";
case search_strategy_t::VECTOR_LENGTH_DIVING: return "VECTOR_LENGTH_DIVING";
case search_strategy_t::RINS: return "RINS";
case search_strategy_t::RENS: return "RENS";
}

return "UNKNOWN";
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/branch_and_bound/worker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ struct submip_stats_t {
omp_atomic_t<int> total_infeasible = 0;
omp_atomic_t<double> infeasible_fixrate_sum = 0;
omp_atomic_t<int> total_calls = 0;
omp_atomic_t<int> total_empty = 0;

void save_success(double fixrate)
{
Expand All @@ -276,6 +277,7 @@ struct submip_stats_t {
infeasible_fixrate_sum += fixrate;
}

void save_empty() { ++total_empty; }
double average_infeasible_fixrate() const { return infeasible_fixrate_sum / total_infeasible; }
double average_success_fixrate() const { return success_fixrate_sum / total_success; }
};
Expand Down
5 changes: 4 additions & 1 deletion cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP, &mip_settings.submip_params.min_fixrate_cap, f_t(0.0), f_t(1.0), f_t(0.1), "hard cap on the minimum fix rate for solving a sub-MIP"},
{CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP, &mip_settings.submip_params.target_mip_gap, f_t(0.0), f_t(1.0), f_t(0.01), "MIP gap target for the sub-MIP"},
{CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO, &mip_settings.submip_params.iteration_limit_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "sub-MIP simplex-iteration limit as a factor of parent B&B iterations"},
{CUOPT_MIP_HYPER_SUBMIP_ROUND_CLOSE_RATIO, &mip_settings.submip_params.round_close_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "share of the still-unfixed integers left for later neighbourhood rounds (0 reaches the target fix rate in a single round)"},
};

// Int parameters
Expand Down Expand Up @@ -149,6 +150,7 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS, &mip_settings.strong_chvatal_gomory_cuts, -1, 1, -1},
{CUOPT_MIP_REDUCED_COST_STRENGTHENING, &mip_settings.reduced_cost_strengthening, -1, std::numeric_limits<i_t>::max(), -1},
{CUOPT_MIP_RINS, &mip_settings.submip_params.rins, -1, 1, -1},
{CUOPT_MIP_RENS, &mip_settings.submip_params.rens, -1, 1, -1},
{CUOPT_MIP_OBJECTIVE_STEP, &mip_settings.objective_step, 0, 1, 1},
{CUOPT_NUM_GPUS, &pdlp_settings.num_gpus, -1, 72, 1},
{CUOPT_NUM_GPUS, &mip_settings.num_gpus, -1, 72, 1},
Expand Down Expand Up @@ -185,7 +187,8 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_HYPER_DIVING_NODE_LIMIT, &mip_settings.diving_params.node_limit, 0, std::numeric_limits<i_t>::max(), 500, "maximum nodes explored per dive"},
{CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT, &mip_settings.diving_params.backtrack_limit, 0, std::numeric_limits<int16_t>::max(), 5, "maximum backtracking allowed per dive"},
// Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_")
{CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_BASE, &mip_settings.submip_params.node_limit_base, 0, std::numeric_limits<i_t>::max(), 200, "base node limit for the sub-MIP"},
{CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_OFFSET, &mip_settings.submip_params.node_limit_offset, 0, std::numeric_limits<i_t>::max(), 200, "base node limit for the sub-MIP"},
{CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_OFFSET, &mip_settings.submip_params.iteration_limit_offset, 0, std::numeric_limits<i_t>::max(), 10000, "base sub-MIP simplex-iteration limit for root heuristics"},
{CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL, &mip_settings.submip_params.max_level, 0, std::numeric_limits<i_t>::max(), 10, "maximum sub-MIP recursion level"},
{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
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class sub_mip_recombiner_t : public recombiner_t<i_t, f_t> {
branch_and_bound_settings.zero_half_cuts = 0;
branch_and_bound_settings.inside_submip = 1;
branch_and_bound_settings.submip_settings.rins = 0;
branch_and_bound_settings.submip_settings.rens = 0;
branch_and_bound_settings.strong_branching_simplex_iteration_limit = 200;
branch_and_bound_settings.solution_callback = [this](std::vector<f_t>& solution,
f_t objective) {
Expand Down
5 changes: 3 additions & 2 deletions cpp/src/mip_heuristics/root_heuristics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ struct cut_pass_heuristics_t {
const simplex::simplex_solver_settings_t<i_t, f_t>& settings,
f_t root_obj,
const std::vector<simplex::variable_status_t>& root_vstatus,
const std::vector<f_t>& sol)
const std::vector<f_t>& sol,
search_strategy_t type)
{
submip_worker_ = std::make_unique<diving_worker_t<i_t, f_t>>(
id, lp, Arow_, var_types_, settings, root_solution_, root_edge_norm_);
Expand All @@ -67,7 +68,7 @@ struct cut_pass_heuristics_t {
submip_worker_->leaf_solution.x = sol;
submip_worker_->recompute_bounds = false;
submip_worker_->recompute_basis = true;
submip_worker_->search_strategy = search_strategy_t::RINS;
submip_worker_->search_strategy = type;
submip_worker_->set_active();

return submip_worker_.get();
Expand Down