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
9 changes: 6 additions & 3 deletions cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,11 @@ int run_single_file(const std::string& file_path,
std::make_unique<cuopt::mathematical_optimization::cpu_optimization_problem_t<int, double>>();
}

cuopt::mathematical_optimization::populate_from_mps_data_model(problem_interface.get(),
mps_data_model);
// adopt_from_mps_data_model moves the parsed storage into a HOST-backed problem and falls back to
// a copy/H2D populate for a DEVICE-backed problem, so this single call is correct for either
// backend. Nothing reads mps_data_model afterward (names come from problem_interface below).
cuopt::mathematical_optimization::adopt_from_mps_data_model(problem_interface.get(),
std::move(mps_data_model));

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

if (is_mip) {
auto& mip_settings = settings.get_mip_settings();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
#include <string>
#include <vector>

namespace cuopt::mathematical_optimization::io {
template <typename i_t, typename f_t>
class mps_data_model_t;
} // namespace cuopt::mathematical_optimization::io

namespace cuopt::mathematical_optimization {

// Forward declarations
Expand Down Expand Up @@ -77,6 +82,13 @@ class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t,
void set_variable_names(const std::vector<std::string>& variable_names) override;
void set_row_names(const std::vector<std::string>& row_names) override;

/**
* @brief Transfer parsed MPS/QPS storage into this CPU problem without copying array/string data.
*
* The model is left in a moved-from state and must not be used afterward.
*/
void adopt_from_mps_data_model(io::mps_data_model_t<i_t, f_t>&& data_model);

// Device getters - throw exceptions (not supported for CPU implementation)
i_t get_n_variables() const override;
i_t get_n_constraints() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,27 @@ void populate_from_mps_data_model(optimization_problem_interface_t<i_t, f_t>* pr
}
}

/**
* @brief Transfer parsed MPS/QPS storage into a CPU-backed problem without copying payload arrays.
*
* For GPU-backed problems this falls back to populate_from_mps_data_model (copy/H2D path).
*
* @tparam i_t Integer type for indices
* @tparam f_t Floating point type for values
* @param[out] problem The optimization problem interface to populate
* @param[in] data_model Parsed model; moved-from on return for CPU adopt
*/
template <typename i_t, typename f_t>
void adopt_from_mps_data_model(optimization_problem_interface_t<i_t, f_t>* problem,
io::mps_data_model_t<i_t, f_t>&& data_model)
{
if (auto* cpu_problem = dynamic_cast<cpu_optimization_problem_t<i_t, f_t>*>(problem)) {
cpu_problem->adopt_from_mps_data_model(std::move(data_model));
return;
}
populate_from_mps_data_model(problem, data_model);
}

/**
* @brief Helper function to populate optimization_problem_interface_t from data_model_view_t
*
Expand Down
111 changes: 99 additions & 12 deletions cpp/src/pdlp/cpu_optimization_problem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cuopt/error.hpp>
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
#include <cuopt/mathematical_optimization/csr_matrix_utils.hpp>
#include <cuopt/mathematical_optimization/io/mps_data_model.hpp>
#include <cuopt/mathematical_optimization/optimization_problem.hpp>
#include <cuopt/mathematical_optimization/optimization_problem_utils.hpp>
#include <cuopt/mathematical_optimization/solve_remote.hpp>
Expand All @@ -25,6 +26,26 @@

namespace cuopt::mathematical_optimization {

namespace {

// Classify a problem as LP / MIP / IP from its (enum) variable types. Single source of truth
// shared by set_variable_types() and adopt_from_mps_data_model() so the detection rule lives in
// one place. Empty types (no variables declared) classify as LP, matching the populate path where
// set_variable_types() is skipped and the category keeps its LP default.
problem_category_t problem_category_from_variable_types(const std::vector<var_t>& variable_types)
{
if (variable_types.empty()) { return problem_category_t::LP; }
const std::size_t n_discrete = static_cast<std::size_t>(
std::count_if(variable_types.begin(), variable_types.end(), [](var_t v) {
return v == var_t::INTEGER || v == var_t::SEMI_CONTINUOUS;
}));
if (n_discrete == variable_types.size()) { return problem_category_t::IP; }
if (n_discrete > 0) { return problem_category_t::MIP; }
return problem_category_t::LP;
}

} // namespace

// ==============================================================================
// Constructor
// ==============================================================================
Expand Down Expand Up @@ -210,18 +231,7 @@ void cpu_optimization_problem_t<i_t, f_t>::set_variable_types(const var_t* varia
variable_types_.resize(size);
std::copy(variable_types, variable_types + size, variable_types_.begin());

// Auto-detect problem category based on variable types (matching original optimization_problem_t)
i_t n_discrete = std::count_if(variable_types_.begin(), variable_types_.end(), [](auto val) {
return val == var_t::INTEGER || val == var_t::SEMI_CONTINUOUS;
});
// By default it is LP
if (n_discrete == size) {
problem_category_ = problem_category_t::IP;
} else if (n_discrete > 0) {
problem_category_ = problem_category_t::MIP;
} else {
problem_category_ = problem_category_t::LP;
}
problem_category_ = problem_category_from_variable_types(variable_types_);
}

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

// ==============================================================================
// adopt_from_mps_data_model
// ==============================================================================

namespace {

template <typename i_t, typename f_t>
void move_quadratic_constraints_from_model(
cpu_optimization_problem_t<i_t, f_t>& problem,
std::vector<typename io::mps_data_model_t<i_t, f_t>::quadratic_constraint_t>& model_constraints)
{
using model_qc_t = typename io::mps_data_model_t<i_t, f_t>::quadratic_constraint_t;
std::vector<typename cpu_optimization_problem_t<i_t, f_t>::quadratic_constraint_t> converted;
converted.reserve(model_constraints.size());
for (model_qc_t& qc : model_constraints) {
converted.push_back({qc.constraint_row_index,
std::move(qc.constraint_row_name),
qc.constraint_row_type,
std::move(qc.linear_values),
std::move(qc.linear_indices),
qc.rhs_value,
std::move(qc.rows),
std::move(qc.cols),
std::move(qc.vals)});
}
model_constraints.clear();
problem.set_quadratic_constraints(std::move(converted));
}

} // namespace

template <typename i_t, typename f_t>
void cpu_optimization_problem_t<i_t, f_t>::adopt_from_mps_data_model(
io::mps_data_model_t<i_t, f_t>&& model)
{
maximize_ = model.maximize_;
n_vars_ = model.n_vars_;
n_constraints_ = model.n_constraints_;
objective_scaling_factor_ = model.objective_scaling_factor_;
objective_offset_ = model.objective_offset_;

A_ = std::move(model.A_);
A_indices_ = std::move(model.A_indices_);
A_offsets_ = std::move(model.A_offsets_);
b_ = std::move(model.b_);
c_ = std::move(model.c_);
constraint_lower_bounds_ = std::move(model.constraint_lower_bounds_);
constraint_upper_bounds_ = std::move(model.constraint_upper_bounds_);
row_types_ = std::move(model.row_types_);
variable_lower_bounds_ = std::move(model.variable_lower_bounds_);
variable_upper_bounds_ = std::move(model.variable_upper_bounds_);

objective_name_ = std::move(model.objective_name_);
problem_name_ = std::move(model.problem_name_);
var_names_ = std::move(model.var_names_);
row_names_ = std::move(model.row_names_);

Q_values_ = std::move(model.Q_objective_values_);
Q_indices_ = std::move(model.Q_objective_indices_);
Q_offsets_ = std::move(model.Q_objective_offsets_);

variable_types_.resize(model.var_types_.size());
for (size_t i = 0; i < model.var_types_.size(); ++i) {
variable_types_[i] = char_to_var_type(model.var_types_[i]);
}
problem_category_ = problem_category_from_variable_types(variable_types_);

if (model.has_quadratic_constraints()) {
move_quadratic_constraints_from_model(*this, model.quadratic_constraints_);
}

model.var_types_.clear();
model.n_vars_ = 0;
model.n_constraints_ = 0;
model.nnz_ = 0;
}

// ==============================================================================
// Template instantiations matching optimization_problem_t
// ==============================================================================
Expand Down
8 changes: 5 additions & 3 deletions cpp/src/pdlp/cuopt_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,11 @@ cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* pro
}
}

// Populate interface directly from MPS data model (avoids temporary GPU allocation)
cuopt::mathematical_optimization::populate_from_mps_data_model(problem_and_stream->get_problem(),
*mps_data_model_ptr);
// Transfer parsed payload into the problem. adopt_from_mps_data_model moves the model's storage
// into a HOST-backed problem and falls back to a copy/H2D populate for a DEVICE-backed problem,
// so the same call is correct regardless of the memory backend.
cuopt::mathematical_optimization::adopt_from_mps_data_model(problem_and_stream->get_problem(),
std::move(*mps_data_model_ptr));

*problem_ptr = static_cast<cuOptOptimizationProblem>(problem_and_stream);
return CUOPT_SUCCESS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,97 @@ End
EXPECT_NEAR(problem.get_quadratic_constraints()[0].rhs_value, 0.5, 1e-9);
}

// Build one CPU problem by copying the model (populate) and another by moving an equal model
// (adopt), then assert the two are field-for-field identical. Guards the adopt path against
// silently dropping or mis-moving any member.
static void expect_adopt_matches_populate(const io::mps_data_model_t<int, double>& model_src)
{
io::mps_data_model_t<int, double> model_for_copy = model_src;
io::mps_data_model_t<int, double> model_for_move = model_src;

cpu_optimization_problem_t<int, double> copied;
populate_from_mps_data_model(&copied, model_for_copy);

cpu_optimization_problem_t<int, double> moved;
adopt_from_mps_data_model(&moved, std::move(model_for_move));

// Scalars
EXPECT_EQ(copied.get_n_variables(), moved.get_n_variables());
EXPECT_EQ(copied.get_n_constraints(), moved.get_n_constraints());
EXPECT_EQ(copied.get_nnz(), moved.get_nnz());
EXPECT_EQ(copied.get_n_integers(), moved.get_n_integers());
EXPECT_EQ(copied.get_problem_category(), moved.get_problem_category());
EXPECT_EQ(copied.get_sense(), moved.get_sense());
EXPECT_DOUBLE_EQ(copied.get_objective_offset(), moved.get_objective_offset());
EXPECT_DOUBLE_EQ(copied.get_objective_scaling_factor(), moved.get_objective_scaling_factor());
EXPECT_EQ(copied.has_quadratic_objective(), moved.has_quadratic_objective());
EXPECT_EQ(copied.has_quadratic_constraints(), moved.has_quadratic_constraints());

// Numeric / char / string arrays
EXPECT_EQ(copied.get_constraint_matrix_values_host(), moved.get_constraint_matrix_values_host());
EXPECT_EQ(copied.get_constraint_matrix_indices_host(),
moved.get_constraint_matrix_indices_host());
EXPECT_EQ(copied.get_constraint_matrix_offsets_host(),
moved.get_constraint_matrix_offsets_host());
EXPECT_EQ(copied.get_constraint_bounds_host(), moved.get_constraint_bounds_host());
EXPECT_EQ(copied.get_constraint_lower_bounds_host(), moved.get_constraint_lower_bounds_host());
EXPECT_EQ(copied.get_constraint_upper_bounds_host(), moved.get_constraint_upper_bounds_host());
EXPECT_EQ(copied.get_objective_coefficients_host(), moved.get_objective_coefficients_host());
EXPECT_EQ(copied.get_variable_lower_bounds_host(), moved.get_variable_lower_bounds_host());
EXPECT_EQ(copied.get_variable_upper_bounds_host(), moved.get_variable_upper_bounds_host());
EXPECT_EQ(copied.get_row_types_host(), moved.get_row_types_host());
EXPECT_EQ(copied.get_variable_types_host(), moved.get_variable_types_host());
EXPECT_EQ(copied.get_variable_names(), moved.get_variable_names());
EXPECT_EQ(copied.get_row_names(), moved.get_row_names());

// Quadratic objective (CSR)
EXPECT_EQ(copied.get_quadratic_objective_values(), moved.get_quadratic_objective_values());
EXPECT_EQ(copied.get_quadratic_objective_indices(), moved.get_quadratic_objective_indices());
EXPECT_EQ(copied.get_quadratic_objective_offsets(), moved.get_quadratic_objective_offsets());

// Quadratic constraints (compare size + per-row key fields)
const auto& qc_copied = copied.get_quadratic_constraints();
const auto& qc_moved = moved.get_quadratic_constraints();
ASSERT_EQ(qc_copied.size(), qc_moved.size());
for (size_t i = 0; i < qc_copied.size(); ++i) {
EXPECT_EQ(qc_copied[i].constraint_row_name, qc_moved[i].constraint_row_name);
EXPECT_EQ(qc_copied[i].constraint_row_type, qc_moved[i].constraint_row_type);
EXPECT_DOUBLE_EQ(qc_copied[i].rhs_value, qc_moved[i].rhs_value);
EXPECT_EQ(qc_copied[i].linear_values, qc_moved[i].linear_values);
EXPECT_EQ(qc_copied[i].linear_indices, qc_moved[i].linear_indices);
EXPECT_EQ(qc_copied[i].rows, qc_moved[i].rows);
EXPECT_EQ(qc_copied[i].cols, qc_moved[i].cols);
EXPECT_EQ(qc_copied[i].vals, qc_moved[i].vals);
}

// The moved-from model must be left empty (adopt consumed it).
EXPECT_EQ(model_for_move.get_n_variables(), 0);
EXPECT_EQ(model_for_move.get_n_constraints(), 0);
}

// adopt_from_mps_data_model must produce a problem identical to populate_from_mps_data_model.
TEST_F(SolutionInterfaceTest, adopt_matches_populate_lp_file)
{
const auto model = cuopt::mathematical_optimization::io::read_mps<int, double>(lp_file_);
expect_adopt_matches_populate(model);
}

TEST_F(SolutionInterfaceTest, adopt_matches_populate_quadratic_constraints)
{
const auto model = cuopt::mathematical_optimization::io::read_lp_from_string<int, double>(R"LP(
Minimize
obj: x + y
Subject To
q0: [ 4 x * y ] <= 0.5
Bounds
-1 <= x <= 1
-1 <= y <= 1
End
)LP");
ASSERT_TRUE(model.has_quadratic_constraints());
expect_adopt_matches_populate(model);
}

// =============================================================================
// Solution conversion tests (hand-constructed, known values)
// =============================================================================
Expand Down
Loading