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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would say Signature of callback for receiving ... or Type of callback for receiving ... so it is clear to the reader that this is declaring the type.

*
* @param[in] solution - Device pointer to incumbent solution values.
* @param[in] objective_value - Device pointer to incumbent objective value.
Comment on lines +687 to +688
Copy link
Contributor

@aliceb-nv aliceb-nv Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if these should be host pointers instead, in terms of API design. So far the API has been abstracting away the reality of the CUDA implementation (such as the corresponding context, the streams used...) away from the user, so this may represent a break in the philosophy. Plus we don't expose the GPU problem structure either, so it is likely the user will have to copy it back to the host either way to do any useful work
Maybe device-space callbacks could be implemented later on if need be? We might have to discuss this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. The rest of the API uses abstracts out the device. Probably we should provide a host pointer here.

* @param[in] user_data - User context pointer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe User context pointer -> Pointer to user data

*/
typedef void (*cuOptMipGetSolutionCallback)(const cuopt_float_t* solution,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other parts of the C API we capitalize MIP. So I would call this cuOptMIPGetSolutionCallback

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should mention the dimension of the solution_values (i.e. that it needs to be equal to the number of variables in the original problem).

Also, do we crush the solution from the original variables into the presolve variables when MIP presolve is turned on?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this should probably be a host pointer rather than a device pointer

* @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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: I would call this cuOptMIPSetSolutionCallback for consistency with the rest of the API

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to cuOptSetMIPSolutionCallback

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to cuOptSetMIPSolutionCallback

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_; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Google C++ style guide and other common conventions reserves the underscore postfix to refer to the protected member variable. So it's a bit confusing to have user_data_ be the input argument, as this is usually the name of the member variable. Since you aren't using underscores for member variable names here, maybe use a different name for the argument (e.g. input_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