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
2 changes: 1 addition & 1 deletion benchmarks/linear_programming/cuopt/benchmark_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#pragma once

#include <cuopt/linear_programming/optimization_problem.hpp>
#include <cuopt/linear_programming/optimization_problem_interface.hpp>
#include <cuopt/linear_programming/pdlp/pdlp_hyper_params.cuh>
#include <cuopt/linear_programming/pdlp/solver_solution.hpp>
#include <cuopt/linear_programming/solve.hpp>
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/linear_programming/cuopt/run_mip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <cstdio>
#include <cuopt/linear_programming/mip/solver_settings.hpp>
#include <cuopt/linear_programming/mip/solver_solution.hpp>
#include <cuopt/linear_programming/optimization_problem.hpp>
#include <cuopt/linear_programming/optimization_problem_interface.hpp>
#include <cuopt/linear_programming/solve.hpp>
#include <mps_parser/parser.hpp>
#include <utilities/logger.hpp>
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/linear_programming/cuopt/run_pdlp.cu
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
/* clang-format on */

#include <cuopt/linear_programming/optimization_problem.hpp>
#include <cuopt/linear_programming/optimization_problem_interface.hpp>
#include <cuopt/linear_programming/pdlp/solver_solution.hpp>
#include <cuopt/linear_programming/solve.hpp>
#include <cuopt/linear_programming/solver_settings.hpp>
Expand Down
6 changes: 3 additions & 3 deletions cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
/* clang-format on */

#include <cuopt/linear_programming/backend_selection.hpp>
#include <cuopt/linear_programming/cpu_optimization_problem.hpp>
#include <cuopt/linear_programming/mip/solver_settings.hpp>
#include <cuopt/linear_programming/optimization_problem.hpp>
#include <cuopt/linear_programming/optimization_problem_interface.hpp>
#include <cuopt/linear_programming/optimization_problem_utils.hpp>
#include <cuopt/linear_programming/solve.hpp>
#include <mps_parser/parser.hpp>
Expand Down Expand Up @@ -134,11 +134,11 @@ int run_single_file(const std::string& file_path,
if (memory_backend == cuopt::linear_programming::memory_backend_t::GPU) {
handle_ptr = std::make_unique<raft::handle_t>();
problem_interface =
std::make_unique<cuopt::linear_programming::gpu_optimization_problem_t<int, double>>(
std::make_unique<cuopt::linear_programming::optimization_problem_t<int, double>>(
handle_ptr.get());
} else {
problem_interface =
std::make_unique<cuopt::linear_programming::cpu_optimization_problem_t<int, double>>(nullptr);
std::make_unique<cuopt::linear_programming::cpu_optimization_problem_t<int, double>>();
}

// Populate the problem from MPS data model
Expand Down
11 changes: 3 additions & 8 deletions cpp/include/cuopt/linear_programming/backend_selection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ bool is_remote_execution_enabled();
*/
execution_mode_t get_execution_mode();

/**
* @brief Check if GPU memory should be used for remote execution
* @return true if CUOPT_USE_GPU_MEM_FOR_REMOTE is set to "true" or "1" (case-insensitive)
*/
bool use_gpu_memory_for_remote();

/**
* @brief Check if CPU memory should be used for local execution (test mode)
*
Expand All @@ -59,8 +53,9 @@ bool use_cpu_memory_for_local();
* @brief Determine which memory backend to use based on execution mode
*
* Logic:
* - LOCAL execution -> GPU memory by default, CPU if CUOPT_USE_CPU_MEM_FOR_LOCAL=true (test mode)
* - REMOTE execution -> CPU memory by default, GPU if CUOPT_USE_GPU_MEM_FOR_REMOTE=true
* - REMOTE execution -> always CPU memory
* - LOCAL execution -> GPU memory by default, CPU if CUOPT_USE_CPU_MEM_FOR_LOCAL=true (test
* mode)
*
* @return memory_backend_t::GPU or memory_backend_t::CPU
*/
Expand Down
201 changes: 201 additions & 0 deletions cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

#include <cuopt/linear_programming/optimization_problem_interface.hpp>

#include <raft/core/handle.hpp>
#include <rmm/device_uvector.hpp>

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

namespace cuopt::linear_programming {

// Forward declarations
template <typename i_t, typename f_t>
class optimization_problem_t;
template <typename i_t, typename f_t>
class pdlp_solver_settings_t;
template <typename i_t, typename f_t>
class mip_solver_settings_t;
template <typename i_t, typename f_t>
class lp_solution_interface_t;
template <typename i_t, typename f_t>
class mip_solution_interface_t;

/**
* @brief CPU-based implementation of optimization_problem_interface_t.
*
* This implementation stores all data in CPU memory using std::vector.
* It only implements host getters (returning std::vector by value).
* Device getters throw exceptions as GPU memory access is not supported.
*/
template <typename i_t, typename f_t>
class cpu_optimization_problem_t : public optimization_problem_interface_t<i_t, f_t> {
public:
cpu_optimization_problem_t();

// Setters
void set_maximize(bool maximize) override;
void set_csr_constraint_matrix(const f_t* A_values,
i_t size_values,
const i_t* A_indices,
i_t size_indices,
const i_t* A_offsets,
i_t size_offsets) override;
void set_constraint_bounds(const f_t* b, i_t size) override;
void set_objective_coefficients(const f_t* c, i_t size) override;
void set_objective_scaling_factor(f_t objective_scaling_factor) override;
void set_objective_offset(f_t objective_offset) override;
void set_quadratic_objective_matrix(const f_t* Q_values,
i_t size_values,
const i_t* Q_indices,
i_t size_indices,
const i_t* Q_offsets,
i_t size_offsets,
bool validate_positive_semi_definite = false) override;
void set_variable_lower_bounds(const f_t* variable_lower_bounds, i_t size) override;
void set_variable_upper_bounds(const f_t* variable_upper_bounds, i_t size) override;
void set_variable_types(const var_t* variable_types, i_t size) override;
void set_problem_category(const problem_category_t& category) override;
void set_constraint_lower_bounds(const f_t* constraint_lower_bounds, i_t size) override;
void set_constraint_upper_bounds(const f_t* constraint_upper_bounds, i_t size) override;
void set_row_types(const char* row_types, i_t size) override;
void set_objective_name(const std::string& objective_name) override;
void set_problem_name(const std::string& problem_name) override;
void set_variable_names(const std::vector<std::string>& variable_names) override;
void set_row_names(const std::vector<std::string>& row_names) override;

// Device getters - throw exceptions (not supported for CPU implementation)
i_t get_n_variables() const override;
i_t get_n_constraints() const override;
i_t get_nnz() const override;
i_t get_n_integers() const override;
const rmm::device_uvector<f_t>& get_constraint_matrix_values() const override;
rmm::device_uvector<f_t>& get_constraint_matrix_values() override;
const rmm::device_uvector<i_t>& get_constraint_matrix_indices() const override;
rmm::device_uvector<i_t>& get_constraint_matrix_indices() override;
const rmm::device_uvector<i_t>& get_constraint_matrix_offsets() const override;
rmm::device_uvector<i_t>& get_constraint_matrix_offsets() override;
const rmm::device_uvector<f_t>& get_constraint_bounds() const override;
rmm::device_uvector<f_t>& get_constraint_bounds() override;
const rmm::device_uvector<f_t>& get_objective_coefficients() const override;
rmm::device_uvector<f_t>& get_objective_coefficients() override;
f_t get_objective_scaling_factor() const override;
f_t get_objective_offset() const override;
const rmm::device_uvector<f_t>& get_variable_lower_bounds() const override;
rmm::device_uvector<f_t>& get_variable_lower_bounds() override;
const rmm::device_uvector<f_t>& get_variable_upper_bounds() const override;
rmm::device_uvector<f_t>& get_variable_upper_bounds() override;
const rmm::device_uvector<f_t>& get_constraint_lower_bounds() const override;
rmm::device_uvector<f_t>& get_constraint_lower_bounds() override;
const rmm::device_uvector<f_t>& get_constraint_upper_bounds() const override;
rmm::device_uvector<f_t>& get_constraint_upper_bounds() override;
const rmm::device_uvector<char>& get_row_types() const override;
const rmm::device_uvector<var_t>& get_variable_types() const override;
bool get_sense() const override;
bool empty() const override;
std::string get_objective_name() const override;
std::string get_problem_name() const override;
problem_category_t get_problem_category() const override;
const std::vector<std::string>& get_variable_names() const override;
const std::vector<std::string>& get_row_names() const override;
const std::vector<i_t>& get_quadratic_objective_offsets() const override;
const std::vector<i_t>& get_quadratic_objective_indices() const override;
const std::vector<f_t>& get_quadratic_objective_values() const override;
bool has_quadratic_objective() const override;

// Host getters - these are the only supported getters for CPU implementation
std::vector<f_t> get_constraint_matrix_values_host() const override;
std::vector<i_t> get_constraint_matrix_indices_host() const override;
std::vector<i_t> get_constraint_matrix_offsets_host() const override;
std::vector<f_t> get_constraint_bounds_host() const override;
std::vector<f_t> get_objective_coefficients_host() const override;
std::vector<f_t> get_variable_lower_bounds_host() const override;
std::vector<f_t> get_variable_upper_bounds_host() const override;
std::vector<f_t> get_constraint_lower_bounds_host() const override;
std::vector<f_t> get_constraint_upper_bounds_host() const override;
std::vector<char> get_row_types_host() const override;
std::vector<var_t> get_variable_types_host() const override;

/**
* @brief Convert this CPU optimization problem to an optimization_problem_t
* by copying CPU data to GPU (requires GPU memory transfer).
*
* @param handle_ptr RAFT handle with CUDA resources for GPU memory allocation.
* @return unique_ptr to new optimization_problem_t with all data copied to GPU
* @throws std::runtime_error if handle_ptr is null
*/
std::unique_ptr<optimization_problem_t<i_t, f_t>> to_optimization_problem(
raft::handle_t const* handle_ptr = nullptr) override;

/**
* @brief Write the optimization problem to an MPS file.
* @param[in] mps_file_path Path to the output MPS file
*/
void write_to_mps(const std::string& mps_file_path) override;

/**
* @brief Check if this problem is equivalent to another problem.
* @param[in] other The other optimization problem to compare against
* @return true if the problems are equivalent (up to permutation of variables/constraints)
*/
bool is_equivalent(const optimization_problem_interface_t<i_t, f_t>& other) const override;

// C API support: Copy to host (polymorphic)
void copy_objective_coefficients_to_host(f_t* output, i_t size) const override;
void copy_constraint_matrix_to_host(f_t* values,
i_t* indices,
i_t* offsets,
i_t num_values,
i_t num_indices,
i_t num_offsets) const override;
void copy_row_types_to_host(char* output, i_t size) const override;
void copy_constraint_bounds_to_host(f_t* output, i_t size) const override;
void copy_constraint_lower_bounds_to_host(f_t* output, i_t size) const override;
void copy_constraint_upper_bounds_to_host(f_t* output, i_t size) const override;
void copy_variable_lower_bounds_to_host(f_t* output, i_t size) const override;
void copy_variable_upper_bounds_to_host(f_t* output, i_t size) const override;
void copy_variable_types_to_host(var_t* output, i_t size) const override;

private:
problem_category_t problem_category_ = problem_category_t::LP;
bool maximize_{false};
i_t n_vars_{0};
i_t n_constraints_{0};

// CPU memory storage
std::vector<f_t> A_;
std::vector<i_t> A_indices_;
std::vector<i_t> A_offsets_;
std::vector<f_t> b_;
std::vector<f_t> c_;
f_t objective_scaling_factor_{1};
f_t objective_offset_{0};

std::vector<i_t> Q_offsets_;
std::vector<i_t> Q_indices_;
std::vector<f_t> Q_values_;

std::vector<f_t> variable_lower_bounds_;
std::vector<f_t> variable_upper_bounds_;
std::vector<f_t> constraint_lower_bounds_;
std::vector<f_t> constraint_upper_bounds_;
std::vector<char> row_types_;
std::vector<var_t> variable_types_;

std::string objective_name_;
std::string problem_name_;
std::vector<std::string> var_names_{};
std::vector<std::string> row_names_{};
};

} // namespace cuopt::linear_programming
Original file line number Diff line number Diff line change
Expand Up @@ -238,59 +238,6 @@ class cpu_lp_solution_t : public lp_solution_interface_t<i_t, f_t> {
return pdlp_warm_start_data_.iterations_since_last_restart_;
}

/**
* @brief Convert CPU solution to GPU solution
* Copies data from host (std::vector) to device (rmm::device_uvector)
*/
optimization_problem_solution_t<i_t, f_t> to_gpu_solution(
rmm::cuda_stream_view stream_view) override
{
// Create device vectors and copy data from host
rmm::device_uvector<f_t> primal_device(primal_solution_.size(), stream_view);
rmm::device_uvector<f_t> dual_device(dual_solution_.size(), stream_view);
rmm::device_uvector<f_t> reduced_cost_device(reduced_cost_.size(), stream_view);

if (!primal_solution_.empty()) {
raft::copy(
primal_device.data(), primal_solution_.data(), primal_solution_.size(), stream_view);
}
if (!dual_solution_.empty()) {
raft::copy(dual_device.data(), dual_solution_.data(), dual_solution_.size(), stream_view);
}
if (!reduced_cost_.empty()) {
raft::copy(
reduced_cost_device.data(), reduced_cost_.data(), reduced_cost_.size(), stream_view);
}

using additional_info_t =
typename optimization_problem_solution_t<i_t, f_t>::additional_termination_information_t;
std::vector<additional_info_t> termination_stats(1);
termination_stats[0].primal_objective = primal_objective_;
termination_stats[0].dual_objective = dual_objective_;
termination_stats[0].solve_time = solve_time_;
termination_stats[0].l2_primal_residual = l2_primal_residual_;
termination_stats[0].l2_dual_residual = l2_dual_residual_;
termination_stats[0].gap = gap_;
termination_stats[0].number_of_steps_taken = num_iterations_;
termination_stats[0].solved_by_pdlp = solved_by_pdlp_;

std::vector<pdlp_termination_status_t> termination_status_vec = {termination_status_};

// Convert CPU warmstart to GPU warmstart
auto gpu_warmstart = convert_to_gpu_warmstart(pdlp_warm_start_data_, stream_view);

// Create GPU solution
return optimization_problem_solution_t<i_t, f_t>(primal_device,
dual_device,
reduced_cost_device,
std::move(gpu_warmstart),
"", // objective_name
{}, // var_names
{}, // row_names
std::move(termination_stats),
std::move(termination_status_vec));
}

/**
* @brief Convert to CPU-backed linear_programming_ret_t struct for Python/Cython
* Populates the cpu_solutions_t variant. Moves std::vector data with zero-copy.
Expand Down Expand Up @@ -414,39 +361,6 @@ class cpu_mip_solution_t : public mip_solution_interface_t<i_t, f_t> {

i_t get_num_simplex_iterations() const override { return num_simplex_iterations_; }

/**
* @brief Convert CPU solution to GPU solution
* Copies data from host (std::vector) to device (rmm::device_uvector)
*/
mip_solution_t<i_t, f_t> to_gpu_solution(rmm::cuda_stream_view stream_view) override
{
// Create device vector and copy data from host
rmm::device_uvector<f_t> solution_device(solution_.size(), stream_view);

if (!solution_.empty()) {
raft::copy(solution_device.data(), solution_.data(), solution_.size(), stream_view);
}

// Create solver stats
solver_stats_t<i_t, f_t> stats;
stats.total_solve_time = total_solve_time_;
stats.presolve_time = presolve_time_;
stats.solution_bound = solution_bound_;
stats.num_nodes = num_nodes_;
stats.num_simplex_iterations = num_simplex_iterations_;

// Create GPU solution
return mip_solution_t<i_t, f_t>(std::move(solution_device),
{}, // var_names
objective_,
mip_gap_,
termination_status_,
max_constraint_violation_,
max_int_violation_,
max_variable_bound_violation_,
stats);
}

/**
* @brief Convert to CPU-backed mip_ret_t struct for Python/Cython
* Populates the cpu_buffer variant. Moves std::vector data with zero-copy.
Expand Down
Loading