From 943b6328edc5f4b4b49484eaace560ce314ecd53 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Thu, 15 Jan 2026 06:30:27 -0800 Subject: [PATCH 1/6] add c api callbacks --- .../cuopt/linear_programming/cuopt_c.h | 42 ++++- cpp/src/linear_programming/cuopt_c.cpp | 92 ++++++++-- .../c_api_tests/c_api_test.c | 167 ++++++++++++++++++ .../c_api_tests/c_api_tests.cpp | 4 +- .../c_api_tests/c_api_tests.h | 3 +- 5 files changed, 292 insertions(+), 16 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 06af2ae86..46a573224 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -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 */ @@ -681,6 +681,46 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, const char* parameter_name, cuopt_float_t* parameter_value); +/** + * @brief Callback for receiving incumbent MIP solutions. + * + * @param[in] solution - Device pointer to incumbent solution values. + * @param[in] objective_value - Device pointer to incumbent objective value. + */ +typedef void (*cuOptMipGetSolutionCallback)(const cuopt_float_t* solution, + const cuopt_float_t* objective_value); + +/** + * @brief Callback for injecting MIP solutions. + * + * @param[out] solution - Device pointer to solution values to set. + * @param[out] objective_value - Device pointer to objective value to set. + */ +typedef void (*cuOptMipSetSolutionCallback)(cuopt_float_t* solution, + cuopt_float_t* objective_value); + +/** + * @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. + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, + cuOptMipGetSolutionCallback callback); + +/** + * @brief Register a callback to inject MIP solutions. + * + * @param[in] settings - The solver settings object. + * @param[in] callback - Callback function to inject solutions. + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptSetMipSetSolutionCallback(cuOptSolverSettings settings, + cuOptMipSetSolutionCallback callback); + /** @brief Check if an optimization problem is a mixed integer programming problem. * * @param[in] problem - The optimization problem. diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index 0772dd14b..b2350cd93 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -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 */ @@ -20,6 +20,7 @@ #include #include #include +#include using namespace cuopt::mps_parser; using namespace cuopt::linear_programming; @@ -49,6 +50,48 @@ 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) override + { + if (callback_ == nullptr) { return; } + callback_(static_cast(data), + static_cast(objective_value)); + } + + 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) override + { + if (callback_ == nullptr) { return; } + callback_(static_cast(data), static_cast(objective_value)); + } + + 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()) {} + ~solver_settings_handle_t() { delete settings; } + solver_settings_t* settings; + std::vector> callbacks; +}; + +solver_settings_handle_t* get_settings_handle(cuOptSolverSettings settings) +{ + return static_cast(settings); +} + int8_t cuOptGetFloatSize() { return sizeof(cuopt_float_t); } int8_t cuOptGetIntSize() { return sizeof(cuopt_int_t); } @@ -574,16 +617,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* settings = - new solver_settings_t(); - *settings_ptr = static_cast(settings); + solver_settings_handle_t* settings_handle = new solver_settings_handle_t(); + *settings_ptr = static_cast(settings_handle); return CUOPT_SUCCESS; } void cuOptDestroySolverSettings(cuOptSolverSettings* settings_ptr) { if (settings_ptr == nullptr) { return; } - delete static_cast*>(*settings_ptr); + delete get_settings_handle(*settings_ptr); *settings_ptr = nullptr; } @@ -595,7 +637,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; try { solver_settings->set_parameter_from_string(parameter_name, parameter_value); } catch (const std::exception& e) { @@ -614,7 +656,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* solver_settings = - static_cast*>(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()); @@ -631,7 +673,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; try { solver_settings->set_parameter(parameter_name, parameter_value); } catch (const std::invalid_argument& e) { @@ -656,7 +698,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; try { *parameter_value_ptr = solver_settings->get_parameter(parameter_name); } catch (const std::invalid_argument& e) { @@ -680,7 +722,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; try { solver_settings->set_parameter(parameter_name, parameter_value); } catch (const std::exception& e) { @@ -697,7 +739,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; try { *parameter_value_ptr = solver_settings->get_parameter(parameter_name); } catch (const std::exception& e) { @@ -706,6 +748,30 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, return CUOPT_SUCCESS; } +cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, + cuOptMipGetSolutionCallback callback) +{ + 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(callback); + settings_handle->settings->set_mip_callback(callback_wrapper.get()); + settings_handle->callbacks.push_back(std::move(callback_wrapper)); + return CUOPT_SUCCESS; +} + +cuopt_int_t cuOptSetMipSetSolutionCallback(cuOptSolverSettings settings, + cuOptMipSetSolutionCallback callback) +{ + 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(callback); + settings_handle->settings->set_mip_callback(callback_wrapper.get()); + 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; } @@ -733,7 +799,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* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; mip_solver_settings_t& mip_settings = solver_settings->get_mip_settings(); optimization_problem_t* op_problem = @@ -750,7 +816,7 @@ cuopt_int_t cuOptSolve(cuOptOptimizationProblem problem, solution_and_stream_view->mip_solution_ptr->get_error_status().get_error_type()); } else { solver_settings_t* solver_settings = - static_cast*>(settings); + get_settings_handle(settings)->settings; pdlp_solver_settings_t& pdlp_settings = solver_settings->get_pdlp_settings(); optimization_problem_t* op_problem = diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 52be9e16f..356b088f9 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -9,6 +9,7 @@ #include +#include #include #include @@ -131,6 +132,172 @@ cuopt_int_t test_bad_parameter_name() { return status; } +typedef struct mip_callback_context_t { + cuopt_int_t n_variables; + int get_calls; + int set_calls; + int error; + cuopt_float_t last_objective; + cuopt_float_t* last_solution; +} mip_callback_context_t; + +static mip_callback_context_t* callback_context = NULL; + +static void mip_get_solution_callback(const cuopt_float_t* solution, + const cuopt_float_t* objective_value) +{ + mip_callback_context_t* context = callback_context; + if (context == NULL) { return; } + context->get_calls += 1; + if (context->last_solution == NULL) { + context->last_solution = + (cuopt_float_t*)malloc(context->n_variables * sizeof(cuopt_float_t)); + if (context->last_solution == NULL) { + context->error = 1; + return; + } + } + if (cudaMemcpy(context->last_solution, + solution, + context->n_variables * sizeof(cuopt_float_t), + cudaMemcpyDeviceToHost) != cudaSuccess) { + context->error = 1; + return; + } + if (cudaMemcpy(&context->last_objective, + objective_value, + sizeof(cuopt_float_t), + cudaMemcpyDeviceToHost) != cudaSuccess) { + context->error = 1; + } +} + +static void mip_set_solution_callback(cuopt_float_t* solution, + cuopt_float_t* objective_value) +{ + mip_callback_context_t* context = callback_context; + if (context == NULL) { return; } + context->set_calls += 1; + if (context->last_solution == NULL) { return; } + if (cudaMemcpy(solution, + context->last_solution, + context->n_variables * sizeof(cuopt_float_t), + cudaMemcpyHostToDevice) != cudaSuccess) { + context->error = 1; + return; + } + if (cudaMemcpy(objective_value, + &context->last_objective, + sizeof(cuopt_float_t), + cudaMemcpyHostToDevice) != cudaSuccess) { + context->error = 1; + } +} + +cuopt_int_t test_mip_callbacks() +{ + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + mip_callback_context_t context = {0}; + +#define NUM_ITEMS 8 +#define NUM_CONSTRAINTS 1 + cuopt_int_t num_items = NUM_ITEMS; + cuopt_float_t max_weight = 102; + cuopt_float_t value[] = {15, 100, 90, 60, 40, 15, 10, 1}; + cuopt_float_t weight[] = {2, 20, 20, 30, 40, 30, 60, 10}; + + cuopt_int_t num_variables = NUM_ITEMS; + cuopt_int_t num_constraints = NUM_CONSTRAINTS; + + cuopt_int_t row_offsets[] = {0, NUM_ITEMS}; + cuopt_int_t column_indices[NUM_ITEMS]; + + cuopt_float_t rhs[] = {max_weight}; + char constraint_sense[] = {CUOPT_LESS_THAN}; + cuopt_float_t lower_bounds[NUM_ITEMS]; + cuopt_float_t upper_bounds[NUM_ITEMS]; + char variable_types[NUM_ITEMS]; + cuopt_int_t status; + + for (cuopt_int_t j = 0; j < NUM_ITEMS; j++) { + column_indices[j] = j; + } + + for (cuopt_int_t j = 0; j < NUM_ITEMS; j++) { + variable_types[j] = CUOPT_INTEGER; + lower_bounds[j] = 0; + upper_bounds[j] = 1; + } + + status = cuOptCreateProblem(num_constraints, + num_variables, + CUOPT_MAXIMIZE, + 0, + value, + row_offsets, + column_indices, + weight, + constraint_sense, + rhs, + lower_bounds, + upper_bounds, + variable_types, + &problem); + if (status != CUOPT_SUCCESS) { + printf("Error creating optimization problem\n"); + goto DONE; + } + + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings\n"); + goto DONE; + } + + context.n_variables = num_variables; + callback_context = &context; + + status = cuOptSetMipGetSolutionCallback(settings, mip_get_solution_callback); + if (status != CUOPT_SUCCESS) { + printf("Error setting get-solution callback\n"); + goto DONE; + } + + status = cuOptSetMipSetSolutionCallback(settings, mip_set_solution_callback); + if (status != CUOPT_SUCCESS) { + printf("Error setting set-solution callback\n"); + goto DONE; + } + + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem\n"); + goto DONE; + } + + if (context.error != 0) { + printf("Error in callback data transfer\n"); + status = CUOPT_INVALID_ARGUMENT; + goto DONE; + } + + if (context.get_calls < 1 || context.set_calls < 1) { + printf("Expected callbacks to be called at least once\n"); + status = CUOPT_INVALID_ARGUMENT; + goto DONE; + } + +DONE: + callback_context = NULL; + if (context.last_solution != NULL) { free(context.last_solution); } + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + return status; +} + cuopt_int_t burglar_problem() { cuOptOptimizationProblem problem = NULL; diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index af1295298..a5405198a 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -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 */ @@ -88,6 +88,8 @@ TEST(c_api, solve_time_bb_preemption) TEST(c_api, bad_parameter_name) { EXPECT_EQ(test_bad_parameter_name(), CUOPT_INVALID_ARGUMENT); } +TEST(c_api, mip_callbacks) { EXPECT_EQ(test_mip_callbacks(), CUOPT_SUCCESS); } + TEST(c_api, burglar) { EXPECT_EQ(burglar_problem(), CUOPT_SUCCESS); } TEST(c_api, test_missing_file) { EXPECT_EQ(test_missing_file(), CUOPT_MPS_FILE_ERROR); } diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index 5726c3a99..808179756 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -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 */ @@ -28,6 +28,7 @@ cuopt_int_t solve_mps_file(const char* filename, cuopt_int_t test_missing_file(); cuopt_int_t test_infeasible_problem(); cuopt_int_t test_bad_parameter_name(); +cuopt_int_t test_mip_callbacks(); cuopt_int_t test_ranged_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); cuopt_int_t test_invalid_bounds(cuopt_int_t test_mip); cuopt_int_t test_quadratic_problem(cuopt_int_t* termination_status_ptr, From 41a3b687aeb545fc91a269c153dfb6467eed882a Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Fri, 16 Jan 2026 07:12:33 -0800 Subject: [PATCH 2/6] add user context to the callbacks --- .../cuopt/linear_programming/cuopt_c.h | 18 +++++++---- .../mip/solver_settings.hpp | 5 ++-- .../linear_programming/solver_settings.hpp | 5 ++-- .../utilities/callbacks_implems.hpp | 30 +++++++++++++------ .../utilities/internals.hpp | 8 +++-- cpp/src/linear_programming/cuopt_c.cpp | 20 ++++++++----- cpp/src/math_optimization/solver_settings.cu | 7 +++-- cpp/src/mip/diversity/diversity_manager.cu | 4 +-- cpp/src/mip/diversity/population.cu | 9 ++++-- cpp/src/mip/solver_settings.cu | 5 ++-- .../c_api_tests/c_api_test.c | 19 +++++------- cpp/tests/mip/incumbent_callback_test.cu | 8 +++-- .../internals/internals.pyx | 14 +++++++-- .../linear_programming/solver/solver.pxd | 5 ++-- .../solver/solver_wrapper.pyx | 10 +++++-- .../solver_settings/solver_settings.py | 8 +++-- 16 files changed, 113 insertions(+), 62 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 46a573224..3dcf5e7c2 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -682,22 +682,26 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, cuopt_float_t* parameter_value); /** - * @brief Callback for receiving incumbent MIP solutions. + * @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); + const cuopt_float_t* objective_value, + void* user_data); /** - * @brief Callback for injecting MIP solutions. + * @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); + cuopt_float_t* objective_value, + void* user_data); /** * @brief Register a callback to receive incumbent MIP solutions. @@ -708,7 +712,8 @@ typedef void (*cuOptMipSetSolutionCallback)(cuopt_float_t* solution, * @return A status code indicating success or failure. */ cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, - cuOptMipGetSolutionCallback callback); + cuOptMipGetSolutionCallback callback, + void* user_data); /** * @brief Register a callback to inject MIP solutions. @@ -719,7 +724,8 @@ cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, * @return A status code indicating success or failure. */ cuopt_int_t cuOptSetMipSetSolutionCallback(cuOptSolverSettings settings, - cuOptMipSetSolutionCallback callback); + cuOptMipSetSolutionCallback callback, + void* user_data); /** @brief Check if an optimization problem is a mixed integer programming problem. * diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 4f6320752..e0bad0f2f 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -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 */ @@ -37,7 +37,8 @@ class mip_solver_settings_t { /** * @brief Set the callback for the user solution */ - 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. diff --git a/cpp/include/cuopt/linear_programming/solver_settings.hpp b/cpp/include/cuopt/linear_programming/solver_settings.hpp index 180293254..dd910a8f4 100644 --- a/cpp/include/cuopt/linear_programming/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/solver_settings.hpp @@ -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 */ @@ -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& get_pdlp_warm_start_data_view() const noexcept; const std::vector get_mip_callbacks() const; diff --git a/cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp b/cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp index f0cd74c24..a2c70d840 100644 --- a/cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp +++ b/cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp @@ -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 */ @@ -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(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; @@ -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(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; diff --git a/cpp/include/cuopt/linear_programming/utilities/internals.hpp b/cpp/include/cuopt/linear_programming/utilities/internals.hpp index 90d856b23..fac6397de 100644 --- a/cpp/include/cuopt/linear_programming/utilities/internals.hpp +++ b/cpp/include/cuopt/linear_programming/utilities/internals.hpp @@ -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; @@ -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; diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index b2350cd93..468c97335 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -54,11 +54,12 @@ class c_get_solution_callback_t : public cuopt::internals::get_solution_callback public: explicit c_get_solution_callback_t(cuOptMipGetSolutionCallback callback) : callback_(callback) {} - void get_solution(void* data, void* objective_value) override + void get_solution(void* data, void* objective_value, void* user_data) override { if (callback_ == nullptr) { return; } callback_(static_cast(data), - static_cast(objective_value)); + static_cast(objective_value), + user_data); } private: @@ -69,10 +70,11 @@ class c_set_solution_callback_t : public cuopt::internals::set_solution_callback public: explicit c_set_solution_callback_t(cuOptMipSetSolutionCallback callback) : callback_(callback) {} - void set_solution(void* data, void* objective_value) override + void set_solution(void* data, void* objective_value, void* user_data) override { if (callback_ == nullptr) { return; } - callback_(static_cast(data), static_cast(objective_value)); + callback_( + static_cast(data), static_cast(objective_value), user_data); } private: @@ -749,25 +751,27 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, } cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, - cuOptMipGetSolutionCallback callback) + 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(callback); - settings_handle->settings->set_mip_callback(callback_wrapper.get()); + 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) + 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(callback); - settings_handle->settings->set_mip_callback(callback_wrapper.get()); + settings_handle->settings->set_mip_callback(callback_wrapper.get(), user_data); settings_handle->callbacks.push_back(std::move(callback_wrapper)); return CUOPT_SUCCESS; } diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 4e3dc6465..94ef3d61b 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -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 */ @@ -382,9 +382,10 @@ void solver_settings_t::add_initial_mip_solution(const f_t* solution, } template -void solver_settings_t::set_mip_callback(internals::base_solution_callback_t* callback) +void solver_settings_t::set_mip_callback(internals::base_solution_callback_t* callback, + void* user_data) { - mip_settings.set_mip_callback(callback); + mip_settings.set_mip_callback(callback, user_data); } template diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 483ffeb68..5d6b5f4f2 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -446,7 +446,7 @@ solution_t diversity_manager_t::run_solver() // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); } - + exit(0); if (ls.lp_optimal_exists) { solution_t lp_rounded_sol(*problem_ptr); lp_rounded_sol.copy_new_assignment(lp_optimal_solution); diff --git a/cpp/src/mip/diversity/population.cu b/cpp/src/mip/diversity/population.cu index 766ed09cb..f343320cc 100644 --- a/cpp/src/mip/diversity/population.cu +++ b/cpp/src/mip/diversity/population.cu @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -299,7 +299,8 @@ void population_t::run_solution_callbacks(solution_t& sol) temp_sol.problem_ptr->get_user_obj_from_solver_obj(temp_sol.get_objective()); user_objective_vec.set_element_async(0, user_objective, temp_sol.handle_ptr->get_stream()); CUOPT_LOG_DEBUG("Returning incumbent solution with objective %g", user_objective); - get_sol_callback->get_solution(temp_sol.assignment.data(), user_objective_vec.data()); + get_sol_callback->get_solution( + temp_sol.assignment.data(), user_objective_vec.data(), get_sol_callback->get_user_data()); } } // save the best objective here, because we might not have been able to return the solution to @@ -320,7 +321,9 @@ void population_t::run_solution_callbacks(solution_t& sol) auto inf = std::numeric_limits::infinity(); d_outside_sol_objective.set_value_async(inf, sol.handle_ptr->get_stream()); sol.handle_ptr->sync_stream(); - set_sol_callback->set_solution(incumbent_assignment.data(), d_outside_sol_objective.data()); + set_sol_callback->set_solution(incumbent_assignment.data(), + d_outside_sol_objective.data(), + set_sol_callback->get_user_data()); f_t outside_sol_objective = d_outside_sol_objective.value(sol.handle_ptr->get_stream()); // The callback might be called without setting any valid solution or objective which triggers diff --git a/cpp/src/mip/solver_settings.cu b/cpp/src/mip/solver_settings.cu index 205d4fe68..92df7aa29 100644 --- a/cpp/src/mip/solver_settings.cu +++ b/cpp/src/mip/solver_settings.cu @@ -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 */ @@ -25,8 +25,9 @@ void mip_solver_settings_t::add_initial_solution(const f_t* initial_so template void mip_solver_settings_t::set_mip_callback( - internals::base_solution_callback_t* callback) + internals::base_solution_callback_t* callback, void* user_data) { + if (callback != nullptr) { callback->set_user_data(user_data); } mip_callbacks_.push_back(callback); } diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 356b088f9..7cff7e343 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -141,12 +141,11 @@ typedef struct mip_callback_context_t { cuopt_float_t* last_solution; } mip_callback_context_t; -static mip_callback_context_t* callback_context = NULL; - static void mip_get_solution_callback(const cuopt_float_t* solution, - const cuopt_float_t* objective_value) + const cuopt_float_t* objective_value, + void* user_data) { - mip_callback_context_t* context = callback_context; + mip_callback_context_t* context = (mip_callback_context_t*)user_data; if (context == NULL) { return; } context->get_calls += 1; if (context->last_solution == NULL) { @@ -173,9 +172,10 @@ static void mip_get_solution_callback(const cuopt_float_t* solution, } static void mip_set_solution_callback(cuopt_float_t* solution, - cuopt_float_t* objective_value) + cuopt_float_t* objective_value, + void* user_data) { - mip_callback_context_t* context = callback_context; + mip_callback_context_t* context = (mip_callback_context_t*)user_data; if (context == NULL) { return; } context->set_calls += 1; if (context->last_solution == NULL) { return; } @@ -257,15 +257,13 @@ cuopt_int_t test_mip_callbacks() } context.n_variables = num_variables; - callback_context = &context; - - status = cuOptSetMipGetSolutionCallback(settings, mip_get_solution_callback); + status = cuOptSetMipGetSolutionCallback(settings, mip_get_solution_callback, &context); if (status != CUOPT_SUCCESS) { printf("Error setting get-solution callback\n"); goto DONE; } - status = cuOptSetMipSetSolutionCallback(settings, mip_set_solution_callback); + status = cuOptSetMipSetSolutionCallback(settings, mip_set_solution_callback, &context); if (status != CUOPT_SUCCESS) { printf("Error setting set-solution callback\n"); goto DONE; @@ -290,7 +288,6 @@ cuopt_int_t test_mip_callbacks() } DONE: - callback_context = NULL; if (context.last_solution != NULL) { free(context.last_solution); } cuOptDestroyProblem(&problem); cuOptDestroySolverSettings(&settings); diff --git a/cpp/tests/mip/incumbent_callback_test.cu b/cpp/tests/mip/incumbent_callback_test.cu index 6d6792df2..945a702d4 100644 --- a/cpp/tests/mip/incumbent_callback_test.cu +++ b/cpp/tests/mip/incumbent_callback_test.cu @@ -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 */ @@ -40,8 +40,9 @@ class test_set_solution_callback_t : public cuopt::internals::set_solution_callb { } // This will check that the we are able to recompute our own solution - void set_solution(void* data, void* cost) override + void set_solution(void* data, void* cost, void* user_data) override { + (void)user_data; n_calls++; rmm::cuda_stream_view stream{}; auto assignment = static_cast(data); @@ -64,8 +65,9 @@ class test_get_solution_callback_t : public cuopt::internals::get_solution_callb : solutions(solutions_in), n_calls(0), n_variables(n_variables_) { } - void get_solution(void* data, void* cost) override + void get_solution(void* data, void* cost, void* user_data) override { + (void)user_data; n_calls++; rmm::cuda_stream_view stream{}; rmm::device_uvector assignment(n_variables, stream); diff --git a/python/cuopt/cuopt/linear_programming/internals/internals.pyx b/python/cuopt/cuopt/linear_programming/internals/internals.pyx index 0e4342fe1..7e4f335d6 100644 --- a/python/cuopt/cuopt/linear_programming/internals/internals.pyx +++ b/python/cuopt/cuopt/linear_programming/internals/internals.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 # cython: profile=False @@ -23,12 +23,12 @@ cdef extern from "cuopt/linear_programming/utilities/callbacks_implems.hpp" name cdef cppclass default_get_solution_callback_t(Callback): void setup() except + - void get_solution(void* data, void* objective_value) except + + void get_solution(void* data, void* objective_value, void* user_data) except + PyObject* pyCallbackClass cdef cppclass default_set_solution_callback_t(Callback): void setup() except + - void set_solution(void* data, void* objective_value) except + + void set_solution(void* data, void* objective_value, void* user_data) except + PyObject* pyCallbackClass @@ -69,6 +69,10 @@ cdef class GetSolutionCallback(PyCallback): def __init__(self): self.native_callback.pyCallbackClass = self + self._user_data = None + + def get_user_data_ptr(self): + return self._user_data def get_native_callback(self): return &(self.native_callback) @@ -80,6 +84,10 @@ cdef class SetSolutionCallback(PyCallback): def __init__(self): self.native_callback.pyCallbackClass = self + self._user_data = None + + def get_user_data_ptr(self): + return self._user_data def get_native_callback(self): return &(self.native_callback) diff --git a/python/cuopt/cuopt/linear_programming/solver/solver.pxd b/python/cuopt/cuopt/linear_programming/solver/solver.pxd index c140e3d0c..6688e5166 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver.pxd +++ b/python/cuopt/cuopt/linear_programming/solver/solver.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 @@ -78,7 +78,8 @@ cdef extern from "cuopt/linear_programming/solver_settings.hpp" namespace "cuopt i_t size ) except + void set_mip_callback( - base_solution_callback_t* callback + base_solution_callback_t* callback, + void* user_data ) except + diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx index 1991af0d6..0582abb2c 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # noqa # SPDX-License-Identifier: Apache-2.0 @@ -186,9 +186,15 @@ cdef set_solver_setting( for callback in callbacks: if callback: callback_ptr = callback.get_native_callback() + cdef uintptr_t callback_user_data = ( + callback.get_user_data_ptr() + if hasattr(callback, "get_user_data_ptr") + else 0 + ) c_solver_settings.set_mip_callback( - callback_ptr + callback_ptr, + callback_user_data ) else: if data_model_obj is not None and data_model_obj.get_initial_primal_solution().shape[0] != 0: # noqa diff --git a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py index 85a944e2e..42b31bdf5 100644 --- a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py +++ b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py @@ -1,4 +1,4 @@ -# 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 from enum import IntEnum, auto @@ -245,7 +245,7 @@ def set_pdlp_warm_start_data(self, pdlp_warm_start_data): """ self.pdlp_warm_start_data = pdlp_warm_start_data - def set_mip_callback(self, callback): + def set_mip_callback(self, callback, user_data=None): """ Note: Only supported for MILP @@ -256,6 +256,8 @@ def set_mip_callback(self, callback): callback : class for function callback Callback class that inherits from GetSolutionCallback or SetSolutionCallback. + user_data : object, optional + User context passed to the callback. Examples -------- @@ -298,6 +300,8 @@ def set_mip_callback(self, callback): >>> settings.set_mip_callback(get_callback) >>> settings.set_mip_callback(set_callback) """ + if callback is not None: + callback._user_data = user_data self.mip_callbacks.append(callback) def get_mip_callbacks(self): From 1e7a2e5b367e60716cb14bca3cbd92c69447ec41 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Fri, 16 Jan 2026 07:43:38 -0800 Subject: [PATCH 3/6] handle ai reviews --- cpp/include/cuopt/linear_programming/cuopt_c.h | 4 ++++ cpp/src/mip/diversity/diversity_manager.cu | 4 ++-- cpp/src/mip/solver_settings.cu | 3 ++- python/cuopt/cuopt/linear_programming/internals/internals.pyx | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 3dcf5e7c2..028cba822 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -708,6 +708,8 @@ typedef void (*cuOptMipSetSolutionCallback)(cuopt_float_t* solution, * * @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. */ @@ -720,6 +722,8 @@ cuopt_int_t cuOptSetMipGetSolutionCallback(cuOptSolverSettings settings, * * @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. */ diff --git a/cpp/src/mip/diversity/diversity_manager.cu b/cpp/src/mip/diversity/diversity_manager.cu index 5d6b5f4f2..483ffeb68 100644 --- a/cpp/src/mip/diversity/diversity_manager.cu +++ b/cpp/src/mip/diversity/diversity_manager.cu @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -446,7 +446,7 @@ solution_t diversity_manager_t::run_solver() // in case the pdlp returned var boudns that are out of bounds clamp_within_var_bounds(lp_optimal_solution, problem_ptr, problem_ptr->handle_ptr); } - exit(0); + if (ls.lp_optimal_exists) { solution_t lp_rounded_sol(*problem_ptr); lp_rounded_sol.copy_new_assignment(lp_optimal_solution); diff --git a/cpp/src/mip/solver_settings.cu b/cpp/src/mip/solver_settings.cu index 92df7aa29..f69b45575 100644 --- a/cpp/src/mip/solver_settings.cu +++ b/cpp/src/mip/solver_settings.cu @@ -27,7 +27,8 @@ template void mip_solver_settings_t::set_mip_callback( internals::base_solution_callback_t* callback, void* user_data) { - if (callback != nullptr) { callback->set_user_data(user_data); } + if (callback == nullptr) { return; } + callback->set_user_data(user_data); mip_callbacks_.push_back(callback); } diff --git a/python/cuopt/cuopt/linear_programming/internals/internals.pyx b/python/cuopt/cuopt/linear_programming/internals/internals.pyx index 7e4f335d6..3f1ad1954 100644 --- a/python/cuopt/cuopt/linear_programming/internals/internals.pyx +++ b/python/cuopt/cuopt/linear_programming/internals/internals.pyx @@ -66,6 +66,7 @@ cdef class PyCallback: cdef class GetSolutionCallback(PyCallback): cdef default_get_solution_callback_t native_callback + cdef object _user_data def __init__(self): self.native_callback.pyCallbackClass = self @@ -81,6 +82,7 @@ cdef class GetSolutionCallback(PyCallback): cdef class SetSolutionCallback(PyCallback): cdef default_set_solution_callback_t native_callback + cdef object _user_data def __init__(self): self.native_callback.pyCallbackClass = self From fda3f4fd151490c5f5ea398cccb372e86d30e663 Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Fri, 16 Jan 2026 07:45:03 -0800 Subject: [PATCH 4/6] add doxygen string --- cpp/include/cuopt/linear_programming/mip/solver_settings.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index e0bad0f2f..11a4c303d 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -36,6 +36,9 @@ 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* user_data = nullptr); From 4e50db4153d730dedf47a035fa1638a82e855e6f Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Fri, 16 Jan 2026 09:05:28 -0800 Subject: [PATCH 5/6] python callback casting issues --- .../internals/internals.pyx | 21 ++++++++++++------- .../solver/solver_wrapper.pyx | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/python/cuopt/cuopt/linear_programming/internals/internals.pyx b/python/cuopt/cuopt/linear_programming/internals/internals.pyx index 3f1ad1954..3e3d4a925 100644 --- a/python/cuopt/cuopt/linear_programming/internals/internals.pyx +++ b/python/cuopt/cuopt/linear_programming/internals/internals.pyx @@ -8,15 +8,12 @@ from libc.stdint cimport uintptr_t +from cpython.ref cimport PyObject import numpy as np from numba.cuda.api import from_cuda_array_interface -cdef extern from "Python.h": - cdef cppclass PyObject - - cdef extern from "cuopt/linear_programming/utilities/callbacks_implems.hpp" namespace "cuopt::internals": # noqa cdef cppclass Callback: pass @@ -72,8 +69,12 @@ cdef class GetSolutionCallback(PyCallback): self.native_callback.pyCallbackClass = self self._user_data = None - def get_user_data_ptr(self): - return self._user_data + cpdef uintptr_t get_user_data_ptr(self): + cdef PyObject* ptr + if self._user_data is None: + return 0 + ptr = self._user_data + return ptr def get_native_callback(self): return &(self.native_callback) @@ -88,8 +89,12 @@ cdef class SetSolutionCallback(PyCallback): self.native_callback.pyCallbackClass = self self._user_data = None - def get_user_data_ptr(self): - return self._user_data + cpdef uintptr_t get_user_data_ptr(self): + cdef PyObject* ptr + if self._user_data is None: + return 0 + ptr = self._user_data + return ptr def get_native_callback(self): return &(self.native_callback) diff --git a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx index 0582abb2c..8c1c8fdc3 100644 --- a/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx +++ b/python/cuopt/cuopt/linear_programming/solver/solver_wrapper.pyx @@ -169,6 +169,7 @@ cdef set_solver_setting( cdef uintptr_t c_last_restart_duality_gap_primal_solution cdef uintptr_t c_last_restart_duality_gap_dual_solution cdef uintptr_t callback_ptr = 0 + cdef uintptr_t callback_user_data = 0 if mip: if data_model_obj is not None and data_model_obj.get_initial_primal_solution().shape[0] != 0: # noqa c_solver_settings.add_initial_mip_solution( @@ -186,7 +187,7 @@ cdef set_solver_setting( for callback in callbacks: if callback: callback_ptr = callback.get_native_callback() - cdef uintptr_t callback_user_data = ( + callback_user_data = ( callback.get_user_data_ptr() if hasattr(callback, "get_user_data_ptr") else 0 From 826b3c0326d44f6763037805aaa3a7fb40de6abd Mon Sep 17 00:00:00 2001 From: akifcorduk Date: Fri, 16 Jan 2026 09:17:47 -0800 Subject: [PATCH 6/6] more review comments --- .../internals/internals.pyx | 38 ++++++++++--------- .../solver_settings/solver_settings.py | 2 +- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/python/cuopt/cuopt/linear_programming/internals/internals.pyx b/python/cuopt/cuopt/linear_programming/internals/internals.pyx index 3e3d4a925..695465fbb 100644 --- a/python/cuopt/cuopt/linear_programming/internals/internals.pyx +++ b/python/cuopt/cuopt/linear_programming/internals/internals.pyx @@ -31,6 +31,24 @@ cdef extern from "cuopt/linear_programming/utilities/callbacks_implems.hpp" name cdef class PyCallback: + cdef object _user_data + + def __init__(self): + self._user_data = None + + property user_data: + def __get__(self): + return self._user_data + def __set__(self, value): + self._user_data = value + + cpdef uintptr_t get_user_data_ptr(self): + cdef PyObject* ptr + if self._user_data is None: + return 0 + ptr = self._user_data + return ptr + def get_numba_matrix(self, data, shape, typestr): sizeofType = 4 if typestr == "float32" else 8 @@ -63,18 +81,10 @@ cdef class PyCallback: cdef class GetSolutionCallback(PyCallback): cdef default_get_solution_callback_t native_callback - cdef object _user_data def __init__(self): + super().__init__() self.native_callback.pyCallbackClass = self - self._user_data = None - - cpdef uintptr_t get_user_data_ptr(self): - cdef PyObject* ptr - if self._user_data is None: - return 0 - ptr = self._user_data - return ptr def get_native_callback(self): return &(self.native_callback) @@ -83,18 +93,10 @@ cdef class GetSolutionCallback(PyCallback): cdef class SetSolutionCallback(PyCallback): cdef default_set_solution_callback_t native_callback - cdef object _user_data def __init__(self): + super().__init__() self.native_callback.pyCallbackClass = self - self._user_data = None - - cpdef uintptr_t get_user_data_ptr(self): - cdef PyObject* ptr - if self._user_data is None: - return 0 - ptr = self._user_data - return ptr def get_native_callback(self): return &(self.native_callback) diff --git a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py index 42b31bdf5..41d086752 100644 --- a/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py +++ b/python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py @@ -301,7 +301,7 @@ def set_mip_callback(self, callback, user_data=None): >>> settings.set_mip_callback(set_callback) """ if callback is not None: - callback._user_data = user_data + callback.user_data = user_data self.mip_callbacks.append(callback) def get_mip_callbacks(self):