Skip to content

Commit 0333c30

Browse files
Fix occasional incorrect solution bound on maximization problems (#83)
<!-- Thank you for contributing to cuOpt :) Here are some guidelines to help the review process go smoothly. 1. Please write a description in this text box of the changes that are being made. 2. Please ensure that you have written units tests for the changes made/features added. 3. If you are closing an issue please use one of the automatic closing words as noted here: https://help.github.com/articles/closing-issues-using-keywords/ 4. If your pull request is not ready for review but you want to make use of the continuous integration testing facilities please label it with `[WIP]`. 5. If your pull request is ready to be reviewed without requiring additional work on top of it, then remove the `[WIP]` label (if present) and replace it with `[REVIEW]`. If assistance is required to complete the functionality, for example when the C/C++ code of a feature is complete but Python bindings are still required, then add the label `[HELP-REQ]` so that others can triage and assist. The additional changes then can be implemented on top of the same PR. If the assistance is done by members of the rapidsAI team, then no additional actions are required by the creator of the original PR for this, otherwise the original author of the PR needs to give permission to the person(s) assisting to commit to their personal fork of the project. If that doesn't happen then a new PR based on the code of the original PR can be opened by the person assisting, which then will be the PR that will be merged. 6. Once all work has been done and review has taken place please do not add features or make changes out of the scope of those requested by the reviewer (doing this just add delays as already reviewed code ends up having to be re-reviewed/it is hard to tell what is new etc!). Further, please do not rebase your branch on main/force push/rewrite history, doing any of these causes the context of any comments made by reviewers to be lost. If conflicts occur against main they should be resolved by merging main into the branch used for making the pull request. Many thanks in advance for your cooperation! --> During the PDLP LP relaxation phase, the solution bound was incorrectly converted to a user objective value twice, yielding an incorrect sign on maximizations problems. This only occurs before B&B updates the bound further, which explains why this bug only occurs in specific scenarios (short runs, large problems). This also fixes optimality not being reported on simple maximizations problems. closes #76
1 parent ccbee4f commit 0333c30

File tree

6 files changed

+32304
-19
lines changed

6 files changed

+32304
-19
lines changed

cpp/src/mip/diversity/diversity_manager.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
351351
ls.lp_optimal_exists = true;
352352
if (lp_result.get_termination_status() == pdlp_termination_status_t::Optimal) {
353353
// get lp user objective and pass it to set_new_user_bound
354-
set_new_user_bound(problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()));
354+
set_new_user_bound(lp_result.get_objective_value());
355355
} else if (lp_result.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible) {
356356
// PDLP's infeasibility detection isn't an exact method and might be subject to false positives.
357357
// Issue a warning, and continue solving.

cpp/tests/mip/termination_test.cu

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@
4545

4646
namespace cuopt::linear_programming::test {
4747

48-
static std::pair<mip_termination_status_t, double> test_mps_file(std::string test_instance,
49-
bool heuristics_only = true,
50-
double time_limit = 10)
48+
static std::tuple<mip_termination_status_t, double, double> test_mps_file(
49+
std::string test_instance, bool heuristics_only = true, double time_limit = 10)
5150
{
5251
const raft::handle_t handle_{};
5352

@@ -59,58 +58,69 @@ static std::pair<mip_termination_status_t, double> test_mps_file(std::string tes
5958
settings.time_limit = time_limit;
6059
settings.heuristics_only = heuristics_only;
6160
mip_solution_t<int, double> solution = solve_mip(&handle_, problem, settings);
62-
return std::make_pair(solution.get_termination_status(), solution.get_objective_value());
61+
return std::make_tuple(solution.get_termination_status(),
62+
solution.get_objective_value(),
63+
solution.get_solution_bound());
6364
}
6465

6566
TEST(termination_status, trivial_presolve_optimality_test)
6667
{
67-
auto [termination_status, obj_val] = test_mps_file("mip/trivial-presolve-optimality.mps");
68+
auto [termination_status, obj_val, lb] = test_mps_file("mip/trivial-presolve-optimality.mps");
6869
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
6970
EXPECT_EQ(obj_val, -1);
7071
}
7172

7273
TEST(termination_status, presolve_optimality_test)
7374
{
74-
auto [termination_status, obj_val] = test_mps_file("mip/sudoku.mps");
75+
auto [termination_status, obj_val, lb] = test_mps_file("mip/sudoku.mps");
7576
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
7677
EXPECT_EQ(obj_val, 0);
7778
}
7879

7980
TEST(termination_status, presolve_infeasible_test)
8081
{
81-
auto [termination_status, obj_val] = test_mps_file("mip/presolve-infeasible.mps");
82+
auto [termination_status, obj_val, lb] = test_mps_file("mip/presolve-infeasible.mps");
8283
EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible);
8384
}
8485

8586
TEST(termination_status, feasible_found_test)
8687
{
87-
auto [termination_status, obj_val] = test_mps_file("mip/gen-ip054.mps");
88+
auto [termination_status, obj_val, lb] = test_mps_file("mip/gen-ip054.mps");
8889
EXPECT_EQ(termination_status, mip_termination_status_t::FeasibleFound);
8990
}
9091

9192
TEST(termination_status, timeout_test)
9293
{
93-
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps");
94+
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps");
9495
EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit);
9596
}
9697

9798
TEST(termination_status, optimality_test)
9899
{
99-
auto [termination_status, obj_val] = test_mps_file("mip/bb_optimality.mps", false);
100+
auto [termination_status, obj_val, lb] = test_mps_file("mip/bb_optimality.mps", false);
100101
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
101102
EXPECT_EQ(obj_val, 2);
102103
}
103104

105+
// Ensure the lower bound on maximization problems when BB times out has the right sign
106+
TEST(termination_status, lower_bound_bb_timeout)
107+
{
108+
auto [termination_status, obj_val, lb] = test_mps_file("mip/cod105_max.mps", false, 0.5);
109+
EXPECT_EQ(termination_status, mip_termination_status_t::FeasibleFound);
110+
EXPECT_EQ(obj_val, 12);
111+
EXPECT_GE(lb, obj_val);
112+
}
113+
104114
TEST(termination_status, bb_infeasible_test)
105115
{
106116
// First, check that presolve doesn't reduce the problem to infeasibility
107117
{
108-
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", true, 1);
118+
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps", true, 1);
109119
EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit);
110120
}
111121
// Ensure that B&B proves the MIP infeasible
112122
{
113-
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", false, 30);
123+
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps", false, 30);
114124
EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible);
115125
}
116126
}

cpp/tests/mip/unit_test.cu

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,11 @@ INSTANTIATE_TEST_SUITE_P(
215215
MILPTests,
216216
MILPTestParams,
217217
testing::Values(
218-
std::make_tuple(
219-
true, true, true, cuopt::linear_programming::mip_termination_status_t::FeasibleFound),
218+
std::make_tuple(true, true, true, cuopt::linear_programming::mip_termination_status_t::Optimal),
220219
std::make_tuple(
221220
false, true, false, cuopt::linear_programming::mip_termination_status_t::Optimal),
222221
std::make_tuple(
223-
true, false, true, cuopt::linear_programming::mip_termination_status_t::FeasibleFound),
222+
true, false, true, cuopt::linear_programming::mip_termination_status_t::Optimal),
224223
std::make_tuple(
225224
false, false, false, cuopt::linear_programming::mip_termination_status_t::Optimal)));
226225

0 commit comments

Comments
 (0)