Skip to content

Commit e4e1aad

Browse files
authored
Recursive RINS (#1482)
This PR implements the recursive RINS heuristics. More specifically, it applies a dive-like procedure to create a RINS sub-MIP, presolved it with Papilo and then passes to the cut-and-branch procedure. It also launch a CPU FJ worker over the sub-MIP that runs in parallel to cut-and-branch. Since the current implementation, does not have an unified problem representation, this PR also adds: - A routine for converting a `lp_problem_t` (standard form) to `user_problem_t` (range form). - Allow Papilo to be applied to an `user_problem_t` instead of `optimization_problem_t`. - Adds an upper bound callback for retrieving the upper bound of the main solver (since it may change if another worker find a better incumbent). Additionally, the number of worker per diving heuristic is no longer tied to the total number of workers, which allows the solver to use all diving heuristics in low thread counts (we rotate between the different heuristics for each diving worker launch). ## Results MIPLIB2017, 10min, GH200 ``` ================================================================================ main-2026-07-03 (1) vs recursive-submip (2) ================================================================================ ------------------------------------------------------------------------------------------------------------------------------ | | Run 1 | Run 2 | Abs. Diff. | Rel. Diff. (%) | ------------------------------------------------------------------------------------------------------------------------------ | Imported 240 240 +0 --- | | Feasible 227 228 +1 --- | | Optimal 86 91 +5 --- | | Solutions with <0.1% primal gap 138 151 +13 --- | | Nodes explored (mean) 1.326e+07 1.296e+07 -3.057e+05 -2.31 | | Nodes explored (shifted geomean) 1.262e+04 1.239e+04 -230 -1.82 | | Relative MIP gap (mean) 0.2777 0.2726 -0.00512 -1.84 | | Relative MIP gap (shifted geomean) 0.09089 0.08901 -0.001883 -2.07 | | Solve time (mean) 422.6 435.4 +12.79 +3.03 | | Solve time (shifted geomean) 196.9 226.6 +29.66 +15.1 | | Primal gap (mean) 9.969 9.29 -0.679 -6.81 | | Primal gap (shifted geomean) 0.4584 0.3491 -0.1093 -23.8 | | Primal integral (mean) 25.02 22.97 -2.051 -8.2 | | Primal integral (shifted geomean) 2.849 2.421 -0.4275 -15 | ------------------------------------------------------------------------------------------------------------------------------ ---------------------------------------------------------------------- | Name | status 1 | status 2 | ---------------------------------------------------------------------- | neos-1171737 feasible optimal | | radiationm40-10-02 feasible optimal | | rail01 timeout feasible | | ran14x18-disj-8 feasible optimal | | triptim1 feasible optimal | | unitcal_7 feasible optimal | ---------------------------------------------------------------------- ``` Authors: - Nicolas L. Guidotti (https://github.com/nguidotti) Approvers: - Alice Boucher (https://github.com/aliceb-nv) - Chris Maes (https://github.com/chris-maes) - Ramakrishna Prabhu (https://github.com/ramakrishnap-nv) URL: #1482
1 parent 37a7b63 commit e4e1aad

38 files changed

Lines changed: 2099 additions & 978 deletions

cpp/CMakeLists.txt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,14 @@ endif ()
252252

253253
FetchContent_Declare(
254254
papilo
255-
GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
255+
GIT_REPOSITORY "https://github.com/nguidotti/papilo.git"
256256
# We would want to get the main branch. However, the main branch
257257
# does not have some of the presolvers and settings that we need
258258
# Mainly, probing and clique merging.
259259
# This is the reason we are using the development branch
260260
# from Oct 12, 2025. Once these changes are merged into the main branch,
261261
#we can switch to the main branch.
262-
GIT_TAG "741a2b9c8155b249d6df574d758b4d97d4417520"
262+
GIT_TAG "0cfea20c5655249174dbd04f0fb7bd6b1f6e9a0c"
263263
GIT_PROGRESS TRUE
264264
EXCLUDE_FROM_ALL
265265
SYSTEM
@@ -293,13 +293,13 @@ set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVED})
293293
# dejavu - header-only graph automorphism library for MIP symmetry detection
294294
# https://github.com/markusa4/dejavu (header-only, skip its CMakeLists.txt)
295295
FetchContent_Declare(
296-
dejavu
297-
GIT_REPOSITORY "https://github.com/markusa4/dejavu.git"
298-
GIT_TAG "v2.1"
299-
GIT_PROGRESS TRUE
300-
EXCLUDE_FROM_ALL
301-
SYSTEM
302-
SOURCE_SUBDIR _nonexistent
296+
dejavu
297+
GIT_REPOSITORY "https://github.com/markusa4/dejavu.git"
298+
GIT_TAG "v2.1"
299+
GIT_PROGRESS TRUE
300+
EXCLUDE_FROM_ALL
301+
SYSTEM
302+
SOURCE_SUBDIR _nonexistent
303303
)
304304
FetchContent_MakeAvailable(dejavu)
305305
message(STATUS "dejavu (graph automorphism): ${dejavu_SOURCE_DIR}")
@@ -369,7 +369,7 @@ if (NOT SKIP_GRPC_BUILD)
369369

370370
# Proto search paths: manual protos in src/grpc, generated data proto in src/grpc/codegen/generated
371371
set(PROTO_PATH_MANUAL "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc")
372-
set(PROTO_PATH_GEN "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated")
372+
set(PROTO_PATH_GEN "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated")
373373

374374
# Generate C++ code from cuopt_remote_data.proto (auto-generated data definitions)
375375
set(DATA_PROTO_FILE "${PROTO_PATH_GEN}/cuopt_remote_data.proto")
@@ -441,8 +441,8 @@ add_subdirectory(src)
441441
# nvcc 13.0.3 ICE (signal 11) compiling sliding_window.cu with 7 GPU architectures;
442442
# --split-compile breaks the codegen into per-arch sub-jobs to avoid the crash
443443
set_source_files_properties(
444-
${CMAKE_CURRENT_SOURCE_DIR}/src/routing/local_search/sliding_window.cu
445-
PROPERTIES COMPILE_OPTIONS "--split-compile=0")
444+
${CMAKE_CURRENT_SOURCE_DIR}/src/routing/local_search/sliding_window.cu
445+
PROPERTIES COMPILE_OPTIONS "--split-compile=0")
446446

447447
if (HOST_LINEINFO)
448448
set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1")
@@ -495,8 +495,8 @@ set_target_properties(cuopt
495495
)
496496

497497
target_compile_definitions(cuopt
498-
PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
499-
PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API
498+
PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
499+
PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API
500500
)
501501

502502
target_compile_options(cuopt
@@ -528,8 +528,8 @@ target_include_directories(cuopt PRIVATE
528528
)
529529

530530
target_include_directories(cuopt SYSTEM PRIVATE
531-
"${pslp_SOURCE_DIR}/include"
532-
"${dejavu_SOURCE_DIR}"
531+
"${pslp_SOURCE_DIR}/include"
532+
"${dejavu_SOURCE_DIR}"
533533
)
534534

535535
target_include_directories(cuopt

cpp/include/cuopt/mathematical_optimization/constants.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
#define CUOPT_MIP_ZERO_HALF_CUTS "mip_zero_half_cuts"
7676
#define CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS "mip_strong_chvatal_gomory_cuts"
7777
#define CUOPT_MIP_REDUCED_COST_STRENGTHENING "mip_reduced_cost_strengthening"
78+
#define CUOPT_MIP_RINS "mip_rins"
7879
#define CUOPT_MIP_OBJECTIVE_STEP "mip_objective_step"
7980
#define CUOPT_MIP_CUT_CHANGE_THRESHOLD "mip_cut_change_threshold"
8081
#define CUOPT_MIP_CUT_MIN_ORTHOGONALITY "mip_cut_min_orthogonality"
@@ -127,9 +128,19 @@
127128
#define CUOPT_MIP_HYPER_DIVING_NODE_LIMIT "mip_hyper_diving_node_limit"
128129
#define CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR "mip_hyper_diving_iteration_limit_factor"
129130
#define CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT "mip_hyper_diving_backtrack_limit"
130-
/* @brief Show per-strategy diving symbol in logs (true) instead of a generic 'D' */
131+
/* @brief Show per-strategy diving symbol in logs instead of a generic 'D' */
131132
#define CUOPT_MIP_HYPER_DIVING_SHOW_TYPE "mip_hyper_diving_show_type"
132133

134+
/* @brief Recursive sub-MIP (RINS) hyper-parameters */
135+
#define CUOPT_MIP_HYPER_SUBMIP_BASE_TARGET_FIXRATE "mip_hyper_submip_base_target_fixrate"
136+
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE "mip_hyper_submip_min_fixrate"
137+
#define CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP "mip_hyper_submip_min_fixrate_cap"
138+
#define CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP "mip_hyper_submip_target_mip_gap"
139+
#define CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_BASE "mip_hyper_submip_node_limit_base"
140+
#define CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL "mip_hyper_submip_max_level"
141+
#define CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO "mip_hyper_submip_iteration_limit_ratio"
142+
#define CUOPT_MIP_HYPER_SUBMIP_ENABLE_CPUFJ "mip_hyper_submip_enable_cpufj"
143+
133144
/* @brief MIP determinism mode constants */
134145
#define CUOPT_MODE_OPPORTUNISTIC 0
135146
#define CUOPT_MODE_DETERMINISTIC 1

cpp/include/cuopt/mathematical_optimization/mip/diving_hyper_params.hpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@
1010
namespace cuopt::mathematical_optimization {
1111

1212
/**
13-
* @brief Tuning knobs for the dual-simplex diving heuristics used in MIP B&B.
14-
*
15-
* Used directly as simplex_solver_settings_t::diving_settings and copied into
16-
* branch_and_bound_settings.diving_settings before solve. These are registered
17-
* in the unified parameter framework via solver_settings_t and can be loaded
18-
* from a config file with load_parameters_from_file().
13+
* @brief Tuning knobs for the diving heuristics for the MIP solver.
1914
*/
2015
template <typename i_t, typename f_t>
2116
struct mip_diving_hyper_params_t {

cpp/include/cuopt/mathematical_optimization/mip/solver_settings.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <cuopt/mathematical_optimization/constants.h>
1414
#include <cuopt/mathematical_optimization/mip/diving_hyper_params.hpp>
1515
#include <cuopt/mathematical_optimization/mip/heuristics_hyper_params.hpp>
16+
#include <cuopt/mathematical_optimization/mip/submip_hyper_params.hpp>
1617
#include <cuopt/mathematical_optimization/pdlp/pdlp_hyper_params.cuh>
1718
#include <cuopt/mathematical_optimization/utilities/internals.hpp>
1819

@@ -184,8 +185,8 @@ class mip_solver_settings_t {
184185
pdlp::pdlp_hyper_params_t hyper_params;
185186

186187
mip_heuristics_hyper_params_t<i_t, f_t> heuristic_params;
187-
188188
mip_diving_hyper_params_t<i_t, f_t> diving_params;
189+
mip_submip_hyper_params_t<i_t, f_t> submip_params;
189190

190191
private:
191192
std::vector<internals::base_solution_callback_t*> mip_callbacks_;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* clang-format off */
2+
/*
3+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
/* clang-format on */
7+
8+
#pragma once
9+
10+
/**
11+
* @brief Tuning knobs for the recursive sub-MIP.
12+
*/
13+
template <typename i_t, typename f_t>
14+
struct mip_submip_hyper_params_t {
15+
// Enable or disable (recursive) RINS: -1 automatic, 0 disabled, 1 enabled
16+
i_t rins = -1;
17+
18+
// Base for calculating the target fix rate for the neighbourhood. Actual target value is
19+
// determined automatically according to the success and infeasible rate.
20+
f_t base_target_fixrate = 0.6;
21+
22+
// Minimum fix rate for accepting the neighbourhood.
23+
f_t min_fixrate = 0.25;
24+
25+
// Hard cap for the minimum fix rate for solving a sub-MIP.
26+
f_t min_fixrate_cap = 0.1;
27+
28+
// MIP gap for the sub-MIP (unless the MIP gap from the B&B is lower)
29+
f_t target_mip_gap = 0.01;
30+
31+
// The base node limit for the sub-MIP
32+
i_t node_limit_base = 200;
33+
34+
// The current level in the recursion. This is an internal parameter and will set automatically.
35+
i_t level = 0;
36+
37+
// Maximum recursion level
38+
i_t max_level = 10;
39+
40+
// Limit the number of simplex iterations spent in the submip. Set as a factor of the total
41+
// number of simplex iteration from the parent B&B.
42+
f_t iteration_limit_ratio = 0.8;
43+
44+
// Run CPU FJ over the sub-MIP
45+
bool enable_cpufj = true;
46+
};

0 commit comments

Comments
 (0)