Skip to content

Commit 64d2744

Browse files
authored
Merge branch 'main' into reuse-basis-factorization
2 parents a1caad8 + 5879493 commit 64d2744

File tree

13 files changed

+264
-45
lines changed

13 files changed

+264
-45
lines changed

benchmarks/linear_programming/utils/get_datasets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ def extract(file, dir, type):
693693
raise Exception(f"Unknown file extension found for extraction {file}")
694694
# download emps and compile
695695
# Disable emps for now
696-
if type == "netlib":
696+
if type == "netlib" and False:
697697
url = MittelmannInstances["emps"]
698698
file = os.path.join(dir, "emps.c")
699699
download(url, file)

cpp/src/dual_simplex/barrier.cu

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3575,6 +3575,9 @@ lp_status_t barrier_solver_t<i_t, f_t>::solve(f_t start_time,
35753575
} catch (const raft::cuda_error& e) {
35763576
settings.log.debug("Error in barrier_solver_t: %s\n", e.what());
35773577
return lp_status_t::NUMERICAL_ISSUES;
3578+
} catch (const rmm::out_of_memory& e) {
3579+
settings.log.debug("Out of memory in barrier_solver_t: %s\n", e.what());
3580+
return lp_status_t::NUMERICAL_ISSUES;
35783581
}
35793582
}
35803583

cpp/src/linear_programming/solve.cu

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <mip/presolve/third_party_presolve.hpp>
2929
#include <mip/presolve/trivial_presolve.cuh>
3030
#include <mip/solver.cuh>
31+
#include <mip/utilities/sort_csr.cuh>
3132

3233
#include <cuopt/linear_programming/pdlp/pdlp_hyper_params.cuh>
3334
#include <cuopt/linear_programming/pdlp/solver_settings.hpp>
@@ -444,7 +445,7 @@ optimization_problem_solution_t<i_t, f_t> run_barrier(
444445
{
445446
// Convert data structures to dual simplex format and back
446447
dual_simplex::user_problem_t<i_t, f_t> dual_simplex_problem =
447-
cuopt_problem_to_simplex_problem<i_t, f_t>(problem);
448+
cuopt_problem_to_simplex_problem<i_t, f_t>(problem.handle_ptr, problem);
448449
auto sol_dual_simplex = run_barrier(dual_simplex_problem, settings, timer);
449450
return convert_dual_simplex_sol(problem,
450451
std::get<0>(sol_dual_simplex),
@@ -515,7 +516,7 @@ optimization_problem_solution_t<i_t, f_t> run_dual_simplex(
515516
{
516517
// Convert data structures to dual simplex format and back
517518
dual_simplex::user_problem_t<i_t, f_t> dual_simplex_problem =
518-
cuopt_problem_to_simplex_problem<i_t, f_t>(problem);
519+
cuopt_problem_to_simplex_problem<i_t, f_t>(problem.handle_ptr, problem);
519520
auto sol_dual_simplex = run_dual_simplex(dual_simplex_problem, settings, timer);
520521
return convert_dual_simplex_sol(problem,
521522
std::get<0>(sol_dual_simplex),
@@ -671,16 +672,14 @@ optimization_problem_solution_t<i_t, f_t> run_concurrent(
671672
// Initialize the dual simplex structures before we run PDLP.
672673
// Otherwise, CUDA API calls to the problem stream may occur in both threads and throw graph
673674
// capture off
674-
auto barrier_handle = raft::handle_t(*op_problem.get_handle_ptr());
675-
detail::problem_t<i_t, f_t> d_barrier_problem(problem);
675+
auto barrier_handle = raft::handle_t(*op_problem.get_handle_ptr());
676676
rmm::cuda_stream_view barrier_stream = rmm::cuda_stream_per_thread;
677-
d_barrier_problem.handle_ptr = &barrier_handle;
678677
raft::resource::set_cuda_stream(barrier_handle, barrier_stream);
679678
// Make sure allocations are done on the original stream
680679
problem.handle_ptr->sync_stream();
681680

682681
dual_simplex::user_problem_t<i_t, f_t> dual_simplex_problem =
683-
cuopt_problem_to_simplex_problem<i_t, f_t>(d_barrier_problem);
682+
cuopt_problem_to_simplex_problem<i_t, f_t>(&barrier_handle, problem);
684683
// Create a thread for dual simplex
685684
std::unique_ptr<
686685
std::tuple<dual_simplex::lp_solution_t<i_t, f_t>, dual_simplex::lp_status_t, f_t, f_t, f_t>>
@@ -837,24 +836,24 @@ optimization_problem_solution_t<i_t, f_t> solve_lp(optimization_problem_t<i_t, f
837836
if (!run_presolve) { CUOPT_LOG_INFO("Third-party presolve is disabled, skipping"); }
838837

839838
if (run_presolve) {
839+
detail::sort_csr(op_problem);
840840
// allocate no more than 10% of the time limit to presolve.
841841
// Note that this is not the presolve time, but the time limit for presolve.
842842
// But no less than 1 second, to avoid early timeout triggering known crashes
843843
const double presolve_time_limit =
844844
std::max(1.0, std::min(0.1 * lp_timer.remaining_time(), 60.0));
845-
presolver = std::make_unique<detail::third_party_presolve_t<i_t, f_t>>();
846-
auto [reduced_problem, feasible] =
847-
presolver->apply(op_problem,
848-
cuopt::linear_programming::problem_category_t::LP,
849-
settings.dual_postsolve,
850-
settings.tolerances.absolute_primal_tolerance,
851-
settings.tolerances.relative_primal_tolerance,
852-
presolve_time_limit);
853-
if (!feasible) {
845+
presolver = std::make_unique<detail::third_party_presolve_t<i_t, f_t>>();
846+
auto result = presolver->apply(op_problem,
847+
cuopt::linear_programming::problem_category_t::LP,
848+
settings.dual_postsolve,
849+
settings.tolerances.absolute_primal_tolerance,
850+
settings.tolerances.relative_primal_tolerance,
851+
presolve_time_limit);
852+
if (!result.has_value()) {
854853
return optimization_problem_solution_t<i_t, f_t>(
855854
pdlp_termination_status_t::PrimalInfeasible, op_problem.get_handle_ptr()->get_stream());
856855
}
857-
problem = detail::problem_t<i_t, f_t>(reduced_problem);
856+
problem = detail::problem_t<i_t, f_t>(result->reduced_problem);
858857
presolve_time = lp_timer.elapsed_time();
859858
CUOPT_LOG_INFO("Papilo presolve time: %f", presolve_time);
860859
}

cpp/src/linear_programming/translate.hpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,21 @@ namespace cuopt::linear_programming {
2828

2929
template <typename i_t, typename f_t>
3030
static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
31-
detail::problem_t<i_t, f_t>& model)
31+
raft::handle_t const* handle_ptr, detail::problem_t<i_t, f_t>& model)
3232
{
33-
dual_simplex::user_problem_t<i_t, f_t> user_problem(model.handle_ptr);
33+
dual_simplex::user_problem_t<i_t, f_t> user_problem(handle_ptr);
3434

3535
int m = model.n_constraints;
3636
int n = model.n_variables;
3737
int nz = model.nnz;
3838
user_problem.num_rows = m;
3939
user_problem.num_cols = n;
40-
user_problem.objective = cuopt::host_copy(model.objective_coefficients);
40+
user_problem.objective = cuopt::host_copy(model.objective_coefficients, handle_ptr->get_stream());
4141

4242
dual_simplex::csr_matrix_t<i_t, f_t> csr_A(m, n, nz);
43-
csr_A.x = cuopt::host_copy(model.coefficients);
44-
csr_A.j = cuopt::host_copy(model.variables);
45-
csr_A.row_start = cuopt::host_copy(model.offsets);
43+
csr_A.x = cuopt::host_copy(model.coefficients, handle_ptr->get_stream());
44+
csr_A.j = cuopt::host_copy(model.variables, handle_ptr->get_stream());
45+
csr_A.row_start = cuopt::host_copy(model.offsets, handle_ptr->get_stream());
4646

4747
csr_A.to_compressed_col(user_problem.A);
4848

@@ -51,8 +51,10 @@ static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
5151
user_problem.range_rows.clear();
5252
user_problem.range_value.clear();
5353

54-
auto model_constraint_lower_bounds = cuopt::host_copy(model.constraint_lower_bounds);
55-
auto model_constraint_upper_bounds = cuopt::host_copy(model.constraint_upper_bounds);
54+
auto model_constraint_lower_bounds =
55+
cuopt::host_copy(model.constraint_lower_bounds, handle_ptr->get_stream());
56+
auto model_constraint_upper_bounds =
57+
cuopt::host_copy(model.constraint_upper_bounds, handle_ptr->get_stream());
5658

5759
// All constraints have lower and upper bounds
5860
// lr <= a_i^T x <= ur
@@ -79,7 +81,7 @@ static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
7981
}
8082
user_problem.num_range_rows = user_problem.range_rows.size();
8183
std::tie(user_problem.lower, user_problem.upper) =
82-
extract_host_bounds<f_t>(model.variable_bounds, model.handle_ptr);
84+
extract_host_bounds<f_t>(model.variable_bounds, handle_ptr);
8385
user_problem.problem_name = model.original_problem_ptr->get_problem_name();
8486
if (model.row_names.size() > 0) {
8587
user_problem.row_names.resize(m);
@@ -97,7 +99,7 @@ static dual_simplex::user_problem_t<i_t, f_t> cuopt_problem_to_simplex_problem(
9799
user_problem.obj_scale = model.presolve_data.objective_scaling_factor;
98100
user_problem.var_types.resize(n);
99101

100-
auto model_variable_types = cuopt::host_copy(model.variable_types);
102+
auto model_variable_types = cuopt::host_copy(model.variable_types, handle_ptr->get_stream());
101103
for (int j = 0; j < n; ++j) {
102104
user_problem.var_types[j] =
103105
model_variable_types[j] == var_t::CONTINUOUS

cpp/src/mip/presolve/third_party_presolve.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ void set_presolve_parameters(papilo::Presolve<f_t>& presolver,
391391
}
392392

393393
template <typename i_t, typename f_t>
394-
std::pair<optimization_problem_t<i_t, f_t>, bool> third_party_presolve_t<i_t, f_t>::apply(
394+
std::optional<third_party_presolve_result_t<i_t, f_t>> third_party_presolve_t<i_t, f_t>::apply(
395395
optimization_problem_t<i_t, f_t> const& op_problem,
396396
problem_category_t category,
397397
bool dual_postsolve,
@@ -423,7 +423,7 @@ std::pair<optimization_problem_t<i_t, f_t>, bool> third_party_presolve_t<i_t, f_
423423
check_presolve_status(result.status);
424424
if (result.status == papilo::PresolveStatus::kInfeasible ||
425425
result.status == papilo::PresolveStatus::kUnbndOrInfeas) {
426-
return std::make_pair(optimization_problem_t<i_t, f_t>(op_problem.get_handle_ptr()), false);
426+
return std::nullopt;
427427
}
428428
post_solve_storage_ = result.postsolve;
429429
CUOPT_LOG_INFO("Presolve removed: %d constraints, %d variables, %d nonzeros",
@@ -435,8 +435,16 @@ std::pair<optimization_problem_t<i_t, f_t>, bool> third_party_presolve_t<i_t, f_
435435
papilo_problem.getNCols(),
436436
papilo_problem.getConstraintMatrix().getNnz());
437437

438-
return std::make_pair(
439-
build_optimization_problem<i_t, f_t>(papilo_problem, op_problem.get_handle_ptr()), true);
438+
auto opt_problem =
439+
build_optimization_problem<i_t, f_t>(papilo_problem, op_problem.get_handle_ptr());
440+
auto col_flags = papilo_problem.getColFlags();
441+
std::vector<i_t> implied_integer_indices;
442+
for (size_t i = 0; i < col_flags.size(); i++) {
443+
if (col_flags[i].test(papilo::ColFlag::kImplInt)) implied_integer_indices.push_back(i);
444+
}
445+
446+
return std::make_optional(
447+
third_party_presolve_result_t<i_t, f_t>{opt_problem, implied_integer_indices});
440448
}
441449

442450
template <typename i_t, typename f_t>

cpp/src/mip/presolve/third_party_presolve.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,25 @@
1717

1818
#pragma once
1919

20+
#include <optional>
21+
2022
#include <cuopt/linear_programming/optimization_problem.hpp>
2123

2224
namespace cuopt::linear_programming::detail {
2325

26+
template <typename i_t, typename f_t>
27+
struct third_party_presolve_result_t {
28+
optimization_problem_t<i_t, f_t> reduced_problem;
29+
std::vector<i_t> implied_integer_indices;
30+
// clique info, etc...
31+
};
32+
2433
template <typename i_t, typename f_t>
2534
class third_party_presolve_t {
2635
public:
2736
third_party_presolve_t() = default;
2837

29-
std::pair<optimization_problem_t<i_t, f_t>, bool> apply(
38+
std::optional<third_party_presolve_result_t<i_t, f_t>> apply(
3039
optimization_problem_t<i_t, f_t> const& op_problem,
3140
problem_category_t category,
3241
bool dual_postsolve,

cpp/src/mip/problem/presolve_data.cuh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ class presolve_data_t {
3939
objective_offset(problem.get_objective_offset()),
4040
objective_scaling_factor(problem.get_objective_scaling_factor()),
4141
variable_mapping(0, stream),
42-
fixed_var_assignment(0, stream)
42+
fixed_var_assignment(0, stream),
43+
var_flags(0, stream)
4344
{
4445
}
4546

@@ -50,7 +51,8 @@ class presolve_data_t {
5051
objective_offset(other.objective_offset),
5152
objective_scaling_factor(other.objective_scaling_factor),
5253
variable_mapping(other.variable_mapping, stream),
53-
fixed_var_assignment(other.fixed_var_assignment, stream)
54+
fixed_var_assignment(other.fixed_var_assignment, stream),
55+
var_flags(other.var_flags, stream)
5456
{
5557
}
5658

@@ -86,6 +88,7 @@ class presolve_data_t {
8688

8789
rmm::device_uvector<i_t> variable_mapping;
8890
rmm::device_uvector<f_t> fixed_var_assignment;
91+
rmm::device_uvector<i_t> var_flags;
8992
};
9093

9194
} // namespace linear_programming::detail

cpp/src/mip/problem/problem.cu

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t,
8989
// If maximization problem, convert the problem
9090
if (maximize) convert_to_maximization_problem(*this);
9191
if (is_mip) {
92+
presolve_data.var_flags.resize(n_variables, handle_ptr->get_stream());
93+
thrust::fill(handle_ptr->get_thrust_policy(),
94+
presolve_data.var_flags.begin(),
95+
presolve_data.var_flags.end(),
96+
0);
9297
integer_indices.resize(n_variables, handle_ptr->get_stream());
9398
is_binary_variable.resize(n_variables, handle_ptr->get_stream());
9499
compute_n_integer_vars();
@@ -190,6 +195,7 @@ problem_t<i_t, f_t>::problem_t(const problem_t<i_t, f_t>& problem_)
190195
objective_name(problem_.objective_name),
191196
is_scaled_(problem_.is_scaled_),
192197
preprocess_called(problem_.preprocess_called),
198+
objective_is_integral(problem_.objective_is_integral),
193199
lp_state(problem_.lp_state),
194200
fixing_helpers(problem_.fixing_helpers, handle_ptr),
195201
vars_with_objective_coeffs(problem_.vars_with_objective_coeffs),
@@ -284,6 +290,7 @@ problem_t<i_t, f_t>::problem_t(const problem_t<i_t, f_t>& problem_, bool no_deep
284290
objective_name(problem_.objective_name),
285291
is_scaled_(problem_.is_scaled_),
286292
preprocess_called(problem_.preprocess_called),
293+
objective_is_integral(problem_.objective_is_integral),
287294
lp_state(problem_.lp_state),
288295
fixing_helpers(problem_.fixing_helpers, handle_ptr),
289296
vars_with_objective_coeffs(problem_.vars_with_objective_coeffs),
@@ -933,6 +940,8 @@ typename problem_t<i_t, f_t>::view_t problem_t<i_t, f_t>::view()
933940
v.variable_types = raft::device_span<var_t>{variable_types.data(), variable_types.size()};
934941
v.is_binary_variable =
935942
raft::device_span<i_t>{is_binary_variable.data(), is_binary_variable.size()};
943+
v.var_flags =
944+
raft::device_span<i_t>{presolve_data.var_flags.data(), presolve_data.var_flags.size()};
936945
v.related_variables = raft::device_span<i_t>{related_variables.data(), related_variables.size()};
937946
v.related_variables_offsets =
938947
raft::device_span<i_t>{related_variables_offsets.data(), related_variables_offsets.size()};
@@ -953,6 +962,7 @@ void problem_t<i_t, f_t>::resize_variables(size_t size)
953962
variable_types.resize(size, handle_ptr->get_stream());
954963
objective_coefficients.resize(size, handle_ptr->get_stream());
955964
is_binary_variable.resize(size, handle_ptr->get_stream());
965+
presolve_data.var_flags.resize(size, handle_ptr->get_stream()); // 0 is default - no flag
956966
related_variables_offsets.resize(size, handle_ptr->get_stream());
957967
}
958968
@@ -1060,6 +1070,32 @@ void problem_t<i_t, f_t>::insert_constraints(constraints_delta_t<i_t, f_t>& h_co
10601070
combine_constraint_bounds<i_t, f_t>(*this, combined_bounds);
10611071
}
10621072
1073+
template <typename i_t, typename f_t>
1074+
void problem_t<i_t, f_t>::set_implied_integers(const std::vector<i_t>& implied_integer_indices)
1075+
{
1076+
raft::common::nvtx::range fun_scope("set_implied_integers");
1077+
auto d_indices = cuopt::device_copy(implied_integer_indices, handle_ptr->get_stream());
1078+
print("implied integer indices", d_indices);
1079+
thrust::for_each(handle_ptr->get_thrust_policy(),
1080+
d_indices.begin(),
1081+
d_indices.end(),
1082+
[var_flags = make_span(presolve_data.var_flags),
1083+
var_types = make_span(variable_types)] __device__(i_t idx) {
1084+
cuopt_assert(idx < var_flags.size(), "Index out of bounds");
1085+
cuopt_assert(var_types[idx] == var_t::CONTINUOUS, "Variable is integer");
1086+
var_flags[idx] |= (i_t)VAR_IMPLIED_INTEGER;
1087+
});
1088+
objective_is_integral = thrust::all_of(handle_ptr->get_thrust_policy(),
1089+
thrust::make_counting_iterator(0),
1090+
thrust::make_counting_iterator(n_variables),
1091+
[v = view()] __device__(i_t var_idx) {
1092+
if (v.objective_coefficients[var_idx] == 0) return true;
1093+
return v.is_integer(v.objective_coefficients[var_idx]) &&
1094+
(v.variable_types[var_idx] == var_t::INTEGER ||
1095+
(v.var_flags[var_idx] & VAR_IMPLIED_INTEGER));
1096+
});
1097+
}
1098+
10631099
template <typename i_t, typename f_t>
10641100
void problem_t<i_t, f_t>::fix_given_variables(problem_t<i_t, f_t>& original_problem,
10651101
rmm::device_uvector<f_t>& assignment,
@@ -1249,6 +1285,13 @@ void problem_t<i_t, f_t>::remove_given_variables(problem_t<i_t, f_t>& original_p
12491285
original_problem.variable_types.begin(),
12501286
variable_types.begin());
12511287
variable_types.resize(variable_map.size(), handle_ptr->get_stream());
1288+
// keep implied-integer and other flags consistent with new variable set
1289+
thrust::gather(handle_ptr->get_thrust_policy(),
1290+
variable_map.begin(),
1291+
variable_map.end(),
1292+
original_problem.presolve_data.var_flags.begin(),
1293+
presolve_data.var_flags.begin());
1294+
presolve_data.var_flags.resize(variable_map.size(), handle_ptr->get_stream());
12521295
const i_t TPB = 64;
12531296
// compute new offsets
12541297
compute_new_offsets<i_t, f_t><<<variable_map.size(), TPB, 0, handle_ptr->get_stream()>>>(

cpp/src/mip/problem/problem.cuh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class problem_t {
9191

9292
void insert_variables(variables_delta_t<i_t, f_t>& h_vars);
9393
void insert_constraints(constraints_delta_t<i_t, f_t>& h_constraints);
94+
void set_implied_integers(const std::vector<i_t>& implied_integer_indices);
9495
void resize_variables(size_t size);
9596
void resize_constraints(size_t matrix_size, size_t constraint_size, size_t var_size);
9697
void preprocess_problem();
@@ -100,6 +101,7 @@ class problem_t {
100101
void post_process_solution(solution_t<i_t, f_t>& solution);
101102
void compute_transpose_of_problem();
102103
f_t get_user_obj_from_solver_obj(f_t solver_obj) const;
104+
bool is_objective_integral() const { return objective_is_integral; }
103105
void compute_integer_fixed_problem();
104106
void fill_integer_fixed_problem(rmm::device_uvector<f_t>& assignment,
105107
const raft::handle_t* handle_ptr);
@@ -116,6 +118,10 @@ class problem_t {
116118
void compute_vars_with_objective_coeffs();
117119
void test_problem_fixing_time();
118120

121+
enum var_flags_t : i_t {
122+
VAR_IMPLIED_INTEGER = 1 << 0,
123+
};
124+
119125
struct view_t {
120126
HDI std::pair<i_t, i_t> reverse_range_for_var(i_t v) const
121127
{
@@ -193,6 +199,7 @@ class problem_t {
193199
raft::device_span<f_t> constraint_upper_bounds;
194200
raft::device_span<var_t> variable_types;
195201
raft::device_span<i_t> is_binary_variable;
202+
raft::device_span<i_t> var_flags;
196203
raft::device_span<i_t> integer_indices;
197204
raft::device_span<i_t> binary_indices;
198205
raft::device_span<i_t> nonbinary_indices;
@@ -269,6 +276,7 @@ class problem_t {
269276
f_t objective_offset;
270277
bool is_scaled_{false};
271278
bool preprocess_called{false};
279+
bool objective_is_integral{false};
272280
// this LP state keeps the warm start data of some solution of
273281
// 1. Original problem: it is unchanged and part of it is used
274282
// to warm start slightly modified problems.

0 commit comments

Comments
 (0)