Skip to content

Commit e00e605

Browse files
committed
change semantics of problem construction in cuOptReadProblem
since the mps data model is constructed internally, use move semantics when building a CPU problem to avoid a copy
1 parent 1762a54 commit e00e605

6 files changed

Lines changed: 228 additions & 18 deletions

File tree

cpp/cuopt_cli.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ int run_single_file(const std::string& file_path,
138138
std::make_unique<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>>();
139139
}
140140

141-
cuopt::mathematical_optimization::populate_from_mps_data_model(problem_interface.get(),
142-
mps_data_model);
141+
cuopt::mathematical_optimization::adopt_from_mps_data_model(problem_interface.get(),
142+
std::move(mps_data_model));
143143

144144
const bool is_mip = (problem_interface->get_problem_category() ==
145145
cuopt::mathematical_optimization::problem_category_t::MIP ||
@@ -152,7 +152,7 @@ int run_single_file(const std::string& file_path,
152152
initial_solution_file.empty()
153153
? std::vector<double>()
154154
: cuopt::mathematical_optimization::solution_reader_t::get_variable_values_from_sol_file(
155-
initial_solution_file, mps_data_model.get_variable_names());
155+
initial_solution_file, problem_interface->get_variable_names());
156156

157157
if (is_mip) {
158158
auto& mip_settings = settings.get_mip_settings();

cpp/include/cuopt/mathematical_optimization/cpu_optimization_problem.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121
namespace cuopt::mathematical_optimization {
2222

23+
namespace io {
24+
template <typename i_t, typename f_t>
25+
class mps_data_model_t;
26+
}
27+
2328
// Forward declarations
2429
template <typename i_t, typename f_t>
2530
class optimization_problem_t;
@@ -77,6 +82,13 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
7782
void set_variable_names(const std::vector<std::string>& variable_names) override;
7883
void set_row_names(const std::vector<std::string>& row_names) override;
7984

85+
/**
86+
* @brief Transfer parsed MPS/QPS storage into this CPU problem without copying array/string data.
87+
*
88+
* The model is left in a moved-from state and must not be used afterward.
89+
*/
90+
void adopt_from_mps_data_model(io::mps_data_model_t<i_t, f_t>&& data_model);
91+
8092
// Device getters - throw exceptions (not supported for CPU implementation)
8193
i_t get_n_variables() const override;
8294
i_t get_n_constraints() const override;

cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,27 @@ void populate_from_mps_data_model(optimization_problem_interface_t<i_t, f_t>* pr
136136
}
137137
}
138138

139+
/**
140+
* @brief Transfer parsed MPS/QPS storage into a CPU-backed problem without copying payload arrays.
141+
*
142+
* For GPU-backed problems this falls back to populate_from_mps_data_model (copy/H2D path).
143+
*
144+
* @tparam i_t Integer type for indices
145+
* @tparam f_t Floating point type for values
146+
* @param[out] problem The optimization problem interface to populate
147+
* @param[in] data_model Parsed model; moved-from on return for CPU adopt
148+
*/
149+
template <typename i_t, typename f_t>
150+
void adopt_from_mps_data_model(optimization_problem_interface_t<i_t, f_t>* problem,
151+
io::mps_data_model_t<i_t, f_t>&& data_model)
152+
{
153+
if (auto* cpu_problem = dynamic_cast<cpu_optimization_problem_t<i_t, f_t>*>(problem)) {
154+
cpu_problem->adopt_from_mps_data_model(std::move(data_model));
155+
return;
156+
}
157+
populate_from_mps_data_model(problem, data_model);
158+
}
159+
139160
/**
140161
* @brief Helper function to populate optimization_problem_interface_t from data_model_view_t
141162
*

cpp/src/pdlp/cpu_optimization_problem.cpp

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <cuopt/error.hpp>
99
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
1010
#include <cuopt/mathematical_optimization/csr_matrix_utils.hpp>
11+
#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
1112
#include <cuopt/mathematical_optimization/optimization_problem.hpp>
1213
#include <cuopt/mathematical_optimization/optimization_problem_utils.hpp>
1314
#include <cuopt/mathematical_optimization/solve_remote.hpp>
@@ -25,6 +26,26 @@
2526

2627
namespace cuopt::mathematical_optimization {
2728

29+
namespace {
30+
31+
// Classify a problem as LP / MIP / IP from its (enum) variable types. Single source of truth
32+
// shared by set_variable_types() and adopt_from_mps_data_model() so the detection rule lives in
33+
// one place. Empty types (no variables declared) classify as LP, matching the populate path where
34+
// set_variable_types() is skipped and the category keeps its LP default.
35+
problem_category_t problem_category_from_variable_types(const std::vector<var_t>& variable_types)
36+
{
37+
if (variable_types.empty()) { return problem_category_t::LP; }
38+
const std::size_t n_discrete = static_cast<std::size_t>(
39+
std::count_if(variable_types.begin(), variable_types.end(), [](var_t v) {
40+
return v == var_t::INTEGER || v == var_t::SEMI_CONTINUOUS;
41+
}));
42+
if (n_discrete == variable_types.size()) { return problem_category_t::IP; }
43+
if (n_discrete > 0) { return problem_category_t::MIP; }
44+
return problem_category_t::LP;
45+
}
46+
47+
} // namespace
48+
2849
// ==============================================================================
2950
// Constructor
3051
// ==============================================================================
@@ -210,18 +231,7 @@ void cpu_optimization_problem_t<i_t, f_t>::set_variable_types(const var_t* varia
210231
variable_types_.resize(size);
211232
std::copy(variable_types, variable_types + size, variable_types_.begin());
212233

213-
// Auto-detect problem category based on variable types (matching original optimization_problem_t)
214-
i_t n_discrete = std::count_if(variable_types_.begin(), variable_types_.end(), [](auto val) {
215-
return val == var_t::INTEGER || val == var_t::SEMI_CONTINUOUS;
216-
});
217-
// By default it is LP
218-
if (n_discrete == size) {
219-
problem_category_ = problem_category_t::IP;
220-
} else if (n_discrete > 0) {
221-
problem_category_ = problem_category_t::MIP;
222-
} else {
223-
problem_category_ = problem_category_t::LP;
224-
}
234+
problem_category_ = problem_category_from_variable_types(variable_types_);
225235
}
226236

227237
template <typename i_t, typename f_t>
@@ -1095,6 +1105,83 @@ void cpu_optimization_problem_t<i_t, f_t>::copy_variable_types_to_host(var_t* ou
10951105
std::copy(variable_types_.begin(), variable_types_.begin() + size, output);
10961106
}
10971107

1108+
// ==============================================================================
1109+
// adopt_from_mps_data_model
1110+
// ==============================================================================
1111+
1112+
namespace {
1113+
1114+
template <typename i_t, typename f_t>
1115+
void move_quadratic_constraints_from_model(
1116+
cpu_optimization_problem_t<i_t, f_t>& problem,
1117+
std::vector<typename io::mps_data_model_t<i_t, f_t>::quadratic_constraint_t>& model_constraints)
1118+
{
1119+
using model_qc_t = typename io::mps_data_model_t<i_t, f_t>::quadratic_constraint_t;
1120+
std::vector<typename cpu_optimization_problem_t<i_t, f_t>::quadratic_constraint_t> converted;
1121+
converted.reserve(model_constraints.size());
1122+
for (model_qc_t& qc : model_constraints) {
1123+
converted.push_back({qc.constraint_row_index,
1124+
std::move(qc.constraint_row_name),
1125+
qc.constraint_row_type,
1126+
std::move(qc.linear_values),
1127+
std::move(qc.linear_indices),
1128+
qc.rhs_value,
1129+
std::move(qc.rows),
1130+
std::move(qc.cols),
1131+
std::move(qc.vals)});
1132+
}
1133+
model_constraints.clear();
1134+
problem.set_quadratic_constraints(std::move(converted));
1135+
}
1136+
1137+
} // namespace
1138+
1139+
template <typename i_t, typename f_t>
1140+
void cpu_optimization_problem_t<i_t, f_t>::adopt_from_mps_data_model(
1141+
io::mps_data_model_t<i_t, f_t>&& model)
1142+
{
1143+
maximize_ = model.maximize_;
1144+
n_vars_ = model.n_vars_;
1145+
n_constraints_ = model.n_constraints_;
1146+
objective_scaling_factor_ = model.objective_scaling_factor_;
1147+
objective_offset_ = model.objective_offset_;
1148+
1149+
A_ = std::move(model.A_);
1150+
A_indices_ = std::move(model.A_indices_);
1151+
A_offsets_ = std::move(model.A_offsets_);
1152+
b_ = std::move(model.b_);
1153+
c_ = std::move(model.c_);
1154+
constraint_lower_bounds_ = std::move(model.constraint_lower_bounds_);
1155+
constraint_upper_bounds_ = std::move(model.constraint_upper_bounds_);
1156+
row_types_ = std::move(model.row_types_);
1157+
variable_lower_bounds_ = std::move(model.variable_lower_bounds_);
1158+
variable_upper_bounds_ = std::move(model.variable_upper_bounds_);
1159+
1160+
objective_name_ = std::move(model.objective_name_);
1161+
problem_name_ = std::move(model.problem_name_);
1162+
var_names_ = std::move(model.var_names_);
1163+
row_names_ = std::move(model.row_names_);
1164+
1165+
Q_values_ = std::move(model.Q_objective_values_);
1166+
Q_indices_ = std::move(model.Q_objective_indices_);
1167+
Q_offsets_ = std::move(model.Q_objective_offsets_);
1168+
1169+
variable_types_.resize(model.var_types_.size());
1170+
for (size_t i = 0; i < model.var_types_.size(); ++i) {
1171+
variable_types_[i] = char_to_var_type(model.var_types_[i]);
1172+
}
1173+
problem_category_ = problem_category_from_variable_types(variable_types_);
1174+
1175+
if (model.has_quadratic_constraints()) {
1176+
move_quadratic_constraints_from_model(*this, model.quadratic_constraints_);
1177+
}
1178+
1179+
model.var_types_.clear();
1180+
model.n_vars_ = 0;
1181+
model.n_constraints_ = 0;
1182+
model.nnz_ = 0;
1183+
}
1184+
10981185
// ==============================================================================
10991186
// Template instantiations matching optimization_problem_t
11001187
// ==============================================================================

cpp/src/pdlp/cuopt_c.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,8 @@ cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* pro
217217
}
218218
}
219219

220-
// Populate interface directly from MPS data model (avoids temporary GPU allocation)
221-
cuopt::mathematical_optimization::populate_from_mps_data_model(problem_and_stream->get_problem(),
222-
*mps_data_model_ptr);
220+
cuopt::mathematical_optimization::adopt_from_mps_data_model(problem_and_stream->get_problem(),
221+
std::move(*mps_data_model_ptr));
223222

224223
*problem_ptr = static_cast<cuOptOptimizationProblem>(problem_and_stream);
225224
return CUOPT_SUCCESS;

cpp/tests/linear_programming/unit_tests/solution_interface_test.cu

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,97 @@ End
424424
EXPECT_NEAR(problem.get_quadratic_constraints()[0].rhs_value, 0.5, 1e-9);
425425
}
426426

427+
// Build one CPU problem by copying the model (populate) and another by moving an equal model
428+
// (adopt), then assert the two are field-for-field identical. Guards the adopt path against
429+
// silently dropping or mis-moving any member.
430+
static void expect_adopt_matches_populate(const io::mps_data_model_t<int, double>& model_src)
431+
{
432+
io::mps_data_model_t<int, double> model_for_copy = model_src;
433+
io::mps_data_model_t<int, double> model_for_move = model_src;
434+
435+
cpu_optimization_problem_t<int, double> copied;
436+
populate_from_mps_data_model(&copied, model_for_copy);
437+
438+
cpu_optimization_problem_t<int, double> moved;
439+
adopt_from_mps_data_model(&moved, std::move(model_for_move));
440+
441+
// Scalars
442+
EXPECT_EQ(copied.get_n_variables(), moved.get_n_variables());
443+
EXPECT_EQ(copied.get_n_constraints(), moved.get_n_constraints());
444+
EXPECT_EQ(copied.get_nnz(), moved.get_nnz());
445+
EXPECT_EQ(copied.get_n_integers(), moved.get_n_integers());
446+
EXPECT_EQ(copied.get_problem_category(), moved.get_problem_category());
447+
EXPECT_EQ(copied.get_sense(), moved.get_sense());
448+
EXPECT_DOUBLE_EQ(copied.get_objective_offset(), moved.get_objective_offset());
449+
EXPECT_DOUBLE_EQ(copied.get_objective_scaling_factor(), moved.get_objective_scaling_factor());
450+
EXPECT_EQ(copied.has_quadratic_objective(), moved.has_quadratic_objective());
451+
EXPECT_EQ(copied.has_quadratic_constraints(), moved.has_quadratic_constraints());
452+
453+
// Numeric / char / string arrays
454+
EXPECT_EQ(copied.get_constraint_matrix_values_host(), moved.get_constraint_matrix_values_host());
455+
EXPECT_EQ(copied.get_constraint_matrix_indices_host(),
456+
moved.get_constraint_matrix_indices_host());
457+
EXPECT_EQ(copied.get_constraint_matrix_offsets_host(),
458+
moved.get_constraint_matrix_offsets_host());
459+
EXPECT_EQ(copied.get_constraint_bounds_host(), moved.get_constraint_bounds_host());
460+
EXPECT_EQ(copied.get_constraint_lower_bounds_host(), moved.get_constraint_lower_bounds_host());
461+
EXPECT_EQ(copied.get_constraint_upper_bounds_host(), moved.get_constraint_upper_bounds_host());
462+
EXPECT_EQ(copied.get_objective_coefficients_host(), moved.get_objective_coefficients_host());
463+
EXPECT_EQ(copied.get_variable_lower_bounds_host(), moved.get_variable_lower_bounds_host());
464+
EXPECT_EQ(copied.get_variable_upper_bounds_host(), moved.get_variable_upper_bounds_host());
465+
EXPECT_EQ(copied.get_row_types_host(), moved.get_row_types_host());
466+
EXPECT_EQ(copied.get_variable_types_host(), moved.get_variable_types_host());
467+
EXPECT_EQ(copied.get_variable_names(), moved.get_variable_names());
468+
EXPECT_EQ(copied.get_row_names(), moved.get_row_names());
469+
470+
// Quadratic objective (CSR)
471+
EXPECT_EQ(copied.get_quadratic_objective_values(), moved.get_quadratic_objective_values());
472+
EXPECT_EQ(copied.get_quadratic_objective_indices(), moved.get_quadratic_objective_indices());
473+
EXPECT_EQ(copied.get_quadratic_objective_offsets(), moved.get_quadratic_objective_offsets());
474+
475+
// Quadratic constraints (compare size + per-row key fields)
476+
const auto& qc_copied = copied.get_quadratic_constraints();
477+
const auto& qc_moved = moved.get_quadratic_constraints();
478+
ASSERT_EQ(qc_copied.size(), qc_moved.size());
479+
for (size_t i = 0; i < qc_copied.size(); ++i) {
480+
EXPECT_EQ(qc_copied[i].constraint_row_name, qc_moved[i].constraint_row_name);
481+
EXPECT_EQ(qc_copied[i].constraint_row_type, qc_moved[i].constraint_row_type);
482+
EXPECT_DOUBLE_EQ(qc_copied[i].rhs_value, qc_moved[i].rhs_value);
483+
EXPECT_EQ(qc_copied[i].linear_values, qc_moved[i].linear_values);
484+
EXPECT_EQ(qc_copied[i].linear_indices, qc_moved[i].linear_indices);
485+
EXPECT_EQ(qc_copied[i].rows, qc_moved[i].rows);
486+
EXPECT_EQ(qc_copied[i].cols, qc_moved[i].cols);
487+
EXPECT_EQ(qc_copied[i].vals, qc_moved[i].vals);
488+
}
489+
490+
// The moved-from model must be left empty (adopt consumed it).
491+
EXPECT_EQ(model_for_move.get_n_variables(), 0);
492+
EXPECT_EQ(model_for_move.get_n_constraints(), 0);
493+
}
494+
495+
// adopt_from_mps_data_model must produce a problem identical to populate_from_mps_data_model.
496+
TEST_F(SolutionInterfaceTest, adopt_matches_populate_lp_file)
497+
{
498+
const auto model = io::read_mps<int, double>(lp_file_);
499+
expect_adopt_matches_populate(model);
500+
}
501+
502+
TEST_F(SolutionInterfaceTest, adopt_matches_populate_quadratic_constraints)
503+
{
504+
const auto model = io::read_lp_from_string<int, double>(R"LP(
505+
Minimize
506+
obj: x + y
507+
Subject To
508+
q0: [ 4 x * y ] <= 0.5
509+
Bounds
510+
-1 <= x <= 1
511+
-1 <= y <= 1
512+
End
513+
)LP");
514+
ASSERT_TRUE(model.has_quadratic_constraints());
515+
expect_adopt_matches_populate(model);
516+
}
517+
427518
// =============================================================================
428519
// Solution conversion tests (hand-constructed, known values)
429520
// =============================================================================

0 commit comments

Comments
 (0)