Skip to content

Commit 44d4779

Browse files
Merge pull request NVIDIA#826 from rgsl888prabhu/main-merge-release/26.02_3
Main merge release/26.02 3
2 parents abaa34b + 4952abc commit 44d4779

File tree

55 files changed

+1565
-448
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1565
-448
lines changed

ci/run_cuopt_pytests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
44

55
set -euo pipefail
@@ -9,4 +9,4 @@ set -euo pipefail
99
# Support invoking run_cuopt_pytests.sh outside the script directory
1010
cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../python/cuopt/cuopt/
1111

12-
pytest --cache-clear "$@" tests
12+
pytest -s --cache-clear "$@" tests

ci/run_cuopt_server_pytests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
44

55
set -euo pipefail
@@ -9,4 +9,4 @@ set -euo pipefail
99
# Support invoking run_cuopt_server_pytests.sh outside the script directory
1010
cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../python/cuopt_server/cuopt_server/
1111

12-
pytest --cache-clear "$@" tests
12+
pytest -s --cache-clear "$@" tests

conda/recipes/libcuopt/recipe.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ cache:
3636
- AWS_SESSION_TOKEN
3737
env:
3838
# Enable assertions (-a flag) for PR builds, but not for nightly or release builds
39-
BUILD_EXTRA_FLAGS: '${{ "-a" if build_type == "pull-request" else "" }}'
39+
BUILD_EXTRA_FLAGS: '${{ "-a --host-lineinfo" if build_type == "pull-request" else "" }}'
4040
CMAKE_C_COMPILER_LAUNCHER: ${{ env.get("CMAKE_C_COMPILER_LAUNCHER") }}
4141
CMAKE_CUDA_COMPILER_LAUNCHER: ${{ env.get("CMAKE_CUDA_COMPILER_LAUNCHER") }}
4242
CMAKE_CXX_COMPILER_LAUNCHER: ${{ env.get("CMAKE_CXX_COMPILER_LAUNCHER") }}

cpp/include/cuopt/linear_programming/cuopt_c.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,76 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings,
695695
const char* parameter_name,
696696
cuopt_float_t* parameter_value);
697697

698+
/**
699+
* @brief Type of callback for receiving incumbent MIP solutions with user context.
700+
*
701+
* @param[in] solution - Pointer to incumbent solution values.
702+
* The allocated array for solution pointer must be at least the number of variables in the original
703+
* problem.
704+
* @param[in] objective_value - Pointer to incumbent objective value.
705+
* @param[in] solution_bound - Pointer to current solution (dual/user) bound.
706+
* @param[in] user_data - Pointer to user data.
707+
* @note All pointer arguments (solution, objective_value, solution_bound, user_data) refer to host
708+
* memory and are only valid during the callback invocation. Do not pass device/GPU pointers.
709+
* Copy any data you need to keep after the callback returns.
710+
*/
711+
typedef void (*cuOptMIPGetSolutionCallback)(const cuopt_float_t* solution,
712+
const cuopt_float_t* objective_value,
713+
const cuopt_float_t* solution_bound,
714+
void* user_data);
715+
716+
/**
717+
* @brief Type of callback for injecting MIP solutions with user context.
718+
*
719+
* @param[out] solution - Pointer to solution values to set.
720+
* The allocated array for solution pointer must be at least the number of variables in the original
721+
* problem.
722+
* @param[out] objective_value - Pointer to objective value to set.
723+
* @param[in] solution_bound - Pointer to current solution (dual/user) bound.
724+
* @param[in] user_data - Pointer to user data.
725+
* @note All pointer arguments (solution, objective_value, solution_bound, user_data) refer to host
726+
* memory and are only valid during the callback invocation. Do not pass device/GPU pointers.
727+
* Copy any data you need to keep after the callback returns.
728+
*/
729+
typedef void (*cuOptMIPSetSolutionCallback)(cuopt_float_t* solution,
730+
cuopt_float_t* objective_value,
731+
const cuopt_float_t* solution_bound,
732+
void* user_data);
733+
734+
/**
735+
* @brief Register a callback to receive incumbent MIP solutions.
736+
*
737+
* @param[in] settings - The solver settings object.
738+
* @param[in] callback - Callback function to receive incumbent solutions.
739+
* @param[in] user_data - User-defined pointer passed through to the callback.
740+
* It will be forwarded to ``cuOptMIPGetSolutionCallback`` when invoked.
741+
* @note The callback arguments refer to host memory and are only valid during the callback
742+
* invocation. Do not pass device/GPU pointers. Copy any data you need to keep after the callback
743+
* returns.
744+
*
745+
* @return A status code indicating success or failure.
746+
*/
747+
cuopt_int_t cuOptSetMIPGetSolutionCallback(cuOptSolverSettings settings,
748+
cuOptMIPGetSolutionCallback callback,
749+
void* user_data);
750+
751+
/**
752+
* @brief Register a callback to inject MIP solutions.
753+
*
754+
* @param[in] settings - The solver settings object.
755+
* @param[in] callback - Callback function to inject solutions.
756+
* @param[in] user_data - User-defined pointer passed through to the callback.
757+
* It will be forwarded to ``cuOptMIPSetSolutionCallback`` when invoked.
758+
* @note Registering a set-solution callback disables presolve.
759+
* @note The callback arguments refer to host memory and are only valid during the callback
760+
* invocation. Do not pass device/GPU pointers. Copy any data you need to keep after the callback
761+
* returns.
762+
*
763+
* @return A status code indicating success or failure.
764+
*/
765+
cuopt_int_t cuOptSetMIPSetSolutionCallback(cuOptSolverSettings settings,
766+
cuOptMIPSetSolutionCallback callback,
767+
void* user_data);
698768
/**
699769
* @brief Set the initial primal solution for an LP solve.
700770
*

cpp/include/cuopt/linear_programming/mip/solver_settings.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ class mip_solver_settings_t {
3737

3838
/**
3939
* @brief Set the callback for the user solution
40+
*
41+
* @param[in] callback - Callback handler for user solutions.
42+
* @param[in] user_data - Pointer to user-defined data forwarded to the callback.
4043
*/
41-
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr);
44+
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
45+
void* user_data = nullptr);
4246

4347
/**
4448
* @brief Add an primal solution.
@@ -91,7 +95,7 @@ class mip_solver_settings_t {
9195

9296
/** Initial primal solutions */
9397
std::vector<std::shared_ptr<rmm::device_uvector<f_t>>> initial_solutions;
94-
bool mip_scaling = true;
98+
bool mip_scaling = false;
9599
bool presolve = true;
96100
// this is for extracting info from different places of the solver during
97101
// benchmarks

cpp/include/cuopt/linear_programming/mip/solver_stats.hpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
/* clang-format off */
22
/*
3-
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
/* clang-format on */
77
#pragma once
8+
9+
#include <atomic>
10+
#include <limits>
811
namespace cuopt::linear_programming {
912

1013
template <typename i_t, typename f_t>
1114
struct solver_stats_t {
12-
f_t total_solve_time = 0.;
13-
f_t presolve_time = 0.;
14-
f_t solution_bound = std::numeric_limits<f_t>::min();
15+
// Direction-neutral placeholder; solver_context initializes based on maximize/minimize.
16+
solver_stats_t() : solution_bound(std::numeric_limits<f_t>::infinity()) {}
17+
18+
solver_stats_t(const solver_stats_t& other) { *this = other; }
19+
20+
solver_stats_t& operator=(const solver_stats_t& other)
21+
{
22+
if (this == &other) { return *this; }
23+
total_solve_time = other.total_solve_time;
24+
presolve_time = other.presolve_time;
25+
solution_bound.store(other.solution_bound.load(std::memory_order_relaxed),
26+
std::memory_order_relaxed);
27+
num_nodes = other.num_nodes;
28+
num_simplex_iterations = other.num_simplex_iterations;
29+
return *this;
30+
}
31+
32+
f_t get_solution_bound() const { return solution_bound.load(std::memory_order_relaxed); }
33+
34+
void set_solution_bound(f_t value) { solution_bound.store(value, std::memory_order_relaxed); }
35+
36+
f_t total_solve_time = 0.;
37+
f_t presolve_time = 0.;
38+
std::atomic<f_t> solution_bound;
1539
i_t num_nodes = 0;
1640
i_t num_simplex_iterations = 0;
1741
};

cpp/include/cuopt/linear_programming/solver_settings.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* clang-format off */
22
/*
3-
* SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
/* clang-format on */
@@ -81,7 +81,8 @@ class solver_settings_t {
8181
void add_initial_mip_solution(const f_t* initial_solution,
8282
i_t size,
8383
rmm::cuda_stream_view stream = rmm::cuda_stream_default);
84-
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr);
84+
void set_mip_callback(internals::base_solution_callback_t* callback = nullptr,
85+
void* user_data = nullptr);
8586

8687
const pdlp_warm_start_data_view_t<i_t, f_t>& get_pdlp_warm_start_data_view() const noexcept;
8788
const std::vector<internals::base_solution_callback_t*> get_mip_callbacks() const;

cpp/include/cuopt/linear_programming/utilities/callbacks_implems.hpp

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* clang-format off */
22
/*
3-
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
/* clang-format on */
@@ -17,17 +17,6 @@ namespace internals {
1717

1818
class default_get_solution_callback_t : public get_solution_callback_t {
1919
public:
20-
PyObject* get_numba_matrix(void* data, std::size_t size)
21-
{
22-
PyObject* pycl = (PyObject*)this->pyCallbackClass;
23-
24-
if (isFloat) {
25-
return PyObject_CallMethod(pycl, "get_numba_matrix", "(lls)", data, size, "float32");
26-
} else {
27-
return PyObject_CallMethod(pycl, "get_numba_matrix", "(lls)", data, size, "float64");
28-
}
29-
}
30-
3120
PyObject* get_numpy_array(void* data, std::size_t size)
3221
{
3322
PyObject* pycl = (PyObject*)this->pyCallbackClass;
@@ -38,33 +27,33 @@ class default_get_solution_callback_t : public get_solution_callback_t {
3827
}
3928
}
4029

41-
void get_solution(void* data, void* objective_value) override
30+
void get_solution(void* data,
31+
void* objective_value,
32+
void* solution_bound,
33+
void* user_data) override
4234
{
43-
PyObject* numba_matrix = get_numba_matrix(data, n_variables);
44-
PyObject* numpy_array = get_numba_matrix(objective_value, 1);
45-
PyObject* res =
46-
PyObject_CallMethod(this->pyCallbackClass, "get_solution", "(OO)", numba_matrix, numpy_array);
47-
Py_DECREF(numba_matrix);
35+
PyObject* numpy_matrix = get_numpy_array(data, n_variables);
36+
PyObject* numpy_array = get_numpy_array(objective_value, 1);
37+
PyObject* numpy_bound = get_numpy_array(solution_bound, 1);
38+
PyObject* py_user_data = user_data == nullptr ? Py_None : static_cast<PyObject*>(user_data);
39+
PyObject* res = PyObject_CallMethod(this->pyCallbackClass,
40+
"get_solution",
41+
"(OOOO)",
42+
numpy_matrix,
43+
numpy_array,
44+
numpy_bound,
45+
py_user_data);
46+
Py_DECREF(numpy_matrix);
4847
Py_DECREF(numpy_array);
49-
Py_DECREF(res);
48+
Py_DECREF(numpy_bound);
49+
if (res != nullptr) { Py_DECREF(res); }
5050
}
5151

5252
PyObject* pyCallbackClass;
5353
};
5454

5555
class default_set_solution_callback_t : public set_solution_callback_t {
5656
public:
57-
PyObject* get_numba_matrix(void* data, std::size_t size)
58-
{
59-
PyObject* pycl = (PyObject*)this->pyCallbackClass;
60-
61-
if (isFloat) {
62-
return PyObject_CallMethod(pycl, "get_numba_matrix", "(lls)", data, size, "float32");
63-
} else {
64-
return PyObject_CallMethod(pycl, "get_numba_matrix", "(lls)", data, size, "float64");
65-
}
66-
}
67-
6857
PyObject* get_numpy_array(void* data, std::size_t size)
6958
{
7059
PyObject* pycl = (PyObject*)this->pyCallbackClass;
@@ -75,15 +64,26 @@ class default_set_solution_callback_t : public set_solution_callback_t {
7564
}
7665
}
7766

78-
void set_solution(void* data, void* objective_value) override
67+
void set_solution(void* data,
68+
void* objective_value,
69+
void* solution_bound,
70+
void* user_data) override
7971
{
80-
PyObject* numba_matrix = get_numba_matrix(data, n_variables);
81-
PyObject* numpy_array = get_numba_matrix(objective_value, 1);
82-
PyObject* res =
83-
PyObject_CallMethod(this->pyCallbackClass, "set_solution", "(OO)", numba_matrix, numpy_array);
84-
Py_DECREF(numba_matrix);
72+
PyObject* numpy_matrix = get_numpy_array(data, n_variables);
73+
PyObject* numpy_array = get_numpy_array(objective_value, 1);
74+
PyObject* numpy_bound = get_numpy_array(solution_bound, 1);
75+
PyObject* py_user_data = user_data == nullptr ? Py_None : static_cast<PyObject*>(user_data);
76+
PyObject* res = PyObject_CallMethod(this->pyCallbackClass,
77+
"set_solution",
78+
"(OOOO)",
79+
numpy_matrix,
80+
numpy_array,
81+
numpy_bound,
82+
py_user_data);
83+
Py_DECREF(numpy_matrix);
8584
Py_DECREF(numpy_array);
86-
Py_DECREF(res);
85+
Py_DECREF(numpy_bound);
86+
if (res != nullptr) { Py_DECREF(res); }
8787
}
8888

8989
PyObject* pyCallbackClass;

cpp/include/cuopt/linear_programming/utilities/internals.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,23 @@ class base_solution_callback_t : public Callback {
3131
this->n_variables = n_variables_;
3232
}
3333

34+
void set_user_data(void* input_user_data) { user_data = input_user_data; }
35+
void* get_user_data() const { return user_data; }
36+
3437
virtual base_solution_callback_type get_type() const = 0;
3538

3639
protected:
3740
bool isFloat = true;
3841
size_t n_variables = 0;
42+
void* user_data = nullptr;
3943
};
4044

4145
class get_solution_callback_t : public base_solution_callback_t {
4246
public:
43-
virtual void get_solution(void* data, void* objective_value) = 0;
47+
virtual void get_solution(void* data,
48+
void* objective_value,
49+
void* solution_bound,
50+
void* user_data) = 0;
4451
base_solution_callback_type get_type() const override
4552
{
4653
return base_solution_callback_type::GET_SOLUTION;
@@ -49,7 +56,10 @@ class get_solution_callback_t : public base_solution_callback_t {
4956

5057
class set_solution_callback_t : public base_solution_callback_t {
5158
public:
52-
virtual void set_solution(void* data, void* objective_value) = 0;
59+
virtual void set_solution(void* data,
60+
void* objective_value,
61+
void* solution_bound,
62+
void* user_data) = 0;
5363
base_solution_callback_type get_type() const override
5464
{
5565
return base_solution_callback_type::SET_SOLUTION;

cpp/src/dual_simplex/branch_and_bound.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ void branch_and_bound_t<i_t, f_t>::report_heuristic(f_t obj)
282282
template <typename i_t, typename f_t>
283283
void branch_and_bound_t<i_t, f_t>::report(char symbol, f_t obj, f_t lower_bound, i_t node_depth)
284284
{
285+
update_user_bound(lower_bound);
285286
i_t nodes_explored = exploration_stats_.nodes_explored;
286287
i_t nodes_unexplored = exploration_stats_.nodes_unexplored;
287288
f_t user_obj = compute_user_objective(original_lp_, obj);
@@ -300,6 +301,14 @@ void branch_and_bound_t<i_t, f_t>::report(char symbol, f_t obj, f_t lower_bound,
300301
toc(exploration_stats_.start_time));
301302
}
302303

304+
template <typename i_t, typename f_t>
305+
void branch_and_bound_t<i_t, f_t>::update_user_bound(f_t lower_bound)
306+
{
307+
if (user_bound_callback_ == nullptr) { return; }
308+
f_t user_lower = compute_user_objective(original_lp_, lower_bound);
309+
user_bound_callback_(user_lower);
310+
}
311+
303312
template <typename i_t, typename f_t>
304313
void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solution)
305314
{
@@ -335,9 +344,10 @@ void branch_and_bound_t<i_t, f_t>::set_new_solution(const std::vector<f_t>& solu
335344
num_fractional);
336345
}
337346
}
347+
} else {
348+
settings_.log.debug("Solution objective not better than current upper_bound_. Not accepted.\n");
338349
}
339350
mutex_upper_.unlock();
340-
341351
if (is_feasible) { report_heuristic(obj); }
342352
if (attempt_repair) {
343353
mutex_repair_.lock();

0 commit comments

Comments
 (0)