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
26 changes: 19 additions & 7 deletions cpp/src/grpc/client/solve_remote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#include <cuopt/mathematical_optimization/solve.hpp>
#include <utilities/logger.hpp>
#include "grpc_client.hpp"
#include "solve_remote_impl.hpp"

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <fstream>
Expand All @@ -22,14 +24,27 @@
#include <sstream>
#include <stdexcept>

#include <thrust/count.h>

namespace cuopt::mathematical_optimization {

// Buffer added to the solver's time_limit to account for worker startup,
// GPU init, and result pipe transfer.
constexpr int kTimeoutBufferSeconds = 120;

// See solve_remote_impl.hpp for the contract.
template <typename i_t, typename f_t>
bool should_disable_unsupported(const cpu_optimization_problem_t<i_t, f_t>& problem,
const mip_solver_settings_t<i_t, f_t>& settings)
{
if (settings.get_mip_callbacks().empty()) { return false; }
const auto var_types = problem.get_variable_types_host();
return std::count(var_types.begin(), var_types.end(), var_t::SEMI_CONTINUOUS) > 0;
}

// Instantiated explicitly: the definition lives here, so the unit test's translation unit
// cannot generate it from the declaration alone.
template CUOPT_EXPORT bool should_disable_unsupported(
const cpu_optimization_problem_t<int, double>&, const mip_solver_settings_t<int, double>&);

// ============================================================================
// Helper function to get gRPC server address from environment variables
// ============================================================================
Expand Down Expand Up @@ -149,11 +164,8 @@ std::unique_ptr<mip_solution_interface_t<i_t, f_t>> solve_mip_remote(
}

// Check if user has set incumbent callbacks
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;
if (has_sc_variables && !mip_callbacks.empty()) {
auto mip_callbacks = settings.get_mip_callbacks();
if (should_disable_unsupported(cpu_problem, settings)) {
CUOPT_LOG_WARN(
"Disabling remote MIP get/set callbacks: semi-continuous models are not "
"supported with callbacks");
Expand Down
36 changes: 36 additions & 0 deletions cpp/src/grpc/client/solve_remote_impl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
#include <cuopt/mathematical_optimization/mip/solver_settings.hpp>

namespace cuopt::mathematical_optimization {

/**
* @brief Whether a feature combination the remote server cannot honour must be dropped.
*
* Some client-side requests cannot be forwarded to cuopt_grpc_server. Rather than failing
* the solve, solve_mip_remote() drops the unsupported part and warns. This predicate is that
* decision, kept out of the RPC plumbing so it is unit-testable without a live connection.
*
* Takes the problem and settings themselves rather than pre-extracted fields, so new rules
* can consult anything either object exposes without changing this signature or adding
* branches at the call site.
*
* Currently one rule: MIP get/set callbacks are not supported for semi-continuous models.
*
* @param problem The problem being submitted.
* @param settings The MIP settings the caller configured.
* @return true when the unsupported request (today, the callbacks) must be dropped.
*/
template <typename i_t, typename f_t>
bool should_disable_unsupported(const cpu_optimization_problem_t<i_t, f_t>& problem,
const mip_solver_settings_t<i_t, f_t>& settings);

} // namespace cuopt::mathematical_optimization
3 changes: 2 additions & 1 deletion cpp/src/math_optimization/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,86 +417,6 @@ std::string solver_settings_t<i_t, f_t>::get_parameter_as_string(const std::stri
throw std::invalid_argument("Parameter " + name + " not found");
}

template <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
const rmm::device_uvector<f_t>& solver_settings_t<i_t, f_t>::get_initial_pdlp_primal_solution()
const
{
return pdlp_settings.get_initial_primal_solution();
}

template <typename i_t, typename f_t>
const rmm::device_uvector<f_t>& solver_settings_t<i_t, f_t>::get_initial_pdlp_dual_solution() const
{
return pdlp_settings.get_initial_dual_solution();
}

template <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
void solver_settings_t<i_t, f_t>::set_mip_callback(internals::base_solution_callback_t* callback,
void* user_data)
Expand Down
181 changes: 181 additions & 0 deletions cpp/src/math_optimization/solver_settings_gpu.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* 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 <cuopt/mathematical_optimization/solver_settings.hpp>

#include <rmm/cuda_stream_view.hpp>
#include <rmm/device_uvector.hpp>

#include <mip_heuristics/mip_constants.hpp>

namespace cuopt {
namespace CUOPT_EXPORT mathematical_optimization {

template <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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 <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

template <typename i_t, typename f_t>
const rmm::device_uvector<f_t>& solver_settings_t<i_t, f_t>::get_initial_pdlp_primal_solution()
const
{
return pdlp_settings.get_initial_primal_solution();
}

template <typename i_t, typename f_t>
const rmm::device_uvector<f_t>& solver_settings_t<i_t, f_t>::get_initial_pdlp_dual_solution() const
{
return pdlp_settings.get_initial_dual_solution();
}

template <typename i_t, typename f_t>
void solver_settings_t<i_t, f_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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

#if MIP_INSTANTIATE_FLOAT
template CUOPT_EXPORT void solver_settings_t<int, float>::set_initial_pdlp_primal_solution(
const float*, int, rmm::cuda_stream_view);
template CUOPT_EXPORT void solver_settings_t<int, float>::set_initial_pdlp_dual_solution(
const float*, int, rmm::cuda_stream_view);
template CUOPT_EXPORT const rmm::device_uvector<float>&
solver_settings_t<int, float>::get_initial_pdlp_primal_solution() const;
template CUOPT_EXPORT const rmm::device_uvector<float>&
solver_settings_t<int, float>::get_initial_pdlp_dual_solution() const;
template CUOPT_EXPORT void solver_settings_t<int, float>::add_initial_mip_solution(
const float*, int, rmm::cuda_stream_view);
// The 19-argument host overload. It was moved into this TU with the rest of the block, but
// `template class` in solver_settings.cpp cannot emit it (definition not visible there), so
// without this line the symbol disappears -- and it is the one the Cython layer binds to,
// which takes down every Python test, docs-build and wheel-test job.
template CUOPT_EXPORT void solver_settings_t<int, float>::set_pdlp_warm_start_data(const float*,
const float*,
const float*,
const float*,
const float*,
const float*,
const float*,
const float*,
const float*,
int,
int,
float,
float,
int,
int,
float,
float,
float,
int);
#endif

#if MIP_INSTANTIATE_DOUBLE
template CUOPT_EXPORT void solver_settings_t<int, double>::set_initial_pdlp_primal_solution(
const double*, int, rmm::cuda_stream_view);
template CUOPT_EXPORT void solver_settings_t<int, double>::set_initial_pdlp_dual_solution(
const double*, int, rmm::cuda_stream_view);
template CUOPT_EXPORT const rmm::device_uvector<double>&
solver_settings_t<int, double>::get_initial_pdlp_primal_solution() const;
template CUOPT_EXPORT const rmm::device_uvector<double>&
solver_settings_t<int, double>::get_initial_pdlp_dual_solution() const;
template CUOPT_EXPORT void solver_settings_t<int, double>::add_initial_mip_solution(
const double*, int, rmm::cuda_stream_view);
// The 19-argument host overload. It was moved into this TU with the rest of the block, but
// `template class` in solver_settings.cpp cannot emit it (definition not visible there), so
// without this line the symbol disappears -- and it is the one the Cython layer binds to,
// which takes down every Python test, docs-build and wheel-test job.
template CUOPT_EXPORT void solver_settings_t<int, double>::set_pdlp_warm_start_data(const double*,
const double*,
const double*,
const double*,
const double*,
const double*,
const double*,
const double*,
const double*,
int,
int,
double,
double,
int,
int,
double,
double,
double,
int);
#endif

} // namespace CUOPT_EXPORT mathematical_optimization
} // namespace cuopt
1 change: 1 addition & 0 deletions cpp/src/mip_heuristics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading