Skip to content
Open
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: 12 additions & 11 deletions cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,18 @@
#define CUOPT_MODE_DETERMINISTIC 1

/* @brief LP/MIP termination status constants */
#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0
#define CUOPT_TERIMINATION_STATUS_OPTIMAL 1
#define CUOPT_TERIMINATION_STATUS_INFEASIBLE 2
#define CUOPT_TERIMINATION_STATUS_UNBOUNDED 3
#define CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT 4
#define CUOPT_TERIMINATION_STATUS_TIME_LIMIT 5
#define CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR 6
#define CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE 7
#define CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND 8
#define CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT 9
#define CUOPT_TERIMINATION_STATUS_WORK_LIMIT 10
#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0
#define CUOPT_TERIMINATION_STATUS_OPTIMAL 1
#define CUOPT_TERIMINATION_STATUS_INFEASIBLE 2
#define CUOPT_TERIMINATION_STATUS_UNBOUNDED 3
#define CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT 4
#define CUOPT_TERIMINATION_STATUS_TIME_LIMIT 5
#define CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR 6
#define CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE 7
#define CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND 8
#define CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT 9
#define CUOPT_TERIMINATION_STATUS_WORK_LIMIT 10
#define CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE 11

/* @brief The objective sense constants */
#define CUOPT_MINIMIZE 1
Expand Down
15 changes: 8 additions & 7 deletions cpp/include/cuopt/linear_programming/mip/solver_solution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
namespace cuopt::linear_programming {

enum class mip_termination_status_t : int8_t {
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
FeasibleFound = CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND,
Infeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
Unbounded = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
WorkLimit = CUOPT_TERIMINATION_STATUS_WORK_LIMIT,
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
FeasibleFound = CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND,
Infeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
Unbounded = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
WorkLimit = CUOPT_TERIMINATION_STATUS_WORK_LIMIT,
UnboundedOrInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE,
};

template <typename i_t, typename f_t>
Expand Down
19 changes: 10 additions & 9 deletions cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ namespace cuopt::linear_programming {

// Possible reasons for terminating
enum class pdlp_termination_status_t : int8_t {
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
NumericalError = CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
PrimalInfeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
DualInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
IterationLimit = CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
PrimalFeasible = CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE,
ConcurrentLimit = CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT
NoTermination = CUOPT_TERIMINATION_STATUS_NO_TERMINATION,
NumericalError = CUOPT_TERIMINATION_STATUS_NUMERICAL_ERROR,
Optimal = CUOPT_TERIMINATION_STATUS_OPTIMAL,
PrimalInfeasible = CUOPT_TERIMINATION_STATUS_INFEASIBLE,
DualInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED,
IterationLimit = CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT,
TimeLimit = CUOPT_TERIMINATION_STATUS_TIME_LIMIT,
PrimalFeasible = CUOPT_TERIMINATION_STATUS_PRIMAL_FEASIBLE,
ConcurrentLimit = CUOPT_TERIMINATION_STATUS_CONCURRENT_LIMIT,
UnboundedOrInfeasible = CUOPT_TERIMINATION_STATUS_UNBOUNDED_OR_INFEASIBLE
};

/**
Expand Down
57 changes: 47 additions & 10 deletions cpp/src/mip_heuristics/presolve/third_party_presolve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,36 @@ void check_presolve_status(const papilo::PresolveStatus& status)
}
}

third_party_presolve_status_t convert_papilo_presolve_status_to_third_party_presolve_status(
const papilo::PresolveStatus& status)
{
switch (status) {
case papilo::PresolveStatus::kUnchanged: return third_party_presolve_status_t::UNCHANGED;
case papilo::PresolveStatus::kReduced: return third_party_presolve_status_t::REDUCED;
case papilo::PresolveStatus::kUnbndOrInfeas:
return third_party_presolve_status_t::UNBNDORINFEAS;
case papilo::PresolveStatus::kInfeasible: return third_party_presolve_status_t::INFEASIBLE;
case papilo::PresolveStatus::kUnbounded:
return third_party_presolve_status_t::UNBOUNDED;
// Do not implement default case to trigger compile time error if new enum is added
}
return third_party_presolve_status_t::UNCHANGED;
}

third_party_presolve_status_t convert_pslp_presolve_status_to_third_party_presolve_status(
const PresolveStatus& status)
{
switch (status) {
case PresolveStatus_::UNCHANGED: return third_party_presolve_status_t::UNCHANGED;
case PresolveStatus_::REDUCED: return third_party_presolve_status_t::REDUCED;
case PresolveStatus_::INFEASIBLE: return third_party_presolve_status_t::INFEASIBLE;
case PresolveStatus_::UNBNDORINFEAS:
return third_party_presolve_status_t::UNBNDORINFEAS;
// Do not implement default case to trigger compile time error if new enum is added
}
return third_party_presolve_status_t::UNCHANGED;
}

void check_postsolve_status(const papilo::PostsolveStatus& status)
{
switch (status) {
Expand Down Expand Up @@ -570,7 +600,7 @@ void set_presolve_parameters(papilo::Presolve<f_t>& presolver,
}

template <typename i_t, typename f_t>
std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_t, f_t>::apply_pslp(
third_party_presolve_result_t<i_t, f_t> third_party_presolve_t<i_t, f_t>::apply_pslp(
optimization_problem_t<i_t, f_t> const& op_problem, const double time_limit)
{
if constexpr (std::is_same_v<f_t, double>) {
Expand All @@ -584,23 +614,24 @@ std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_
pslp_presolver_ = ctx.presolver;
pslp_stgs_ = ctx.settings;

auto status = convert_pslp_presolve_status_to_third_party_presolve_status(ctx.status);
if (ctx.status == PresolveStatus_::INFEASIBLE || ctx.status == PresolveStatus_::UNBNDORINFEAS) {
return std::nullopt;
optimization_problem_t<i_t, f_t> empty_problem(op_problem.get_handle_ptr());
return third_party_presolve_result_t<i_t, f_t>{status, std::move(empty_problem), {}, {}, {}};
}

auto opt_problem = build_optimization_problem_from_pslp<i_t, f_t>(
pslp_presolver_, op_problem.get_handle_ptr(), maximize_, original_obj_offset);
opt_problem.set_problem_name(op_problem.get_problem_name());
return std::make_optional(third_party_presolve_result_t<i_t, f_t>{opt_problem, {}});
return third_party_presolve_result_t<i_t, f_t>{status, std::move(opt_problem), {}, {}, {}};
} else {
cuopt_expects(
false, error_type_t::ValidationError, "PSLP presolver only supports double precision");
return std::nullopt;
return third_party_presolve_result_t<i_t, f_t>{third_party_presolve_status_t::UNCHANGED, optimization_problem_t<i_t, f_t>(op_problem.get_handle_ptr()), {}, {}, {}};
}
}

template <typename i_t, typename f_t>
std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_t, f_t>::apply(
third_party_presolve_result_t<i_t, f_t> third_party_presolve_t<i_t, f_t>::apply(
optimization_problem_t<i_t, f_t> const& op_problem,
problem_category_t category,
cuopt::linear_programming::presolver_t presolver,
Expand Down Expand Up @@ -648,9 +679,12 @@ std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_

auto result = papilo_presolver.apply(papilo_problem);
check_presolve_status(result.status);
auto status = convert_papilo_presolve_status_to_third_party_presolve_status(result.status);
if (result.status == papilo::PresolveStatus::kInfeasible ||
result.status == papilo::PresolveStatus::kUnbndOrInfeas) {
return std::nullopt;
result.status == papilo::PresolveStatus::kUnbndOrInfeas ||
result.status == papilo::PresolveStatus::kUnbounded) {
optimization_problem_t<i_t, f_t> empty_problem(op_problem.get_handle_ptr());
return third_party_presolve_result_t<i_t, f_t>{status, std::move(empty_problem), {}, {}, {}};
}
papilo_post_solve_storage_.reset(new papilo::PostsolveStorage<f_t>(result.postsolve));
CUOPT_LOG_INFO("Presolve removed: %d constraints, %d variables, %d nonzeros",
Expand Down Expand Up @@ -686,8 +720,11 @@ std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_
}
}

return std::make_optional(third_party_presolve_result_t<i_t, f_t>{
opt_problem, implied_integer_indices, reduced_to_original_map_, original_to_reduced_map_});
return third_party_presolve_result_t<i_t, f_t>{status,
std::move(opt_problem),
implied_integer_indices,
reduced_to_original_map_,
original_to_reduced_map_};
}

template <typename i_t, typename f_t>
Expand Down
29 changes: 19 additions & 10 deletions cpp/src/mip_heuristics/presolve/third_party_presolve.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,18 @@ struct papilo_postsolve_deleter {
void operator()(papilo::PostsolveStorage<f_t>* ptr) const;
};

enum class third_party_presolve_status_t {
INFEASIBLE,
UNBNDORINFEAS,
UNBOUNDED,
OPTIMAL,
REDUCED,
UNCHANGED,
};

template <typename i_t, typename f_t>
struct third_party_presolve_result_t {
third_party_presolve_status_t status;
optimization_problem_t<i_t, f_t> reduced_problem;
std::vector<i_t> implied_integer_indices;
std::vector<i_t> reduced_to_original_map;
Expand All @@ -48,15 +58,14 @@ class third_party_presolve_t {
third_party_presolve_t(third_party_presolve_t&&) = delete;
third_party_presolve_t& operator=(third_party_presolve_t&&) = delete;

std::optional<third_party_presolve_result_t<i_t, f_t>> apply(
optimization_problem_t<i_t, f_t> const& op_problem,
problem_category_t category,
cuopt::linear_programming::presolver_t presolver,
bool dual_postsolve,
f_t absolute_tolerance,
f_t relative_tolerance,
double time_limit,
i_t num_cpu_threads = 0);
third_party_presolve_result_t<i_t, f_t> apply(optimization_problem_t<i_t, f_t> const& op_problem,
problem_category_t category,
cuopt::linear_programming::presolver_t presolver,
bool dual_postsolve,
f_t absolute_tolerance,
f_t relative_tolerance,
double time_limit,
i_t num_cpu_threads = 0);

void undo(rmm::device_uvector<f_t>& primal_solution,
rmm::device_uvector<f_t>& dual_solution,
Expand All @@ -74,7 +83,7 @@ class third_party_presolve_t {
~third_party_presolve_t();

private:
std::optional<third_party_presolve_result_t<i_t, f_t>> apply_pslp(
third_party_presolve_result_t<i_t, f_t> apply_pslp(
optimization_problem_t<i_t, f_t> const& op_problem, const double time_limit);

void undo_pslp(rmm::device_uvector<f_t>& primal_solution,
Expand Down
28 changes: 19 additions & 9 deletions cpp/src/mip_heuristics/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ mip_solution_t<i_t, f_t> solve_mip(optimization_problem_t<i_t, f_t>& op_problem,

double presolve_time = 0.0;
std::unique_ptr<detail::third_party_presolve_t<i_t, f_t>> presolver;
std::optional<detail::third_party_presolve_result_t<i_t, f_t>> presolve_result;
std::optional<detail::third_party_presolve_result_t<i_t, f_t>> presolve_result_opt;
detail::problem_t<i_t, f_t> problem(
op_problem, settings.get_tolerances(), settings.determinism_mode == CUOPT_MODE_DETERMINISTIC);

Expand Down Expand Up @@ -283,22 +283,32 @@ mip_solution_t<i_t, f_t> solve_mip(optimization_problem_t<i_t, f_t>& op_problem,
settings.tolerances.relative_tolerance,
presolve_time_limit,
settings.num_cpu_threads);
if (!result.has_value()) {
if (result.status == detail::third_party_presolve_status_t::INFEASIBLE) {
return mip_solution_t<i_t, f_t>(mip_termination_status_t::Infeasible,
solver_stats_t<i_t, f_t>{},
op_problem.get_handle_ptr()->get_stream());
}
presolve_result.emplace(std::move(*result));
if (result.status == detail::third_party_presolve_status_t::UNBNDORINFEAS) {
return mip_solution_t<i_t, f_t>(mip_termination_status_t::UnboundedOrInfeasible,
solver_stats_t<i_t, f_t>{},
op_problem.get_handle_ptr()->get_stream());
}
if (result.status == detail::third_party_presolve_status_t::UNBOUNDED) {
return mip_solution_t<i_t, f_t>(mip_termination_status_t::Unbounded,
solver_stats_t<i_t, f_t>{},
op_problem.get_handle_ptr()->get_stream());
}
presolve_result_opt.emplace(std::move(result));

problem = detail::problem_t<i_t, f_t>(presolve_result->reduced_problem);
problem = detail::problem_t<i_t, f_t>(presolve_result_opt->reduced_problem);
problem.set_papilo_presolve_data(presolver.get(),
presolve_result->reduced_to_original_map,
presolve_result->original_to_reduced_map,
presolve_result_opt->reduced_to_original_map,
presolve_result_opt->original_to_reduced_map,
op_problem.get_n_variables());
problem.set_implied_integers(presolve_result->implied_integer_indices);
problem.set_implied_integers(presolve_result_opt->implied_integer_indices);
presolve_time = timer.elapsed_time();
if (presolve_result->implied_integer_indices.size() > 0) {
CUOPT_LOG_INFO("%d implied integers", presolve_result->implied_integer_indices.size());
if (presolve_result_opt->implied_integer_indices.size() > 0) {
CUOPT_LOG_INFO("%d implied integers", presolve_result_opt->implied_integer_indices.size());
}
CUOPT_LOG_INFO("Papilo presolve time: %.2f", presolve_time);
}
Expand Down
5 changes: 3 additions & 2 deletions cpp/src/mip_heuristics/solver_solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ std::string mip_solution_t<i_t, f_t>::get_termination_status_string(
case mip_termination_status_t::Infeasible: return "Infeasible";
case mip_termination_status_t::TimeLimit: return "TimeLimit";
case mip_termination_status_t::WorkLimit: return "WorkLimit";
case mip_termination_status_t::Unbounded:
return "Unbounded";
case mip_termination_status_t::Unbounded: return "Unbounded";
case mip_termination_status_t::UnboundedOrInfeasible:
return "UnboundedOrInfeasible";
// Do not implement default case to trigger compile time error if new enum is added
}
return std::string();
Expand Down
13 changes: 11 additions & 2 deletions cpp/src/pdlp/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@ optimization_problem_solution_t<i_t, f_t> solve_lp(
auto run_presolve = settings.presolver != presolver_t::None;
run_presolve = run_presolve && settings.get_pdlp_warm_start_data().total_pdlp_iterations_ == -1;

// Declare result at outer scope so that result->reduced_problem (which may be
// Declare result at outer scope so that result.reduced_problem (which may be
// referenced by problem.original_problem_ptr) remains alive through the solve.
std::optional<detail::third_party_presolve_result_t<i_t, f_t>> result;

Expand All @@ -1380,10 +1380,19 @@ optimization_problem_solution_t<i_t, f_t> solve_lp(
settings.tolerances.absolute_primal_tolerance,
settings.tolerances.relative_primal_tolerance,
presolve_time_limit);
if (!result.has_value()) {
if (result->status == detail::third_party_presolve_status_t::INFEASIBLE) {
return optimization_problem_solution_t<i_t, f_t>(
pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
}
if (result->status == detail::third_party_presolve_status_t::UNBNDORINFEAS) {
return optimization_problem_solution_t<i_t, f_t>(
pdlp_termination_status_t::UnboundedOrInfeasible,
op_problem.get_handle_ptr()->get_stream());
}
if (result->status == detail::third_party_presolve_status_t::UNBOUNDED) {
return optimization_problem_solution_t<i_t, f_t>(pdlp_termination_status_t::DualInfeasible,
op_problem.get_handle_ptr()->get_stream());
}

// Handle case where presolve completely solved the problem (reduced to 0 rows/cols)
// Must check before constructing problem_t since it fails on empty problems
Expand Down
6 changes: 5 additions & 1 deletion cpp/src/pdlp/solver_solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,12 @@ std::string optimization_problem_solution_t<i_t, f_t>::get_termination_status_st
case pdlp_termination_status_t::NumericalError: return "A numerical error was encountered.";
case pdlp_termination_status_t::PrimalFeasible: return "Primal Feasible";
case pdlp_termination_status_t::ConcurrentLimit: return "Concurrent Limit";
default: return "Unknown cuOpt status";
case pdlp_termination_status_t::UnboundedOrInfeasible: return "UnboundedOrInfeasible";
case pdlp_termination_status_t::NoTermination:
return "NoTermination";
// Do not implement default case to trigger compile time error if new enum is added
}
return std::string();
}

template <typename i_t, typename f_t>
Expand Down
9 changes: 5 additions & 4 deletions cpp/tests/mip/presolve_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ TEST(problem, find_implied_integers)
1e-12,
20,
1);
ASSERT_TRUE(result.has_value());
ASSERT_NE(result.status, detail::third_party_presolve_status_t::INFEASIBLE);
ASSERT_NE(result.status, detail::third_party_presolve_status_t::UNBNDORINFEAS);

auto problem = detail::problem_t<int, double>(result->reduced_problem);
problem.set_implied_integers(result->implied_integer_indices);
ASSERT_TRUE(result->implied_integer_indices.size() > 0);
auto problem = detail::problem_t<int, double>(result.reduced_problem);
problem.set_implied_integers(result.implied_integer_indices);
ASSERT_TRUE(result.implied_integer_indices.size() > 0);
auto var_types = host_copy(problem.variable_types, handle_.get_stream());
// Find the index of the one continuous variable
auto it = std::find_if(var_types.begin(), var_types.end(), [](var_t var_type) {
Expand Down
Loading
Loading