Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 15 additions & 8 deletions cpp/src/mip/solution/solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ solution_t<i_t, f_t>::solution_t(const solution_t<i_t, f_t>& other)
h_user_obj(other.h_user_obj),
h_infeasibility_cost(other.h_infeasibility_cost),
is_feasible(other.is_feasible),
is_problem_fully_reduced(other.is_problem_fully_reduced),
is_scaled_(other.is_scaled_),
post_process_completed(other.post_process_completed),
lp_state(other.lp_state)
{
}
Expand All @@ -94,8 +96,10 @@ void solution_t<i_t, f_t>::copy_from(const solution_t<i_t, f_t>& other_sol)
other_sol.n_feasible_constraints.data(),
1,
handle_ptr->get_stream());
is_feasible = other_sol.is_feasible;
is_scaled_ = other_sol.is_scaled_;
is_feasible = other_sol.is_feasible;
is_problem_fully_reduced = other_sol.is_problem_fully_reduced;
is_scaled_ = other_sol.is_scaled_;
post_process_completed = other_sol.post_process_completed;
expand_device_copy(
lp_state.prev_primal, other_sol.lp_state.prev_primal, handle_ptr->get_stream());
expand_device_copy(lp_state.prev_dual, other_sol.lp_state.prev_dual, handle_ptr->get_stream());
Expand Down Expand Up @@ -183,15 +187,15 @@ bool solution_t<i_t, f_t>::get_feasible()
}

template <typename i_t, typename f_t>
bool solution_t<i_t, f_t>::get_problem_infeasible()
bool solution_t<i_t, f_t>::get_problem_fully_reduced()
{
return is_problem_infeasible;
return is_problem_fully_reduced;
}

template <typename i_t, typename f_t>
void solution_t<i_t, f_t>::set_problem_infeasible()
void solution_t<i_t, f_t>::set_problem_fully_reduced()
{
is_problem_infeasible = true;
is_problem_fully_reduced = true;
}

template <typename i_t, typename f_t>
Expand Down Expand Up @@ -267,6 +271,8 @@ void solution_t<i_t, f_t>::set_vars_to_values(
template <typename i_t, typename f_t>
void solution_t<i_t, f_t>::compute_constraints()
{
if (problem_ptr->n_constraints == 0) { return; }

i_t TPB = 64;
compute_constraint_values<i_t, f_t>
<<<problem_ptr->n_constraints, TPB, 0, handle_ptr->get_stream()>>>(view());
Expand Down Expand Up @@ -587,6 +593,7 @@ mip_solution_t<i_t, f_t> solution_t<i_t, f_t>::get_solution(bool output_feasible
const double optimality_threshold = problem_ptr->tolerances.relative_mip_gap;
auto term_reason = rel_mip_gap > optimality_threshold ? mip_termination_status_t::FeasibleFound
: mip_termination_status_t::Optimal;
if (is_problem_fully_reduced) { term_reason = mip_termination_status_t::Optimal; }
return mip_solution_t<i_t, f_t>(std::move(assignment),
problem_ptr->var_names,
h_user_obj,
Expand All @@ -601,8 +608,8 @@ mip_solution_t<i_t, f_t> solution_t<i_t, f_t>::get_solution(bool output_feasible
num_nodes,
num_simplex_iterations);
} else {
return mip_solution_t<i_t, f_t>{is_problem_infeasible ? mip_termination_status_t::Infeasible
: mip_termination_status_t::TimeLimit,
return mip_solution_t<i_t, f_t>{is_problem_fully_reduced ? mip_termination_status_t::Infeasible
: mip_termination_status_t::TimeLimit,
handle_ptr->get_stream()};
}
}
Expand Down
10 changes: 5 additions & 5 deletions cpp/src/mip/solution/solution.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ class solution_t {
void set_infeasible();
// gets the is_feasible
bool get_feasible();
// gets the is_problem_infeasible
bool get_problem_infeasible();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we remove the infeasible options? The problem can be infeasible right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I decided to cover both the case of infeasibility and optimality after presolve with set_problem_fully_reduced. get_solution() then determines, based on the status of the solution after post_process, which status to return (Optimal or Infeasible)
This ended up making the code more concise so that's why I went with this :)

https://github.com/NVIDIA/cuopt/pull/26/files#diff-115aa63d798001b25f7e001080d8ff9136923b2509328d8ccd079131e4dbc2b8L589-R613

// sets the is_problem_infeasible flag to 1
void set_problem_infeasible();
// gets the is_problem_fully_reduced
bool get_problem_fully_reduced();
// sets the is_problem_fully_reduced flag to 1
void set_problem_fully_reduced();
// computes the number of integral variables that have integral value
i_t compute_number_of_integers();
// computes the l2 residual value from the excess values
Expand Down Expand Up @@ -148,7 +148,7 @@ class solution_t {
f_t h_user_obj = 0.;
f_t h_infeasibility_cost = 0.;
bool is_feasible = false;
bool is_problem_infeasible{false};
bool is_problem_fully_reduced{false};
bool is_scaled_{false};
bool post_process_completed{false};
lp_state_t<i_t, f_t> lp_state;
Expand Down
10 changes: 0 additions & 10 deletions cpp/src/mip/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,6 @@ mip_solution_t<i_t, f_t> run_mip(detail::problem_t<i_t, f_t>& problem,
"please provide a more numerically stable problem.");
}

// If the trivial presolve fully reduced the problem:
if (scaled_problem.empty) {
detail::solution_t<i_t, f_t> fixed_assignment_solution(problem);
if (fixed_assignment_solution.compute_feasibility()) {
solver.get_solver_stats().solution_bound = scaled_sol.get_user_objective();
} else {
scaled_sol.set_problem_infeasible();
}
}

auto sol = scaled_sol.get_solution(is_feasible_before_scaling || is_feasible_after_unscaling,
solver.get_solver_stats());
detail::print_solution(scaled_problem.handle_ptr, sol.get_solution());
Expand Down
12 changes: 7 additions & 5 deletions cpp/src/mip/solver.cu
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ solution_t<i_t, f_t> mip_solver_t<i_t, f_t>::run_solver()
"preprocess_problem should be called before running the solver");

if (context.problem_ptr->empty) {
CUOPT_LOG_INFO("Problem fully reduced at trivial presolve");
CUOPT_LOG_INFO("Problem fully reduced in presolve");
solution_t<i_t, f_t> sol(*context.problem_ptr);
sol.set_problem_fully_reduced();
context.problem_ptr->post_process_solution(sol);
Copy link
Contributor

Choose a reason for hiding this comment

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

Dumb question: is this where the solution is populated? Where does presolve store the information about the solution when the problem is fully reduced in presolve?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of fully presolved problems the solution is populated by post_process_solution() (which tracks the transformations made by presolve) and additional solving stats information is added when get_solution() is called to return the final mip_solution_t : https://github.com/NVIDIA/cuopt/blob/branch-25.05/cpp/src/mip/solution/solution.cu#L560-L606

return sol;
}
Expand All @@ -106,15 +107,16 @@ solution_t<i_t, f_t> mip_solver_t<i_t, f_t>::run_solver()
dm.timer = timer_;
bool presolve_success = dm.run_presolve(timer_.remaining_time());
if (!presolve_success) {
CUOPT_LOG_INFO("Presolve is infeasible, returning infeasible solution!");
CUOPT_LOG_INFO("Problem proven infeasible in presolve");
solution_t<i_t, f_t> sol(*context.problem_ptr);
sol.set_problem_infeasible();
sol.set_problem_fully_reduced();
Copy link
Contributor

Choose a reason for hiding this comment

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

If we decide to keep infeasible, here it makes more sense to keep set_problem_infeasible

context.problem_ptr->post_process_solution(sol);
return sol;
}
if (context.problem_ptr->empty) {
CUOPT_LOG_INFO("Problem fully reduced at presolve");
CUOPT_LOG_INFO("Problem full reduced in presolve");
solution_t<i_t, f_t> sol(*context.problem_ptr);
sol.set_problem_fully_reduced();
context.problem_ptr->post_process_solution(sol);
return sol;
}
Expand Down Expand Up @@ -182,7 +184,7 @@ solution_t<i_t, f_t> mip_solver_t<i_t, f_t>::run_solver()
context.stats.solution_bound =
context.problem_ptr->get_user_obj_from_solver_obj(branch_and_bound_solution.lower_bound);
}
if (bb_status == dual_simplex::mip_status_t::INFEASIBLE) { sol.set_problem_infeasible(); }
if (bb_status == dual_simplex::mip_status_t::INFEASIBLE) { sol.set_problem_fully_reduced(); }
context.stats.num_nodes = branch_and_bound_solution.nodes_explored;
context.stats.num_simplex_iterations = branch_and_bound_solution.simplex_iterations;
}
Expand Down
15 changes: 15 additions & 0 deletions cpp/tests/mip/termination_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ static std::pair<mip_termination_status_t, double> test_mps_file(std::string tes
return std::make_pair(solution.get_termination_status(), solution.get_objective_value());
}

TEST(termination_status, trivial_presolve_optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/trivial-presolve-optimality.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, -1);
}

TEST(termination_status, presolve_optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/sudoku.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, 0);
}

TEST(termination_status, presolve_infeasible_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/presolve-infeasible.mps");
Expand All @@ -84,6 +98,7 @@ TEST(termination_status, optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/bb_optimality.mps", false);
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, 2);
}

TEST(termination_status, bb_infeasible_test)
Expand Down
Loading