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
33 changes: 29 additions & 4 deletions cpp/src/mip/problem/problem.cu
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,17 @@ template <typename i_t, typename f_t>
void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t, f_t>& problem_)
{
// Mark the problem as empty if the op_problem has an empty matrix.
if (problem_.get_constraint_matrix_values().is_empty()) { empty = true; }
if (problem_.get_constraint_matrix_values().is_empty()) {
cuopt_assert(problem_.get_constraint_matrix_indices().is_empty(),
"Problem is empty but constraint matrix indices are not empty.");
cuopt_assert(problem_.get_constraint_matrix_offsets().size() == 1,
"Problem is empty but constraint matrix offsets are not empty.");
cuopt_assert(problem_.get_constraint_lower_bounds().is_empty(),
"Problem is empty but constraint lower bounds are not empty.");
cuopt_assert(problem_.get_constraint_upper_bounds().is_empty(),
"Problem is empty but constraint upper bounds are not empty.");
empty = true;
}

// Set variables bounds to default if not set and constraints bounds if user has set a row type
set_bounds_if_not_set(*this);
Expand All @@ -63,6 +73,7 @@ void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t,
check_problem_representation(false, false);
// If maximization problem, convert the problem
if (maximize) convert_to_maximization_problem(*this);

const bool is_mip = original_problem_ptr->get_problem_category() != problem_category_t::LP;
if (is_mip) {
// Resize what is needed for MIP
Expand All @@ -74,6 +85,7 @@ void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t,
compute_n_integer_vars();
compute_binary_var_table();
}

compute_transpose_of_problem();
// Check after modifications
check_problem_representation(true, is_mip);
Expand Down Expand Up @@ -272,6 +284,17 @@ void problem_t<i_t, f_t>::compute_transpose_of_problem()
reverse_offsets.resize(n_variables + 1, handle_ptr->get_stream());
reverse_constraints.resize(nnz, handle_ptr->get_stream());
reverse_coefficients.resize(nnz, handle_ptr->get_stream());

// Special case if A is empty
// as cuSparse had a bug up until 12.9 causing cusparseCsr2cscEx2 to return incorrect results
// for empty matrices (CUSPARSE-2319)
// In this case, construct it manually
if (reverse_coefficients.is_empty()) {
thrust::fill(
handle_ptr->get_thrust_policy(), reverse_offsets.begin(), reverse_offsets.end(), 0);
return;
}

raft::sparse::linalg::csr_transpose(*handle_ptr,
offsets.data(),
variables.data(),
Expand Down Expand Up @@ -302,12 +325,12 @@ void problem_t<i_t, f_t>::check_problem_representation(bool check_transposed,
{
raft::common::nvtx::range scope("check_problem_representation");

// Presolve reductions might trivially solve the problem to optimality/infeasibility.
// In this case, it is exptected that the problem fields are empty.
cuopt_assert(!offsets.is_empty(), "A_offsets must never be empty.");
if (check_transposed) {
cuopt_assert(!reverse_offsets.is_empty(), "A_offsets must never be empty.");
}
// Presolve reductions might trivially solve the problem to optimality/infeasibility.
// In this case, it is exptected that the problem fields are empty.
if (!empty) {
// Check for empty fields
cuopt_assert(!coefficients.is_empty(), "A_values must be set before calling the solver.");
Expand All @@ -318,8 +341,9 @@ void problem_t<i_t, f_t>::check_problem_representation(bool check_transposed,
cuopt_assert(!reverse_constraints.is_empty(),
"A_indices must be set before calling the solver.");
}
cuopt_assert(!objective_coefficients.is_empty(), "c must be set before calling the solver.");
}
cuopt_assert(objective_coefficients.size() == n_variables,
"objective_coefficients size mismatch");

// Check CSR validity
check_csr_representation(
Expand Down Expand Up @@ -681,6 +705,7 @@ void problem_t<i_t, f_t>::recompute_auxilliary_data(bool check_representation)
template <typename i_t, typename f_t>
void problem_t<i_t, f_t>::compute_n_integer_vars()
{
cuopt_assert(n_variables == variable_types.size(), "size mismatch");
integer_indices.resize(n_variables, handle_ptr->get_stream());
auto end =
thrust::copy_if(handle_ptr->get_thrust_policy(),
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip/problem/problem_helpers.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ static void convert_to_maximization_problem(detail::problem_t<i_t, f_t>& op_prob
// negating objective coeffs
op_problem.presolve_data.objective_scaling_factor =
-op_problem.presolve_data.objective_scaling_factor;
op_problem.presolve_data.objective_offset = -op_problem.presolve_data.objective_offset;
}

/*
Expand Down
9 changes: 9 additions & 0 deletions cpp/src/mip/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ mip_solution_t<i_t, f_t> run_mip(detail::problem_t<i_t, f_t>& problem,
// if the input problem is empty: early exit
if (problem.empty) {
detail::solution_t<i_t, f_t> solution(problem);
problem.preprocess_problem();
thrust::for_each(problem.handle_ptr->get_thrust_policy(),
thrust::make_counting_iterator(0),
thrust::make_counting_iterator(problem.n_variables),
[sol = solution.assignment.data(), pb = problem.view()] __device__(i_t index) {
sol[index] = pb.objective_coefficients[index] > 0
? pb.variable_lower_bounds[index]
: pb.variable_upper_bounds[index];
});
problem.post_process_solution(solution);
solution.compute_objective(); // just to ensure h_user_obj is set
auto stats = solver_stats_t<i_t, f_t>{};
Expand Down
14 changes: 14 additions & 0 deletions cpp/tests/mip/empty_fixed_problems_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,18 @@ TEST(mip_solve, empty_problem_test)
EXPECT_NEAR(obj_val, 81, 1e-5);
}

TEST(mip_solve, empty_problem_with_objective_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/empty-problem-objective-vars.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_NEAR(obj_val, -2, 1e-5);
}

TEST(mip_solve, empty_max_problem_with_objective_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/empty-max-problem-objective-vars.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_NEAR(obj_val, 11, 1e-5);
}

} // namespace cuopt::linear_programming::test
7 changes: 7 additions & 0 deletions cpp/tests/mip/termination_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ TEST(termination_status, trivial_presolve_optimality_test)
EXPECT_EQ(obj_val, -1);
}

TEST(termination_status, trivial_presolve_no_obj_vars_test)
{
auto [termination_status, obj_val, lb] = test_mps_file("mip/trivial-presolve-no-obj-vars.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, 0);
}

TEST(termination_status, presolve_optimality_test)
{
auto [termination_status, obj_val, lb] = test_mps_file("mip/sudoku.mps");
Expand Down
17 changes: 17 additions & 0 deletions datasets/mip/empty-max-problem-objective-vars.mps
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
NAME
OBJSENSE
MAX
ROWS
N OBJ
COLUMNS
MARKER 'MARKER' 'INTORG'
x1 OBJ -2
x2 OBJ 5.5
MARKER 'MARKER' 'INTEND'
RHS
RANGES
BOUNDS
BV bounds x1
LI bounds x2 -2
UI bounds x2 2
ENDATA
12 changes: 12 additions & 0 deletions datasets/mip/empty-problem-objective-vars.mps
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
NAME
ROWS
N OBJ
COLUMNS
MARKER 'MARKER' 'INTORG'
x1 OBJ -2
MARKER 'MARKER' 'INTEND'
RHS
RANGES
BOUNDS
BV bounds x1
ENDATA
14 changes: 14 additions & 0 deletions datasets/mip/trivial-presolve-no-obj-vars.mps
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
NAME EXAMPLE
ROWS
N OBJ
E C1
COLUMNS
X1 C1 1
RHS
RHS1 C1 0
BOUNDS
LO BND1 X1 0
UP BND1 X1 1
LO BND1 X2 0
UP BND1 X2 1
ENDATA