Skip to content

Recursive RINS#1482

Open
nguidotti wants to merge 70 commits into
NVIDIA:mainfrom
nguidotti:recursive-submip
Open

Recursive RINS#1482
nguidotti wants to merge 70 commits into
NVIDIA:mainfrom
nguidotti:recursive-submip

Conversation

@nguidotti

@nguidotti nguidotti commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

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 |
----------------------------------------------------------------------

@nguidotti nguidotti added this to the 26.08 milestone Jun 26, 2026
@nguidotti nguidotti added non-breaking Introduces a non-breaking change improvement Improves an existing functionality mip labels Jun 26, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
nguidotti added 23 commits June 29, 2026 12:56
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…bmip (and restarted) problems. Papilo can now be applied to an user_problem_t inplace.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
# Conflicts:
#	cpp/src/mip_heuristics/diversity/lns/rins.cu
#	cpp/src/mip_heuristics/diversity/recombiners/sub_mip.cuh
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…e old warm start code.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…/root change diving procedure when the RINS neighbourhood is not large enough

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…ation count for the root relaxation (it should reflect the number of simplex iterations).

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…. this enable the use of diving heuristics for RINS.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…f the neighbourhood is too loose or it already found an improving solution.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
worker->leaf_problem, var_types_, settings_, submip_problem);

third_party_presolve_t<i_t, f_t> presolver;
f_t presolve_time_limit = std::min(0.1 * submip_settings.time_limit, 60.0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: presolve based on time limit introduce non-determinism into the solve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think Papilo supports or uses work limit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we tested if Papilo needs this scaled-down time limit here? On a restarted problem it may be able to reach the fixed point quickly. If not, then the expensive Probing pass has a separate work unit that can be tuned - I assume it's also possible to set a limit on the number of rounds.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copy the settings from the regular presolve pass. Honestly, I would not spend too much effort for this since I hope we can replace the presolve + cut-and-branch for the submip for a call to the entire solver stack (with maybe some parts turn off) when we have the unified problem object

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not introduce sources of nondeterminism in new PRs please :( We'll need a way to run a determinsitic "light" papilo presolve pass at some point regardless

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive RINS is disable for the deterministic mode. In any case, we have the same problem when calling the Papilo in the regular solve, right?

@nguidotti nguidotti Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When and for which node the recursive RINS triggers is quite non-deterministic, too.

@aliceb-nv aliceb-nv Jul 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have this issue when running Papilo at the root because we disable the time limit there in determinsitic mode. Papilo terminates on its own, with some caps on the Probing work limits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay,, let's track that recursive RINS is strictly non-determinsitic with an issue. That's something I'll have to address later

@nguidotti nguidotti Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created here: #1579. If the solver is deterministic and we can call the solver stack directly, then we probably only need to do is make the triggering logic deterministic.


// We need to do this dance of uncrush methods since we are working on the presolved space on
// the augmented space (structural + slack + cuts), while the `set_solution_from_heuristics`
// expects a solution on the user space. So we go from presolved space -> augmented space ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the augmented space? Just with the addition of slack variables. Note that slack variables are always placed at the end, and so the conversion from the problem with slacks to without slacks is trivial, you just drop variables at the end.

@nguidotti nguidotti Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We start with a range form, user_problem_t with $m \times n$, and then add the slacks to transform into the standard form, lp_problem_t with $(m + m_s) \times (n + n_s)$. Then we add cuts and the system become $(m + m_s + m_c) \times (n + n_s + n_c)$. Let us call this the augmented problem.

For the submip, we need to convert the augmented problem back to range form. However, this is a direct conversion (set the range rows as equality and all slacks and cuts are preserved) so the user_problem_t will have the dimensions $(m + m_s + m_c) \times (n + n_s + n_c)$. When calling Papilo for the submip problem, it now consider the augmented system as the original space. Hence, it will crush/uncrush the solution to the $(m + m_s + m_c) \times (n + n_s + n_c)$ augmented system, not for the original $m \times n$ user_problem_t which the routine set_solution_from_heuristic expects. For this reason, we need to first uncrush the solution from the presolved space of the augmented system via Papilo, then uncrush the results again to the original $m \times n$ user_problem_t. Note that this is recursive (a sub-mip 2 levels deep will need to do 2 conversion which is done by going up the solver stack).

We will have a similar issue with restarts unless we accumulate the reductions into a single Papilo object that was created in the beginning

Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
submip_bnb.set_halt_callback(halt_callback_);
} else {
// This should only be called by the main solver.
submip_bnb.set_halt_callback([this](f_t, f_t submip_lower_bound) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a callback necessary here? I think you might be able to simplify things by just having a variable cutoff that can be set to the upper bound by the main solve. Or better yet, why not just have the submip have a pointer to the upper bound variable of the main solve. That way every time the main solve updates the upper bound, the submip can see it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was originally what I did (having a pointer to the upper bound of the main solve). The current approach is more flexible, though. This allows not only to stop due to the cutoff but also if the solver status changes (e.g., let say the solver reaches the node/time limit and stops, then this callback can signal all the sub-MIP solves to stop immediately)

}

fj_cpu_worker_t<i_t, f_t> submip_fj_cpu_worker;
scope_guard cpufj_guard([&]() { submip_fj_cpu_worker.stop(); });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is going on here? Could you add some comment to explain?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment:

    // Launch a CPU FJ worker on the presolved sub-MIP with a fixed budget (in terms of work units)
    // to run in parallel with the cut-and-branch algorithm with the goal of finding a quick feasible 
    // solution for the sub-MIP problem. The CPU FJ uses the current incumbent (crushed into the 
    // presolved space) as initial guess. The worker is automatically stop when we go out of the
    // scope.

Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp
Comment thread cpp/src/branch_and_bound/branch_and_bound.cpp Outdated

@chris-maes chris-maes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only made it through branch_and_bound.cpp. I need to finish the review later.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality mip non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Recursive RINS

5 participants