Skip to content
Merged
Show file tree
Hide file tree
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 Oct 6, 2025
0a7a7eb
refactor based on the reuse-parent-branch
nguidotti Oct 9, 2025
7997b12
fixed value of the bounds_change for the child
nguidotti Oct 9, 2025
21d4bcf
Merge remote-tracking branch 'cuopt/branch-25.10' into bounds-propaga…
nguidotti Oct 9, 2025
9368feb
Merge branch 'fix-diving-bounds' into bounds-propagation
nguidotti Oct 10, 2025
5a5090c
Merge remote-tracking branch 'cuopt/branch-25.10' into bounds-propaga…
nguidotti Oct 11, 2025
861a12e
Merge branch 'branch-25.10' into bounds-propagation
nguidotti Oct 13, 2025
95de283
revert refactor
nguidotti Oct 13, 2025
f44b134
fix incorrect bounds when two nodes shares the same branch variable
nguidotti Oct 13, 2025
320f162
Merge branch 'branch-25.10' into bounds-propagation
nguidotti Oct 14, 2025
bfeb660
constraints_changed is only set for branched variables. refactor boun…
nguidotti Oct 14, 2025
42dee91
Merge branch 'branch-25.10' into bounds-propagation
nguidotti Oct 14, 2025
f954dcf
Merge branch 'branch-25.10' into bounds-propagation
nguidotti Oct 22, 2025
222f95b
Merge branch 'main' into bounds-propagation
nguidotti Oct 22, 2025
b5ed956
small refactor
nguidotti Oct 22, 2025
abeb2cf
fix log spacing
nguidotti Oct 22, 2025
3237913
fix log spacing for repaired solutions
nguidotti Oct 22, 2025
f9853b2
initialize the presolver only once per thread
nguidotti Oct 22, 2025
6ccb6d9
fixed race condition
nguidotti Oct 23, 2025
8f55932
small refactor
nguidotti Oct 23, 2025
7f337d4
fixed missing template parameters
nguidotti Oct 23, 2025
06ca86d
fixed crash
nguidotti Oct 23, 2025
b5fc52c
added asserts
nguidotti Oct 23, 2025
d0b5501
Merge branch 'main' into bounds-propagation
nguidotti Oct 23, 2025
f7a38bc
fix compilation error
nguidotti Oct 23, 2025
649c8ca
Update cpp/src/dual_simplex/mip_node.hpp
nguidotti Oct 24, 2025
4afb71d
addresses reviewer's comments
nguidotti Oct 24, 2025
1c92230
fix style
nguidotti Oct 24, 2025
7d3f392
Merge branch 'main' into bounds-propagation
nguidotti Oct 24, 2025
bbd410b
small refactor. added missing recompute flag to diving.
nguidotti Oct 24, 2025
8c420c4
small fixes
nguidotti Oct 24, 2025
5bd70e1
small fix
nguidotti Oct 24, 2025
56747e5
fix logs
nguidotti Oct 24, 2025
c7facc6
fixed log
nguidotti Oct 24, 2025
8b294be
fixed starting bounds for diving nodes. replaced rounding direction w…
nguidotti Oct 27, 2025
27f9264
fix missing enum
nguidotti Oct 28, 2025
bf142d5
fixed typo
nguidotti Oct 28, 2025
65981b4
Merge branch 'main' into bounds-propagation
nguidotti Oct 28, 2025
6f77e1d
Merge branch 'main' into bounds-propagation
nguidotti Oct 29, 2025
de4e292
Merge branch 'main' into bounds-propagation
nguidotti Oct 31, 2025
cc89861
renamed files and classes
nguidotti Oct 31, 2025
874d45f
fix incorrect report of infeasible solution
nguidotti Oct 31, 2025
5bfda56
fixed typo
nguidotti Oct 31, 2025
04e3063
fixed typo
nguidotti Oct 31, 2025
1df3b4c
rename variable
nguidotti Nov 3, 2025
2c21c35
Merge branch 'main' into bounds-propagation
nguidotti Nov 3, 2025
5ec4472
addresses reviewer's comments
nguidotti Nov 10, 2025
015bb00
fixed incorrect stats update
nguidotti Nov 10, 2025
9cfddeb
Merge branch 'main' into bounds-propagation
nguidotti Nov 10, 2025
ac199a3
fix typo and styling
nguidotti Nov 10, 2025
770adfb
small refactor
nguidotti Nov 10, 2025
3317eaa
fix missing initialization
nguidotti Nov 10, 2025
78a4753
Merge branch 'main' into bounds-propagation
nguidotti Nov 11, 2025
fcfc709
Merge branch 'main' into bounds-propagation
nguidotti Nov 11, 2025
a0f8e13
fixed bugs after merge
nguidotti Nov 11, 2025
7077dbc
revert parameters to their original values
nguidotti Nov 11, 2025
d9a45cf
fixing crashes
nguidotti Nov 11, 2025
dbd413d
Merge commit 'd9a45cf1' into bounds-propagation
nguidotti Nov 11, 2025
58c70ac
disable RINS logs
nguidotti Nov 12, 2025
a881b70
tighten tolerance for refactoring
nguidotti Nov 12, 2025
4947fe1
further tighten the tolerance
nguidotti Nov 12, 2025
c36b6d3
Merge branch 'main' into bounds-propagation
nguidotti Nov 13, 2025
5e3cd26
changed refactor tolerances. updated logger to support different modes.
nguidotti Nov 14, 2025
74ef1ba
tighten tolerance for refactoring the basis. disabled RINS logs.
nguidotti Nov 12, 2025
2cadc89
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti Nov 14, 2025
0911341
Merge branch 'main' into refactor-tolerance
nguidotti Nov 17, 2025
64d1b9f
Merge branch 'release/25.12' into refactor-tolerance
nguidotti Nov 18, 2025
560b402
Merge branch 'release/25.12' into bounds-propagation
nguidotti Nov 18, 2025
1bcc57f
added missing workspace cleaning
nguidotti Nov 19, 2025
7a482fc
Merge branch 'release/25.12' into refactor-tolerance
nguidotti Nov 20, 2025
f687ce4
small refactor
nguidotti Nov 20, 2025
d94228e
Merge branch 'release/25.12' into bounds-propagation
nguidotti Nov 20, 2025
1048327
fixed crash due to selecting the incorrect last column
nguidotti Nov 20, 2025
32055d6
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti Nov 20, 2025
8b4546e
reverting fix
nguidotti Nov 20, 2025
3240f88
fixed missing vector if the matrix is rank deficient and there is no …
nguidotti Nov 20, 2025
180c7ec
Merge branch 'refactor-tolerance' into bounds-propagation
nguidotti Nov 20, 2025
e0c714d
Merge branch 'release/25.12' into bounds-propagation
nguidotti Nov 21, 2025
a90b5ad
removing debug leftover
nguidotti Nov 21, 2025
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
1 change: 1 addition & 0 deletions cpp/src/dual_simplex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set(DUAL_SIMPLEX_SRC_FILES
${CMAKE_CURRENT_SOURCE_DIR}/phase1.cpp
${CMAKE_CURRENT_SOURCE_DIR}/phase2.cpp
${CMAKE_CURRENT_SOURCE_DIR}/presolve.cpp
${CMAKE_CURRENT_SOURCE_DIR}/bounds_strengthening.cpp
${CMAKE_CURRENT_SOURCE_DIR}/primal.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pseudo_costs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/right_looking_lu.cpp
Expand Down
290 changes: 290 additions & 0 deletions cpp/src/dual_simplex/bounds_strengthening.cpp
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
42 changes: 42 additions & 0 deletions cpp/src/dual_simplex/bounds_strengthening.hpp
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>

Comment thread
nguidotti marked this conversation as resolved.
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;
};
Comment thread
nguidotti marked this conversation as resolved.
} // namespace cuopt::linear_programming::dual_simplex
Loading