-
Notifications
You must be signed in to change notification settings - Fork 161
Propagate the bounds from the parent to the child nodes #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rapids-bot
merged 79 commits into
NVIDIA:release/25.12
from
nguidotti:bounds-propagation
Nov 24, 2025
Merged
Changes from all commits
Commits
Show all changes
79 commits
Select commit
Hold shift + click to select a range
09a5e7e
child nodes now reuse the bounds of the parent. fixed incorrect bound…
nguidotti 0a7a7eb
refactor based on the reuse-parent-branch
nguidotti 7997b12
fixed value of the bounds_change for the child
nguidotti 21d4bcf
Merge remote-tracking branch 'cuopt/branch-25.10' into bounds-propaga…
nguidotti 9368feb
Merge branch 'fix-diving-bounds' into bounds-propagation
nguidotti 5a5090c
Merge remote-tracking branch 'cuopt/branch-25.10' into bounds-propaga…
nguidotti 861a12e
Merge branch 'branch-25.10' into bounds-propagation
nguidotti 95de283
revert refactor
nguidotti f44b134
fix incorrect bounds when two nodes shares the same branch variable
nguidotti 320f162
Merge branch 'branch-25.10' into bounds-propagation
nguidotti bfeb660
constraints_changed is only set for branched variables. refactor boun…
nguidotti 42dee91
Merge branch 'branch-25.10' into bounds-propagation
nguidotti f954dcf
Merge branch 'branch-25.10' into bounds-propagation
nguidotti 222f95b
Merge branch 'main' into bounds-propagation
nguidotti b5ed956
small refactor
nguidotti abeb2cf
fix log spacing
nguidotti 3237913
fix log spacing for repaired solutions
nguidotti f9853b2
initialize the presolver only once per thread
nguidotti 6ccb6d9
fixed race condition
nguidotti 8f55932
small refactor
nguidotti 7f337d4
fixed missing template parameters
nguidotti 06ca86d
fixed crash
nguidotti b5fc52c
added asserts
nguidotti d0b5501
Merge branch 'main' into bounds-propagation
nguidotti f7a38bc
fix compilation error
nguidotti 649c8ca
Update cpp/src/dual_simplex/mip_node.hpp
nguidotti 4afb71d
addresses reviewer's comments
nguidotti 1c92230
fix style
nguidotti 7d3f392
Merge branch 'main' into bounds-propagation
nguidotti bbd410b
small refactor. added missing recompute flag to diving.
nguidotti 8c420c4
small fixes
nguidotti 5bd70e1
small fix
nguidotti 56747e5
fix logs
nguidotti c7facc6
fixed log
nguidotti 8b294be
fixed starting bounds for diving nodes. replaced rounding direction w…
nguidotti 27f9264
fix missing enum
nguidotti bf142d5
fixed typo
nguidotti 65981b4
Merge branch 'main' into bounds-propagation
nguidotti 6f77e1d
Merge branch 'main' into bounds-propagation
nguidotti de4e292
Merge branch 'main' into bounds-propagation
nguidotti cc89861
renamed files and classes
nguidotti 874d45f
fix incorrect report of infeasible solution
nguidotti 5bfda56
fixed typo
nguidotti 04e3063
fixed typo
nguidotti 1df3b4c
rename variable
nguidotti 2c21c35
Merge branch 'main' into bounds-propagation
nguidotti 5ec4472
addresses reviewer's comments
nguidotti 015bb00
fixed incorrect stats update
nguidotti 9cfddeb
Merge branch 'main' into bounds-propagation
nguidotti ac199a3
fix typo and styling
nguidotti 770adfb
small refactor
nguidotti 3317eaa
fix missing initialization
nguidotti 78a4753
Merge branch 'main' into bounds-propagation
nguidotti fcfc709
Merge branch 'main' into bounds-propagation
nguidotti a0f8e13
fixed bugs after merge
nguidotti 7077dbc
revert parameters to their original values
nguidotti d9a45cf
fixing crashes
nguidotti dbd413d
Merge commit 'd9a45cf1' into bounds-propagation
nguidotti 58c70ac
disable RINS logs
nguidotti a881b70
tighten tolerance for refactoring
nguidotti 4947fe1
further tighten the tolerance
nguidotti c36b6d3
Merge branch 'main' into bounds-propagation
nguidotti 5e3cd26
changed refactor tolerances. updated logger to support different modes.
nguidotti 74ef1ba
tighten tolerance for refactoring the basis. disabled RINS logs.
nguidotti 2cadc89
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti 0911341
Merge branch 'main' into refactor-tolerance
nguidotti 64d1b9f
Merge branch 'release/25.12' into refactor-tolerance
nguidotti 560b402
Merge branch 'release/25.12' into bounds-propagation
nguidotti 1bcc57f
added missing workspace cleaning
nguidotti 7a482fc
Merge branch 'release/25.12' into refactor-tolerance
nguidotti f687ce4
small refactor
nguidotti d94228e
Merge branch 'release/25.12' into bounds-propagation
nguidotti 1048327
fixed crash due to selecting the incorrect last column
nguidotti 32055d6
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti 8b4546e
reverting fix
nguidotti 3240f88
fixed missing vector if the matrix is rank deficient and there is no …
nguidotti 180c7ec
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti e0c714d
Merge branch 'release/25.12' into bounds-propagation
nguidotti a90b5ad
removing debug leftover
nguidotti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,290 @@ | ||
| /* clang-format off */ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| /* clang-format on */ | ||
|
|
||
| #include <dual_simplex/bounds_strengthening.hpp> | ||
|
|
||
| #include <algorithm> | ||
| #include <cmath> | ||
|
|
||
| namespace cuopt::linear_programming::dual_simplex { | ||
|
|
||
| template <typename f_t> | ||
| static inline f_t update_lb(f_t curr_lb, f_t coeff, f_t delta_min_act, f_t delta_max_act) | ||
| { | ||
| auto comp_bnd = (coeff < 0.) ? delta_min_act / coeff : delta_max_act / coeff; | ||
| return std::max(curr_lb, comp_bnd); | ||
| } | ||
|
|
||
| template <typename f_t> | ||
| static inline f_t update_ub(f_t curr_ub, f_t coeff, f_t delta_min_act, f_t delta_max_act) | ||
| { | ||
| auto comp_bnd = (coeff < 0.) ? delta_max_act / coeff : delta_min_act / coeff; | ||
| return std::min(curr_ub, comp_bnd); | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| static inline bool check_infeasibility(f_t min_a, f_t max_a, f_t cnst_lb, f_t cnst_ub, f_t eps) | ||
| { | ||
| return (min_a > cnst_ub + eps) || (max_a < cnst_lb - eps); | ||
| } | ||
|
|
||
| #define DEBUG_BOUND_STRENGTHENING 0 | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| void print_bounds_stats(const std::vector<f_t>& lower, | ||
| const std::vector<f_t>& upper, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings, | ||
| const std::string msg) | ||
| { | ||
| #if DEBUG_BOUND_STRENGTHENING | ||
| f_t lb_norm = 0.0; | ||
| f_t ub_norm = 0.0; | ||
|
|
||
| i_t sz = lower.size(); | ||
| for (i_t i = 0; i < sz; ++i) { | ||
| if (std::isfinite(lower[i])) { lb_norm += abs(lower[i]); } | ||
| if (std::isfinite(upper[i])) { ub_norm += abs(upper[i]); } | ||
| } | ||
| settings.log.printf("%s :: lb norm %e, ub norm %e\n", msg.c_str(), lb_norm, ub_norm); | ||
| #endif | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| bounds_strengthening_t<i_t, f_t>::bounds_strengthening_t( | ||
| const lp_problem_t<i_t, f_t>& problem, | ||
| const csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<char>& row_sense, | ||
| const std::vector<variable_type_t>& var_types) | ||
| : bounds_changed(problem.num_cols, false), | ||
| A(problem.A), | ||
| Arow(Arow), | ||
| var_types(var_types), | ||
| delta_min_activity(problem.num_rows), | ||
| delta_max_activity(problem.num_rows), | ||
| constraint_lb(problem.num_rows), | ||
| constraint_ub(problem.num_rows) | ||
| { | ||
| const bool is_row_sense_empty = row_sense.empty(); | ||
| if (is_row_sense_empty) { | ||
| std::copy(problem.rhs.begin(), problem.rhs.end(), constraint_lb.begin()); | ||
| std::copy(problem.rhs.begin(), problem.rhs.end(), constraint_ub.begin()); | ||
| } else { | ||
| // Set the constraint bounds | ||
| for (i_t i = 0; i < problem.num_rows; ++i) { | ||
| if (row_sense[i] == 'E') { | ||
| constraint_lb[i] = problem.rhs[i]; | ||
| constraint_ub[i] = problem.rhs[i]; | ||
| } else if (row_sense[i] == 'L') { | ||
| constraint_ub[i] = problem.rhs[i]; | ||
| constraint_lb[i] = -inf; | ||
| } else { | ||
| constraint_lb[i] = problem.rhs[i]; | ||
| constraint_ub[i] = inf; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| bool bounds_strengthening_t<i_t, f_t>::bounds_strengthening( | ||
| std::vector<f_t>& lower_bounds, | ||
| std::vector<f_t>& upper_bounds, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings) | ||
| { | ||
| const i_t m = A.m; | ||
| const i_t n = A.n; | ||
|
|
||
| std::vector<bool> constraint_changed(m, true); | ||
| std::vector<bool> variable_changed(n, false); | ||
| std::vector<bool> constraint_changed_next(m, false); | ||
|
|
||
| if (!bounds_changed.empty()) { | ||
| std::fill(constraint_changed.begin(), constraint_changed.end(), false); | ||
| for (i_t i = 0; i < n; ++i) { | ||
| if (bounds_changed[i]) { | ||
| const i_t row_start = A.col_start[i]; | ||
| const i_t row_end = A.col_start[i + 1]; | ||
| for (i_t p = row_start; p < row_end; ++p) { | ||
| const i_t j = A.i[p]; | ||
| constraint_changed[j] = true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| lower = lower_bounds; | ||
| upper = upper_bounds; | ||
| print_bounds_stats(lower, upper, settings, "Initial bounds"); | ||
|
|
||
| i_t iter = 0; | ||
| const i_t iter_limit = 10; | ||
| while (iter < iter_limit) { | ||
| for (i_t i = 0; i < m; ++i) { | ||
| if (!constraint_changed[i]) { continue; } | ||
| const i_t row_start = Arow.row_start[i]; | ||
| const i_t row_end = Arow.row_start[i + 1]; | ||
|
|
||
| f_t min_a = 0.0; | ||
| f_t max_a = 0.0; | ||
| for (i_t p = row_start; p < row_end; ++p) { | ||
| const i_t j = Arow.j[p]; | ||
| const f_t a_ij = Arow.x[p]; | ||
|
|
||
| variable_changed[j] = true; | ||
| if (a_ij > 0) { | ||
| min_a += a_ij * lower[j]; | ||
| max_a += a_ij * upper[j]; | ||
| } else if (a_ij < 0) { | ||
| min_a += a_ij * upper[j]; | ||
| max_a += a_ij * lower[j]; | ||
| } | ||
| if (upper[j] == inf && a_ij > 0) { max_a = inf; } | ||
| if (lower[j] == -inf && a_ij < 0) { max_a = inf; } | ||
|
|
||
| if (lower[j] == -inf && a_ij > 0) { min_a = -inf; } | ||
| if (upper[j] == inf && a_ij < 0) { min_a = -inf; } | ||
| } | ||
|
|
||
| f_t cnst_lb = constraint_lb[i]; | ||
| f_t cnst_ub = constraint_ub[i]; | ||
| bool is_infeasible = | ||
| check_infeasibility<i_t, f_t>(min_a, max_a, cnst_lb, cnst_ub, settings.primal_tol); | ||
| if (is_infeasible) { | ||
| settings.log.printf( | ||
| "Iter:: %d, Infeasible constraint %d, cnst_lb %e, cnst_ub %e, min_a %e, max_a %e\n", | ||
| iter, | ||
| i, | ||
| cnst_lb, | ||
| cnst_ub, | ||
| min_a, | ||
| max_a); | ||
| return false; | ||
| } | ||
|
|
||
| delta_min_activity[i] = cnst_ub - min_a; | ||
| delta_max_activity[i] = cnst_lb - max_a; | ||
| } | ||
|
|
||
| i_t num_bounds_changed = 0; | ||
|
|
||
| for (i_t k = 0; k < n; ++k) { | ||
| if (!variable_changed[k]) { continue; } | ||
| f_t old_lb = lower[k]; | ||
| f_t old_ub = upper[k]; | ||
|
|
||
| f_t new_lb = old_lb; | ||
| f_t new_ub = old_ub; | ||
|
|
||
| const i_t row_start = A.col_start[k]; | ||
| const i_t row_end = A.col_start[k + 1]; | ||
| for (i_t p = row_start; p < row_end; ++p) { | ||
| const i_t i = A.i[p]; | ||
|
|
||
| if (!constraint_changed[i]) { continue; } | ||
| const f_t a_ik = A.x[p]; | ||
|
|
||
| f_t delta_min_act = delta_min_activity[i]; | ||
| f_t delta_max_act = delta_max_activity[i]; | ||
|
|
||
| delta_min_act += (a_ik < 0) ? a_ik * old_ub : a_ik * old_lb; | ||
| delta_max_act += (a_ik > 0) ? a_ik * old_ub : a_ik * old_lb; | ||
|
|
||
| new_lb = std::max(new_lb, update_lb(old_lb, a_ik, delta_min_act, delta_max_act)); | ||
| new_ub = std::min(new_ub, update_ub(old_ub, a_ik, delta_min_act, delta_max_act)); | ||
| } | ||
|
|
||
| // Integer rounding | ||
| if (!var_types.empty() && | ||
| (var_types[k] == variable_type_t::INTEGER || var_types[k] == variable_type_t::BINARY)) { | ||
| new_lb = std::ceil(new_lb - settings.integer_tol); | ||
| new_ub = std::floor(new_ub + settings.integer_tol); | ||
| } | ||
|
|
||
| bool lb_updated = std::abs(new_lb - old_lb) > 1e3 * settings.primal_tol; | ||
| bool ub_updated = std::abs(new_ub - old_ub) > 1e3 * settings.primal_tol; | ||
|
|
||
| new_lb = std::max(new_lb, lower_bounds[k]); | ||
| new_ub = std::min(new_ub, upper_bounds[k]); | ||
|
|
||
| if (new_lb > new_ub + 1e-6) { | ||
| settings.log.printf( | ||
| "Iter:: %d, Infeasible variable after update %d, %e > %e\n", iter, k, new_lb, new_ub); | ||
| return false; | ||
| } | ||
| if (new_lb != old_lb || new_ub != old_ub) { | ||
| for (i_t p = row_start; p < row_end; ++p) { | ||
| const i_t i = A.i[p]; | ||
| constraint_changed_next[i] = true; | ||
| } | ||
| } | ||
|
|
||
| lower[k] = std::min(new_lb, new_ub); | ||
| upper[k] = std::max(new_lb, new_ub); | ||
|
|
||
| bool bounds_changed = lb_updated || ub_updated; | ||
| if (bounds_changed) { num_bounds_changed++; } | ||
| } | ||
|
|
||
| if (num_bounds_changed == 0) { break; } | ||
|
|
||
| std::swap(constraint_changed, constraint_changed_next); | ||
| std::fill(constraint_changed_next.begin(), constraint_changed_next.end(), false); | ||
| std::fill(variable_changed.begin(), variable_changed.end(), false); | ||
|
|
||
| iter++; | ||
| } | ||
|
|
||
| // settings.log.printf("Total strengthened variables %d\n", total_strengthened_variables); | ||
|
|
||
| #if DEBUG_BOUND_STRENGTHENING | ||
| f_t lb_change = 0.0; | ||
| f_t ub_change = 0.0; | ||
| int num_lb_changed = 0; | ||
| int num_ub_changed = 0; | ||
|
|
||
| for (i_t i = 0; i < n; ++i) { | ||
| if (lower[i] > problem.lower[i] + settings.primal_tol || | ||
| (!std::isfinite(problem.lower[i]) && std::isfinite(lower[i]))) { | ||
| num_lb_changed++; | ||
| lb_change += | ||
| std::isfinite(problem.lower[i]) | ||
| ? (lower[i] - problem.lower[i]) / (1e-6 + std::max(abs(lower[i]), abs(problem.lower[i]))) | ||
| : 1.0; | ||
| } | ||
| if (upper[i] < problem.upper[i] - settings.primal_tol || | ||
| (!std::isfinite(problem.upper[i]) && std::isfinite(upper[i]))) { | ||
| num_ub_changed++; | ||
| ub_change += | ||
| std::isfinite(problem.upper[i]) | ||
| ? (problem.upper[i] - upper[i]) / (1e-6 + std::max(abs(problem.upper[i]), abs(upper[i]))) | ||
| : 1.0; | ||
| } | ||
| } | ||
|
|
||
| if (num_lb_changed > 0 || num_ub_changed > 0) { | ||
| settings.log.printf( | ||
| "lb change %e, ub change %e, num lb changed %d, num ub changed %d, iter %d\n", | ||
| 100 * lb_change / std::max(1, num_lb_changed), | ||
| 100 * ub_change / std::max(1, num_ub_changed), | ||
| num_lb_changed, | ||
| num_ub_changed, | ||
| iter); | ||
| } | ||
| print_bounds_stats(lower, upper, settings, "Final bounds"); | ||
| #endif | ||
|
|
||
| lower_bounds = lower; | ||
| upper_bounds = upper; | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| #ifdef DUAL_SIMPLEX_INSTANTIATE_DOUBLE | ||
| template class bounds_strengthening_t<int, double>; | ||
| #endif | ||
|
|
||
| } // namespace cuopt::linear_programming::dual_simplex |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* clang-format off */ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| /* clang-format on */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <dual_simplex/presolve.hpp> | ||
|
|
||
| namespace cuopt::linear_programming::dual_simplex { | ||
|
|
||
| template <typename i_t, typename f_t> | ||
| class bounds_strengthening_t { | ||
| public: | ||
| // For pure LP bounds strengthening, var_types should be defaulted (i.e. left empty) | ||
| bounds_strengthening_t(const lp_problem_t<i_t, f_t>& problem, | ||
| const csr_matrix_t<i_t, f_t>& Arow, | ||
| const std::vector<char>& row_sense, | ||
| const std::vector<variable_type_t>& var_types); | ||
|
|
||
| bool bounds_strengthening(std::vector<f_t>& lower_bounds, | ||
| std::vector<f_t>& upper_bounds, | ||
| const simplex_solver_settings_t<i_t, f_t>& settings); | ||
|
|
||
| std::vector<bool> bounds_changed; | ||
|
|
||
| private: | ||
| const csc_matrix_t<i_t, f_t>& A; | ||
| const csr_matrix_t<i_t, f_t>& Arow; | ||
| const std::vector<variable_type_t>& var_types; | ||
|
|
||
| std::vector<f_t> lower; | ||
| std::vector<f_t> upper; | ||
|
|
||
| std::vector<f_t> delta_min_activity; | ||
| std::vector<f_t> delta_max_activity; | ||
| std::vector<f_t> constraint_lb; | ||
| std::vector<f_t> constraint_ub; | ||
| }; | ||
|
nguidotti marked this conversation as resolved.
|
||
| } // namespace cuopt::linear_programming::dual_simplex | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.