From b6f656f4d4bdfe3e197ed2ffbdc311979606f6e5 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 25 Aug 2026 15:25:52 -0500 Subject: [PATCH] refactor: split host-only members out of CUDA translation units Several classes are mostly host code but live entirely in .cu files, which means anything needing their host-side members has to link the CUDA library. This separates them so the host halves compile as plain C++. math_optimization/solver_settings.cu -> .cpp + _gpu.cu (713 lines, 5 CUDA) mip_heuristics/solver_settings.cu -> .cu + .cpp (58 lines, 3 CUDA) pdlp/solution_conversion.cu -> + solution_conversion_cpu.cpp Each split follows one rule: host code moves to the .cpp, members taking an rmm::cuda_stream_view or returning a device_uvector stay in the .cu, and the moved members are instantiated explicitly per-member rather than via `template class`. The distinction matters -- `template class` in the .cpp would emit device ctors/dtors for members the host file cannot construct. The explicit instantiations are guarded on MIP_INSTANTIATE_* / PDLP_INSTANTIATE_*, so each new file includes mip_heuristics/mip_constants.hpp. Without it the guards evaluate false and the translation unit silently compiles to zero symbols. Also replaces thrust::count with std::count in solve_remote.cpp; it operates on a host vector, so thrust was gratuitous. No behaviour change: every moved definition is byte-identical, and all files still build into libcuopt exactly as before. Co-Authored-By: Claude Opus 5 Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/client/solve_remote.cpp | 5 +- cpp/src/math_optimization/CMakeLists.txt | 3 +- ...solver_settings.cu => solver_settings.cpp} | 80 ----------- .../math_optimization/solver_settings_gpu.cu | 135 ++++++++++++++++++ cpp/src/mip_heuristics/CMakeLists.txt | 1 + cpp/src/mip_heuristics/solver_settings.cpp | 67 +++++++++ cpp/src/mip_heuristics/solver_settings.cu | 23 --- cpp/src/pdlp/CMakeLists.txt | 1 + cpp/src/pdlp/solution_conversion.cu | 86 ----------- cpp/src/pdlp/solution_conversion_cpu.cpp | 112 +++++++++++++++ 10 files changed, 320 insertions(+), 193 deletions(-) rename cpp/src/math_optimization/{solver_settings.cu => solver_settings.cpp} (90%) create mode 100644 cpp/src/math_optimization/solver_settings_gpu.cu create mode 100644 cpp/src/mip_heuristics/solver_settings.cpp create mode 100644 cpp/src/pdlp/solution_conversion_cpu.cpp diff --git a/cpp/src/grpc/client/solve_remote.cpp b/cpp/src/grpc/client/solve_remote.cpp index eabea39e05..ff808a9fdd 100644 --- a/cpp/src/grpc/client/solve_remote.cpp +++ b/cpp/src/grpc/client/solve_remote.cpp @@ -14,6 +14,7 @@ #include #include "grpc_client.hpp" +#include #include #include #include @@ -22,8 +23,6 @@ #include #include -#include - namespace cuopt::mathematical_optimization { // Buffer added to the solver's time_limit to account for worker startup, @@ -152,7 +151,7 @@ std::unique_ptr> solve_mip_remote( auto mip_callbacks = settings.get_mip_callbacks(); const auto var_types = cpu_problem.get_variable_types_host(); const bool has_sc_variables = - thrust::count(var_types.begin(), var_types.end(), var_t::SEMI_CONTINUOUS) > 0; + std::count(var_types.begin(), var_types.end(), var_t::SEMI_CONTINUOUS) > 0; if (has_sc_variables && !mip_callbacks.empty()) { CUOPT_LOG_WARN( "Disabling remote MIP get/set callbacks: semi-continuous models are not " diff --git a/cpp/src/math_optimization/CMakeLists.txt b/cpp/src/math_optimization/CMakeLists.txt index efa1600c54..e6be4079d7 100644 --- a/cpp/src/math_optimization/CMakeLists.txt +++ b/cpp/src/math_optimization/CMakeLists.txt @@ -5,7 +5,8 @@ list(PREPEND MATH_OPT_SRC_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings_gpu.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_reader.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_writer.cu ${CMAKE_CURRENT_SOURCE_DIR}/tic_toc.cpp diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cpp similarity index 90% rename from cpp/src/math_optimization/solver_settings.cu rename to cpp/src/math_optimization/solver_settings.cpp index 820f41ee3e..9200839c36 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cpp @@ -412,86 +412,6 @@ std::string solver_settings_t::get_parameter_as_string(const std::stri throw std::invalid_argument("Parameter " + name + " not found"); } -template -void solver_settings_t::set_initial_pdlp_primal_solution(const f_t* solution, - i_t size, - rmm::cuda_stream_view stream) -{ - pdlp_settings.set_initial_primal_solution(solution, size, stream); -} - -template -void solver_settings_t::set_initial_pdlp_dual_solution(const f_t* solution, - i_t size, - rmm::cuda_stream_view stream) -{ - pdlp_settings.set_initial_dual_solution(solution, size, stream); -} - -template -void solver_settings_t::set_pdlp_warm_start_data( - const f_t* current_primal_solution, - const f_t* current_dual_solution, - const f_t* initial_primal_average, - const f_t* initial_dual_average, - const f_t* current_ATY, - const f_t* sum_primal_solutions, - const f_t* sum_dual_solutions, - const f_t* last_restart_duality_gap_primal_solution, - const f_t* last_restart_duality_gap_dual_solution, - i_t primal_size, - i_t dual_size, - f_t initial_primal_weight, - f_t initial_step_size, - i_t total_pdlp_iterations, - i_t total_pdhg_iterations, - f_t last_candidate_kkt_score, - f_t last_restart_kkt_score, - f_t sum_solution_weight, - i_t iterations_since_last_restart) -{ - pdlp_settings.set_pdlp_warm_start_data(current_primal_solution, - current_dual_solution, - initial_primal_average, - initial_dual_average, - current_ATY, - sum_primal_solutions, - sum_dual_solutions, - last_restart_duality_gap_primal_solution, - last_restart_duality_gap_dual_solution, - primal_size, - dual_size, - initial_primal_weight, - initial_step_size, - total_pdlp_iterations, - total_pdhg_iterations, - last_candidate_kkt_score, - last_restart_kkt_score, - sum_solution_weight, - iterations_since_last_restart); -} - -template -const rmm::device_uvector& solver_settings_t::get_initial_pdlp_primal_solution() - const -{ - return pdlp_settings.get_initial_primal_solution(); -} - -template -const rmm::device_uvector& solver_settings_t::get_initial_pdlp_dual_solution() const -{ - return pdlp_settings.get_initial_dual_solution(); -} - -template -void solver_settings_t::add_initial_mip_solution(const f_t* solution, - i_t size, - rmm::cuda_stream_view stream) -{ - mip_settings.add_initial_solution(solution, size, stream); -} - template void solver_settings_t::set_mip_callback(internals::base_solution_callback_t* callback, void* user_data) diff --git a/cpp/src/math_optimization/solver_settings_gpu.cu b/cpp/src/math_optimization/solver_settings_gpu.cu new file mode 100644 index 0000000000..7843f1e0ae --- /dev/null +++ b/cpp/src/math_optimization/solver_settings_gpu.cu @@ -0,0 +1,135 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +// Device-facing members of solver_settings_t, split out of solver_settings.cu. +// +// Everything else in that class is host-only parameter handling, so the remainder now +// builds as solver_settings.cpp into the CUDA-free cuopt_client library. Only these +// members take an rmm::cuda_stream_view or hand back a device_uvector, so they are the +// only ones that must stay in a CUDA TU inside libcuopt. +// +// The `template class` instantiation in solver_settings.cpp cannot emit these members +// (their definitions are not visible there), so they are instantiated explicitly below. + +#include + +#include +#include + +#include + +namespace cuopt { +namespace CUOPT_EXPORT mathematical_optimization { + +template +void solver_settings_t::set_initial_pdlp_primal_solution(const f_t* solution, + i_t size, + rmm::cuda_stream_view stream) +{ + pdlp_settings.set_initial_primal_solution(solution, size, stream); +} + +template +void solver_settings_t::set_initial_pdlp_dual_solution(const f_t* solution, + i_t size, + rmm::cuda_stream_view stream) +{ + pdlp_settings.set_initial_dual_solution(solution, size, stream); +} + +template +void solver_settings_t::set_pdlp_warm_start_data( + const f_t* current_primal_solution, + const f_t* current_dual_solution, + const f_t* initial_primal_average, + const f_t* initial_dual_average, + const f_t* current_ATY, + const f_t* sum_primal_solutions, + const f_t* sum_dual_solutions, + const f_t* last_restart_duality_gap_primal_solution, + const f_t* last_restart_duality_gap_dual_solution, + i_t primal_size, + i_t dual_size, + f_t initial_primal_weight, + f_t initial_step_size, + i_t total_pdlp_iterations, + i_t total_pdhg_iterations, + f_t last_candidate_kkt_score, + f_t last_restart_kkt_score, + f_t sum_solution_weight, + i_t iterations_since_last_restart) +{ + pdlp_settings.set_pdlp_warm_start_data(current_primal_solution, + current_dual_solution, + initial_primal_average, + initial_dual_average, + current_ATY, + sum_primal_solutions, + sum_dual_solutions, + last_restart_duality_gap_primal_solution, + last_restart_duality_gap_dual_solution, + primal_size, + dual_size, + initial_primal_weight, + initial_step_size, + total_pdlp_iterations, + total_pdhg_iterations, + last_candidate_kkt_score, + last_restart_kkt_score, + sum_solution_weight, + iterations_since_last_restart); +} + +template +const rmm::device_uvector& solver_settings_t::get_initial_pdlp_primal_solution() + const +{ + return pdlp_settings.get_initial_primal_solution(); +} + +template +const rmm::device_uvector& solver_settings_t::get_initial_pdlp_dual_solution() const +{ + return pdlp_settings.get_initial_dual_solution(); +} + +template +void solver_settings_t::add_initial_mip_solution(const f_t* solution, + i_t size, + rmm::cuda_stream_view stream) +{ + mip_settings.add_initial_solution(solution, size, stream); +} + +#if MIP_INSTANTIATE_FLOAT +template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_primal_solution( + const float*, int, rmm::cuda_stream_view); +template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_dual_solution( + const float*, int, rmm::cuda_stream_view); +template CUOPT_EXPORT const rmm::device_uvector& +solver_settings_t::get_initial_pdlp_primal_solution() const; +template CUOPT_EXPORT const rmm::device_uvector& +solver_settings_t::get_initial_pdlp_dual_solution() const; +template CUOPT_EXPORT void solver_settings_t::add_initial_mip_solution( + const float*, int, rmm::cuda_stream_view); +#endif + +#if MIP_INSTANTIATE_DOUBLE +template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_primal_solution( + const double*, int, rmm::cuda_stream_view); +template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_dual_solution( + const double*, int, rmm::cuda_stream_view); +template CUOPT_EXPORT const rmm::device_uvector& +solver_settings_t::get_initial_pdlp_primal_solution() const; +template CUOPT_EXPORT const rmm::device_uvector& +solver_settings_t::get_initial_pdlp_dual_solution() const; +template CUOPT_EXPORT void solver_settings_t::add_initial_mip_solution( + const double*, int, rmm::cuda_stream_view); +#endif + +} // namespace CUOPT_EXPORT mathematical_optimization +} // namespace cuopt diff --git a/cpp/src/mip_heuristics/CMakeLists.txt b/cpp/src/mip_heuristics/CMakeLists.txt index 6ad1009d84..117407497e 100644 --- a/cpp/src/mip_heuristics/CMakeLists.txt +++ b/cpp/src/mip_heuristics/CMakeLists.txt @@ -9,6 +9,7 @@ set(MIP_LP_NECESSARY_FILES ${CMAKE_CURRENT_SOURCE_DIR}/problem/problem.cu ${CMAKE_CURRENT_SOURCE_DIR}/problem/presolve_data.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/solver_solution.cu ${CMAKE_CURRENT_SOURCE_DIR}/local_search/rounding/simple_rounding.cu ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cpp diff --git a/cpp/src/mip_heuristics/solver_settings.cpp b/cpp/src/mip_heuristics/solver_settings.cpp new file mode 100644 index 0000000000..564248c472 --- /dev/null +++ b/cpp/src/mip_heuristics/solver_settings.cpp @@ -0,0 +1,67 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +// Host-only members of mip_solver_settings_t, split out of solver_settings.cu. +// +// Only add_initial_solution() touches the device (it copies into an rmm::device_uvector), +// so it stays in the CUDA TU while these build into the CUDA-free cuopt_client library. +// The gRPC client reaches get_mip_callbacks() via solve_remote's callback handling. +// +// Instantiated per-member rather than with `template class`: the class holds +// device_uvector-backed initial_solutions, so instantiating all of it here would pull +// device code into the client library. + +#include +#include +#include + +#include + +namespace cuopt::mathematical_optimization { + +template +void mip_solver_settings_t::set_mip_callback( + internals::base_solution_callback_t* callback, void* user_data) +{ + if (callback == nullptr) { return; } + callback->set_user_data(user_data); + mip_callbacks_.push_back(callback); +} + +template +const std::vector +mip_solver_settings_t::get_mip_callbacks() const +{ + return mip_callbacks_; +} + +template +typename mip_solver_settings_t::tolerances_t +mip_solver_settings_t::get_tolerances() const noexcept +{ + return tolerances; +} + +#if MIP_INSTANTIATE_FLOAT +template CUOPT_EXPORT void mip_solver_settings_t::set_mip_callback( + internals::base_solution_callback_t*, void*); +template CUOPT_EXPORT const std::vector +mip_solver_settings_t::get_mip_callbacks() const; +template CUOPT_EXPORT mip_solver_settings_t::tolerances_t +mip_solver_settings_t::get_tolerances() const noexcept; +#endif + +#if MIP_INSTANTIATE_DOUBLE +template CUOPT_EXPORT void mip_solver_settings_t::set_mip_callback( + internals::base_solution_callback_t*, void*); +template CUOPT_EXPORT const std::vector +mip_solver_settings_t::get_mip_callbacks() const; +template CUOPT_EXPORT mip_solver_settings_t::tolerances_t +mip_solver_settings_t::get_tolerances() const noexcept; +#endif + +} // namespace cuopt::mathematical_optimization diff --git a/cpp/src/mip_heuristics/solver_settings.cu b/cpp/src/mip_heuristics/solver_settings.cu index 8b454c949b..a5325137bf 100644 --- a/cpp/src/mip_heuristics/solver_settings.cu +++ b/cpp/src/mip_heuristics/solver_settings.cu @@ -24,29 +24,6 @@ void mip_solver_settings_t::add_initial_solution(const f_t* initial_so raft::copy(initial_solutions.back()->data(), initial_solution, size, stream); } -template -void mip_solver_settings_t::set_mip_callback( - internals::base_solution_callback_t* callback, void* user_data) -{ - if (callback == nullptr) { return; } - callback->set_user_data(user_data); - mip_callbacks_.push_back(callback); -} - -template -const std::vector -mip_solver_settings_t::get_mip_callbacks() const -{ - return mip_callbacks_; -} - -template -typename mip_solver_settings_t::tolerances_t -mip_solver_settings_t::get_tolerances() const noexcept -{ - return tolerances; -} - // Explicit template instantiations for common types #if MIP_INSTANTIATE_FLOAT template class CUOPT_EXPORT mip_solver_settings_t; diff --git a/cpp/src/pdlp/CMakeLists.txt b/cpp/src/pdlp/CMakeLists.txt index 2f90f94872..44dced14bc 100644 --- a/cpp/src/pdlp/CMakeLists.txt +++ b/cpp/src/pdlp/CMakeLists.txt @@ -15,6 +15,7 @@ set(LP_CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/pdhg.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_solution.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_conversion.cu + ${CMAKE_CURRENT_SOURCE_DIR}/solution_conversion_cpu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/saddle_point.cu ${CMAKE_CURRENT_SOURCE_DIR}/cusparse_view.cu ${CMAKE_CURRENT_SOURCE_DIR}/pdlp_warm_start_data.cu diff --git a/cpp/src/pdlp/solution_conversion.cu b/cpp/src/pdlp/solution_conversion.cu index 8293629e6f..533f5ccc3a 100644 --- a/cpp/src/pdlp/solution_conversion.cu +++ b/cpp/src/pdlp/solution_conversion.cu @@ -132,95 +132,9 @@ cuopt::cython::mip_ret_t gpu_mip_solution_t::to_mip_ret_t() } // =========================== -// CPU LP Solution Conversion -// =========================== - -template -cuopt::cython::linear_programming_ret_t -cpu_lp_solution_t::to_cpu_linear_programming_ret_t() -{ - using cpu_solutions_t = cuopt::cython::linear_programming_ret_t::cpu_solutions_t; - cuopt::cython::linear_programming_ret_t ret; - - cpu_solutions_t cpu; - cpu.primal_solution_ = std::move(primal_solution_); - cpu.dual_solution_ = std::move(dual_solution_); - cpu.reduced_cost_ = std::move(reduced_cost_); - - if (!pdlp_warm_start_data_.current_primal_solution_.empty()) { - cpu.current_primal_solution_ = std::move(pdlp_warm_start_data_.current_primal_solution_); - cpu.current_dual_solution_ = std::move(pdlp_warm_start_data_.current_dual_solution_); - cpu.initial_primal_average_ = std::move(pdlp_warm_start_data_.initial_primal_average_); - cpu.initial_dual_average_ = std::move(pdlp_warm_start_data_.initial_dual_average_); - cpu.current_ATY_ = std::move(pdlp_warm_start_data_.current_ATY_); - cpu.sum_primal_solutions_ = std::move(pdlp_warm_start_data_.sum_primal_solutions_); - cpu.sum_dual_solutions_ = std::move(pdlp_warm_start_data_.sum_dual_solutions_); - cpu.last_restart_duality_gap_primal_solution_ = - std::move(pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_); - cpu.last_restart_duality_gap_dual_solution_ = - std::move(pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_); - - ret.initial_primal_weight_ = pdlp_warm_start_data_.initial_primal_weight_; - ret.initial_step_size_ = pdlp_warm_start_data_.initial_step_size_; - ret.total_pdlp_iterations_ = pdlp_warm_start_data_.total_pdlp_iterations_; - ret.total_pdhg_iterations_ = pdlp_warm_start_data_.total_pdhg_iterations_; - ret.last_candidate_kkt_score_ = pdlp_warm_start_data_.last_candidate_kkt_score_; - ret.last_restart_kkt_score_ = pdlp_warm_start_data_.last_restart_kkt_score_; - ret.sum_solution_weight_ = pdlp_warm_start_data_.sum_solution_weight_; - ret.iterations_since_last_restart_ = pdlp_warm_start_data_.iterations_since_last_restart_; - } - - ret.solutions_ = std::move(cpu); - - ret.termination_status_ = termination_status_; - ret.error_status_ = error_status_.get_error_type(); - ret.error_message_ = std::string(error_status_.what()); - ret.l2_primal_residual_ = l2_primal_residual_; - ret.l2_dual_residual_ = l2_dual_residual_; - ret.primal_objective_ = primal_objective_; - ret.dual_objective_ = dual_objective_; - ret.gap_ = gap_; - ret.nb_iterations_ = num_iterations_; - ret.solve_time_ = solve_time_; - ret.solved_by_ = solved_by_; - - return ret; -} - -// =========================== -// CPU MIP Solution Conversion -// =========================== - -template -cuopt::cython::mip_ret_t cpu_mip_solution_t::to_cpu_mip_ret_t() -{ - cuopt::cython::mip_ret_t ret; - - ret.solution_ = std::move(solution_); - - ret.termination_status_ = termination_status_; - ret.error_status_ = error_status_.get_error_type(); - ret.error_message_ = std::string(error_status_.what()); - ret.objective_ = objective_; - ret.mip_gap_ = mip_gap_; - ret.solution_bound_ = solution_bound_; - ret.total_solve_time_ = total_solve_time_; - ret.presolve_time_ = presolve_time_; - ret.max_constraint_violation_ = max_constraint_violation_; - ret.max_int_violation_ = max_int_violation_; - ret.max_variable_bound_violation_ = max_variable_bound_violation_; - ret.nodes_ = num_nodes_; - ret.simplex_iterations_ = num_simplex_iterations_; - - return ret; -} - // Explicit template instantiations template CUOPT_EXPORT cuopt::cython::linear_programming_ret_t gpu_lp_solution_t::to_linear_programming_ret_t(); template CUOPT_EXPORT cuopt::cython::mip_ret_t gpu_mip_solution_t::to_mip_ret_t(); -template CUOPT_EXPORT cuopt::cython::linear_programming_ret_t -cpu_lp_solution_t::to_cpu_linear_programming_ret_t(); -template CUOPT_EXPORT cuopt::cython::mip_ret_t cpu_mip_solution_t::to_cpu_mip_ret_t(); } // namespace cuopt::mathematical_optimization diff --git a/cpp/src/pdlp/solution_conversion_cpu.cpp b/cpp/src/pdlp/solution_conversion_cpu.cpp new file mode 100644 index 0000000000..242df4c824 --- /dev/null +++ b/cpp/src/pdlp/solution_conversion_cpu.cpp @@ -0,0 +1,112 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +// Host-side solution conversions, split out of solution_conversion.cu. +// +// cpu_lp_solution_t / cpu_mip_solution_t hold std::vector data and simply move it into +// the cython ret structs -- no device memory involved. Keeping them in a .cu TU forced +// the gRPC client to depend on libcuopt.so purely to resolve these two symbols, so they +// live in cuopt_client instead. The GPU counterparts stay in solution_conversion.cu. + +#include +#include +#include + +#include +#include + +namespace cuopt::mathematical_optimization { + +// CPU LP Solution Conversion +// =========================== + +template +cuopt::cython::linear_programming_ret_t +cpu_lp_solution_t::to_cpu_linear_programming_ret_t() +{ + using cpu_solutions_t = cuopt::cython::linear_programming_ret_t::cpu_solutions_t; + cuopt::cython::linear_programming_ret_t ret; + + cpu_solutions_t cpu; + cpu.primal_solution_ = std::move(primal_solution_); + cpu.dual_solution_ = std::move(dual_solution_); + cpu.reduced_cost_ = std::move(reduced_cost_); + + if (!pdlp_warm_start_data_.current_primal_solution_.empty()) { + cpu.current_primal_solution_ = std::move(pdlp_warm_start_data_.current_primal_solution_); + cpu.current_dual_solution_ = std::move(pdlp_warm_start_data_.current_dual_solution_); + cpu.initial_primal_average_ = std::move(pdlp_warm_start_data_.initial_primal_average_); + cpu.initial_dual_average_ = std::move(pdlp_warm_start_data_.initial_dual_average_); + cpu.current_ATY_ = std::move(pdlp_warm_start_data_.current_ATY_); + cpu.sum_primal_solutions_ = std::move(pdlp_warm_start_data_.sum_primal_solutions_); + cpu.sum_dual_solutions_ = std::move(pdlp_warm_start_data_.sum_dual_solutions_); + cpu.last_restart_duality_gap_primal_solution_ = + std::move(pdlp_warm_start_data_.last_restart_duality_gap_primal_solution_); + cpu.last_restart_duality_gap_dual_solution_ = + std::move(pdlp_warm_start_data_.last_restart_duality_gap_dual_solution_); + + ret.initial_primal_weight_ = pdlp_warm_start_data_.initial_primal_weight_; + ret.initial_step_size_ = pdlp_warm_start_data_.initial_step_size_; + ret.total_pdlp_iterations_ = pdlp_warm_start_data_.total_pdlp_iterations_; + ret.total_pdhg_iterations_ = pdlp_warm_start_data_.total_pdhg_iterations_; + ret.last_candidate_kkt_score_ = pdlp_warm_start_data_.last_candidate_kkt_score_; + ret.last_restart_kkt_score_ = pdlp_warm_start_data_.last_restart_kkt_score_; + ret.sum_solution_weight_ = pdlp_warm_start_data_.sum_solution_weight_; + ret.iterations_since_last_restart_ = pdlp_warm_start_data_.iterations_since_last_restart_; + } + + ret.solutions_ = std::move(cpu); + + ret.termination_status_ = termination_status_; + ret.error_status_ = error_status_.get_error_type(); + ret.error_message_ = std::string(error_status_.what()); + ret.l2_primal_residual_ = l2_primal_residual_; + ret.l2_dual_residual_ = l2_dual_residual_; + ret.primal_objective_ = primal_objective_; + ret.dual_objective_ = dual_objective_; + ret.gap_ = gap_; + ret.nb_iterations_ = num_iterations_; + ret.solve_time_ = solve_time_; + ret.solved_by_ = solved_by_; + + return ret; +} + +// =========================== +// CPU MIP Solution Conversion +// =========================== + +template +cuopt::cython::mip_ret_t cpu_mip_solution_t::to_cpu_mip_ret_t() +{ + cuopt::cython::mip_ret_t ret; + + ret.solution_ = std::move(solution_); + + ret.termination_status_ = termination_status_; + ret.error_status_ = error_status_.get_error_type(); + ret.error_message_ = std::string(error_status_.what()); + ret.objective_ = objective_; + ret.mip_gap_ = mip_gap_; + ret.solution_bound_ = solution_bound_; + ret.total_solve_time_ = total_solve_time_; + ret.presolve_time_ = presolve_time_; + ret.max_constraint_violation_ = max_constraint_violation_; + ret.max_int_violation_ = max_int_violation_; + ret.max_variable_bound_violation_ = max_variable_bound_violation_; + ret.nodes_ = num_nodes_; + ret.simplex_iterations_ = num_simplex_iterations_; + + return ret; +} + +// Explicit template instantiations +template CUOPT_EXPORT cuopt::cython::linear_programming_ret_t +cpu_lp_solution_t::to_cpu_linear_programming_ret_t(); +template CUOPT_EXPORT cuopt::cython::mip_ret_t cpu_mip_solution_t::to_cpu_mip_ret_t(); + +} // namespace cuopt::mathematical_optimization