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
52 changes: 51 additions & 1 deletion cpp/include/cuopt/linear_programming/cuopt_c.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -681,6 +681,56 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings,
const char* parameter_name,
cuopt_float_t* parameter_value);

/**
* @brief Callback for receiving incumbent MIP solutions with user context.
*
* @param[in] solution - Device pointer to incumbent solution values.
* @param[in] objective_value - Device pointer to incumbent objective value.
* @param[in] user_data - User context pointer.
*/
typedef void (*cuOptMipGetSolutionCallback)(const cuopt_float_t* solution,
const cuopt_float_t* objective_value,
void* user_data);

/**
* @brief Callback for injecting MIP solutions with user context.
*
* @param[out] solution - Device pointer to solution values to set.
* @param[out] objective_value - Device pointer to objective value to set.
* @param[in] user_data - User context pointer.
*/
typedef void (*cuOptMipSetSolutionCallback)(cuopt_float_t* solution,
cuopt_float_t* objective_value,
void* user_data);

/**
* @brief Register a callback to receive incumbent MIP solutions.
*
* @param[in] settings - The solver settings object.
* @param[in] callback - Callback function to receive incumbent solutions.
* @param[in] user_data - User-defined pointer passed through to the callback.
* It will be forwarded to ``cuOptMipGetSolutionCallback`` when invoked.
*
* @return A status code indicating success or failure.
*/
cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings,
cuOptMipGetSolutionCallback callback,
void* user_data);

/**
* @brief Register a callback to inject MIP solutions.
*
* @param[in] settings - The solver settings object.
* @param[in] callback - Callback function to inject solutions.
* @param[in] user_data - User-defined pointer passed through to the callback.
* It will be forwarded to ``cuOptMipSetSolutionCallback`` when invoked.
*
* @return A status code indicating success or failure.
*/
cuopt_int_t cuOptSetMipSetSolutionCallback(cuOptSolverSettings settings,
cuOptMipSetSolutionCallback callback,
void* user_data);

/** @brief Check if an optimization problem is a mixed integer programming problem.
*
* @param[in] problem - The optimization problem.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -36,8 +36,12 @@ class mip_solver_settings_t {

/**
* @brief Set the callback for the user solution
*
* @param[in] callback - Callback handler for user solutions.
* @param[in] user_data - Pointer to user-defined data forwarded to the callback.
*/
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr);
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
void* user_data = nullptr);

/**
* @brief Add an primal solution.
Expand Down
5 changes: 3 additions & 2 deletions cpp/include/cuopt/linear_programming/solver_settings.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -81,7 +81,8 @@ class solver_settings_t {
void add_initial_mip_solution(const f_t* initial_solution,
i_t size,
rmm::cuda_stream_view stream = rmm::cuda_stream_default);
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr);
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
void* user_data = nullptr);

const pdlp_warm_start_data_view_t<i_t, f_t>& get_pdlp_warm_start_data_view() const noexcept;
const std::vector<internals::base_solution_callback_t*> get_mip_callbacks() const;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand Down Expand Up @@ -38,15 +38,21 @@ class default_get_solution_callback_t : public get_solution_callback_t {
}
}

void get_solution(void* data, void* objective_value) override
void get_solution(void* data, void* objective_value, void* user_data) override
{
PyObject* numba_matrix = get_numba_matrix(data, n_variables);
PyObject* numpy_array = get_numba_matrix(objective_value, 1);
PyObject* res =
PyObject_CallMethod(this->pyCallbackClass, "get_solution", "(OO)", numba_matrix, numpy_array);
PyObject* py_user_data = user_data == nullptr ? Py_None : static_cast<PyObject*>(user_data);
PyObject* res = PyObject_CallMethod(
this->pyCallbackClass, "get_solution", "(OOO)", numba_matrix, numpy_array, py_user_data);
if (res == nullptr && PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
res = PyObject_CallMethod(
this->pyCallbackClass, "get_solution", "(OO)", numba_matrix, numpy_array);
}
Py_DECREF(numba_matrix);
Py_DECREF(numpy_array);
Py_DECREF(res);
if (res != nullptr) { Py_DECREF(res); }
}

PyObject* pyCallbackClass;
Expand Down Expand Up @@ -75,15 +81,21 @@ class default_set_solution_callback_t : public set_solution_callback_t {
}
}

void set_solution(void* data, void* objective_value) override
void set_solution(void* data, void* objective_value, void* user_data) override
{
PyObject* numba_matrix = get_numba_matrix(data, n_variables);
PyObject* numpy_array = get_numba_matrix(objective_value, 1);
PyObject* res =
PyObject_CallMethod(this->pyCallbackClass, "set_solution", "(OO)", numba_matrix, numpy_array);
PyObject* py_user_data = user_data == nullptr ? Py_None : static_cast<PyObject*>(user_data);
PyObject* res = PyObject_CallMethod(
this->pyCallbackClass, "set_solution", "(OOO)", numba_matrix, numpy_array, py_user_data);
if (res == nullptr && PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_Clear();
res = PyObject_CallMethod(
this->pyCallbackClass, "set_solution", "(OO)", numba_matrix, numpy_array);
}
Py_DECREF(numba_matrix);
Py_DECREF(numpy_array);
Py_DECREF(res);
if (res != nullptr) { Py_DECREF(res); }
}

PyObject* pyCallbackClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@ class base_solution_callback_t : public Callback {
this->n_variables = n_variables_;
}

void set_user_data(void* user_data_) { user_data = user_data_; }
void* get_user_data() const { return user_data; }

virtual base_solution_callback_type get_type() const = 0;

protected:
bool isFloat = true;
size_t n_variables = 0;
void* user_data = nullptr;
};

class get_solution_callback_t : public base_solution_callback_t {
public:
virtual void get_solution(void* data, void* objective_value) = 0;
virtual void get_solution(void* data, void* objective_value, void* user_data) = 0;
base_solution_callback_type get_type() const override
{
return base_solution_callback_type::GET_SOLUTION;
Expand All @@ -49,7 +53,7 @@ class get_solution_callback_t : public base_solution_callback_t {

class set_solution_callback_t : public base_solution_callback_t {
public:
virtual void set_solution(void* data, void* objective_value) = 0;
virtual void set_solution(void* data, void* objective_value, void* user_data) = 0;
base_solution_callback_type get_type() const override
{
return base_solution_callback_type::SET_SOLUTION;
Expand Down
96 changes: 83 additions & 13 deletions cpp/src/linear_programming/cuopt_c.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */
Expand All @@ -20,6 +20,7 @@
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>

using namespace cuopt::mps_parser;
using namespace cuopt::linear_programming;
Expand Down Expand Up @@ -49,6 +50,50 @@ struct solution_and_stream_view_t {
rmm::cuda_stream_view stream_view;
};

class c_get_solution_callback_t : public cuopt::internals::get_solution_callback_t {
public:
explicit c_get_solution_callback_t(cuOptMipGetSolutionCallback callback) : callback_(callback) {}

void get_solution(void* data, void* objective_value, void* user_data) override
{
if (callback_ == nullptr) { return; }
callback_(static_cast<const cuopt_float_t*>(data),
static_cast<const cuopt_float_t*>(objective_value),
user_data);
}

private:
cuOptMipGetSolutionCallback callback_;
};

class c_set_solution_callback_t : public cuopt::internals::set_solution_callback_t {
public:
explicit c_set_solution_callback_t(cuOptMipSetSolutionCallback callback) : callback_(callback) {}

void set_solution(void* data, void* objective_value, void* user_data) override
{
if (callback_ == nullptr) { return; }
callback_(
static_cast<cuopt_float_t*>(data), static_cast<cuopt_float_t*>(objective_value), user_data);
}

private:
cuOptMipSetSolutionCallback callback_;
};

// Owns solver settings and C callback wrappers for C API lifetime.
struct solver_settings_handle_t {
solver_settings_handle_t() : settings(new solver_settings_t<cuopt_int_t, cuopt_float_t>()) {}
~solver_settings_handle_t() { delete settings; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* settings;
std::vector<std::unique_ptr<cuopt::internals::base_solution_callback_t>> callbacks;
};

solver_settings_handle_t* get_settings_handle(cuOptSolverSettings settings)
{
return static_cast<solver_settings_handle_t*>(settings);
}

int8_t cuOptGetFloatSize() { return sizeof(cuopt_float_t); }

int8_t cuOptGetIntSize() { return sizeof(cuopt_int_t); }
Expand Down Expand Up @@ -574,16 +619,15 @@ cuopt_int_t cuOptGetVariableTypes(cuOptOptimizationProblem problem, char* variab
cuopt_int_t cuOptCreateSolverSettings(cuOptSolverSettings* settings_ptr)
{
if (settings_ptr == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* settings =
new solver_settings_t<cuopt_int_t, cuopt_float_t>();
*settings_ptr = static_cast<cuOptSolverSettings>(settings);
solver_settings_handle_t* settings_handle = new solver_settings_handle_t();
*settings_ptr = static_cast<cuOptSolverSettings>(settings_handle);
return CUOPT_SUCCESS;
}

void cuOptDestroySolverSettings(cuOptSolverSettings* settings_ptr)
{
if (settings_ptr == nullptr) { return; }
delete static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(*settings_ptr);
delete get_settings_handle(*settings_ptr);
*settings_ptr = nullptr;
}

Expand All @@ -595,7 +639,7 @@ cuopt_int_t cuOptSetParameter(cuOptSolverSettings settings,
if (parameter_name == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_value == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
solver_settings->set_parameter_from_string(parameter_name, parameter_value);
} catch (const std::exception& e) {
Expand All @@ -614,7 +658,7 @@ cuopt_int_t cuOptGetParameter(cuOptSolverSettings settings,
if (parameter_value == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_value_size <= 0) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
std::string parameter_value_str = solver_settings->get_parameter_as_string(parameter_name);
std::snprintf(parameter_value, parameter_value_size, "%s", parameter_value_str.c_str());
Expand All @@ -631,7 +675,7 @@ cuopt_int_t cuOptSetIntegerParameter(cuOptSolverSettings settings,
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_name == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
solver_settings->set_parameter<cuopt_int_t>(parameter_name, parameter_value);
} catch (const std::invalid_argument& e) {
Expand All @@ -656,7 +700,7 @@ cuopt_int_t cuOptGetIntegerParameter(cuOptSolverSettings settings,
if (parameter_name == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_value_ptr == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
*parameter_value_ptr = solver_settings->get_parameter<cuopt_int_t>(parameter_name);
} catch (const std::invalid_argument& e) {
Expand All @@ -680,7 +724,7 @@ cuopt_int_t cuOptSetFloatParameter(cuOptSolverSettings settings,
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_name == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
solver_settings->set_parameter<cuopt_float_t>(parameter_name, parameter_value);
} catch (const std::exception& e) {
Expand All @@ -697,7 +741,7 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings,
if (parameter_name == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (parameter_value_ptr == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
try {
*parameter_value_ptr = solver_settings->get_parameter<cuopt_float_t>(parameter_name);
} catch (const std::exception& e) {
Expand All @@ -706,6 +750,32 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings,
return CUOPT_SUCCESS;
}

cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings,
cuOptMipGetSolutionCallback callback,
void* user_data)
{
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (callback == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_handle_t* settings_handle = get_settings_handle(settings);
auto callback_wrapper = std::make_unique<c_get_solution_callback_t>(callback);
settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data);
settings_handle->callbacks.push_back(std::move(callback_wrapper));
return CUOPT_SUCCESS;
}

cuopt_int_t cuOptSetMipSetSolutionCallback(cuOptSolverSettings settings,
cuOptMipSetSolutionCallback callback,
void* user_data)
{
if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (callback == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solver_settings_handle_t* settings_handle = get_settings_handle(settings);
auto callback_wrapper = std::make_unique<c_set_solution_callback_t>(callback);
settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data);
settings_handle->callbacks.push_back(std::move(callback_wrapper));
return CUOPT_SUCCESS;
}

cuopt_int_t cuOptIsMIP(cuOptOptimizationProblem problem, cuopt_int_t* is_mip_ptr)
{
if (problem == nullptr) { return CUOPT_INVALID_ARGUMENT; }
Expand Down Expand Up @@ -733,7 +803,7 @@ cuopt_int_t cuOptSolve(cuOptOptimizationProblem problem,
if (problem_and_stream_view->op_problem->get_problem_category() == problem_category_t::MIP ||
problem_and_stream_view->op_problem->get_problem_category() == problem_category_t::IP) {
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
mip_solver_settings_t<cuopt_int_t, cuopt_float_t>& mip_settings =
solver_settings->get_mip_settings();
optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem =
Expand All @@ -750,7 +820,7 @@ cuopt_int_t cuOptSolve(cuOptOptimizationProblem problem,
solution_and_stream_view->mip_solution_ptr->get_error_status().get_error_type());
} else {
solver_settings_t<cuopt_int_t, cuopt_float_t>* solver_settings =
static_cast<solver_settings_t<cuopt_int_t, cuopt_float_t>*>(settings);
get_settings_handle(settings)->settings;
pdlp_solver_settings_t<cuopt_int_t, cuopt_float_t>& pdlp_settings =
solver_settings->get_pdlp_settings();
optimization_problem_t<cuopt_int_t, cuopt_float_t>* op_problem =
Expand Down
Loading