Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4587b1b
increase the absolute gap tolerance upper limit to infinity
rg20 Jun 16, 2025
a1fc6c2
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jun 17, 2025
646bcde
Fix a bug in copy_from
rg20 Jun 17, 2025
8cb604f
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jun 18, 2025
0c78b52
Fix handling of objective offset
rg20 Jun 18, 2025
6573347
Handle integer bounds correctly
rg20 Jun 19, 2025
89a6443
Support solution bound for LP as well
rg20 Jun 25, 2025
3955d69
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jun 25, 2025
c91a21b
Unit tests for the bugs fixed
rg20 Jun 26, 2025
19dba1e
Remove a comment
rg20 Jun 26, 2025
1131448
Make sure to negate the objective offset as well
rg20 Jun 26, 2025
76424b5
Wire the tolerances correctly in problem construction
rg20 Jun 27, 2025
4c3f557
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jun 27, 2025
2a92cef
Implement cuOptGetDualObjectiveValue
rg20 Jun 30, 2025
c903bf3
Throw compilation error when cuopt is built with pre volta architectures
rg20 Jun 30, 2025
c353106
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jun 30, 2025
2f7aff1
Revert solution bound change for LP
rg20 Jul 1, 2025
f94747a
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jul 1, 2025
7c4d589
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jul 2, 2025
43e2d60
Renaming file
rg20 Jul 2, 2025
a754b1c
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jul 2, 2025
a784731
Merge remote-tracking branch 'upstream/branch-25.08' into jump_interf…
rg20 Jul 3, 2025
d18c8b2
Fix failing test
rg20 Jul 7, 2025
d77cb4d
Change cuOptGetVersion signature
rg20 Jul 8, 2025
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
28 changes: 28 additions & 0 deletions cpp/include/cuopt/linear_programming/cuopt_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ int8_t cuOptGetFloatSize();
*/
int8_t cuOptGetIntSize();

/**
* @brief Get the version of the library.
*
* @param[out] version_major - A pointer to a cuopt_int_t that will contain the major version
* number.
* @param[out] version_minor - A pointer to a cuopt_int_t that will contain the minor version
* number.
* @param[out] version_patch - A pointer to a cuopt_int_t that will contain the patch version
* number.
*
* @return A status code indicating success or failure.
*/
cuopt_int_t cuOptGetVersion(cuopt_int_t* version_major,
cuopt_int_t* version_minor,
cuopt_int_t* version_patch);

/**
* @brief Read an optimization problem from an MPS file.
*
Expand Down Expand Up @@ -656,6 +672,18 @@ cuopt_int_t cuOptGetSolutionBound(cuOptSolution solution, cuopt_float_t* solutio
*/
cuopt_int_t cuOptGetDualSolution(cuOptSolution solution, cuopt_float_t* dual_solution_ptr);

/** @brief Get the dual objective value of an optimization problem.
*
* @param[in] solution - The solution object.
*
* @param[in, out] dual_objective_value_ptr - A pointer to a cuopt_float_t that will contain the
* dual objective value.
*
* @return A status code indicating success or failure.
*/
cuopt_int_t cuOptGetDualObjectiveValue(cuOptSolution solution,
cuopt_float_t* dual_objective_value_ptr);

/** @brief Get the reduced costs of an optimization problem.
*
* @param[in] solution - The solution object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ class optimization_problem_solution_t : public base_solution_t {
*/
f_t get_objective_value() const;

/**
* @brief Returns the dual objective value of the solution as a `f_t`.
* @return objective value of the dual problem
*/
f_t get_dual_objective_value() const;

/**
* @brief Returns the solution for the values of the primal variables as a vector of `f_t`.
*
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/dual_simplex/solve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ template <typename i_t, typename f_t>
f_t compute_user_objective(const lp_problem_t<i_t, f_t>& lp, const std::vector<f_t>& x)
{
const f_t obj = compute_objective(lp, x);
const f_t user_obj = obj * lp.obj_scale + lp.obj_constant;
const f_t user_obj = compute_user_objective(lp, obj);
return user_obj;
}

template <typename i_t, typename f_t>
f_t compute_user_objective(const lp_problem_t<i_t, f_t>& lp, f_t obj)
{
const f_t user_obj = obj * lp.obj_scale + lp.obj_constant;
const f_t user_obj = lp.obj_scale * (obj + lp.obj_constant);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you are changing the convention for how the scaling is done here. I'm not sure why the current convention was chosen. Can you check with @aliceb-nv or @akifcorduk before changing this convention?

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason we chose the current convention was to handle obj_scale values other than 1 or -1. The new way is equivalent only if obj_scale is 1 or -1.

return user_obj;
}

Expand Down
33 changes: 33 additions & 0 deletions cpp/src/linear_programming/cuopt_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

#include <mps_parser/parser.hpp>

#include <cuopt/version_config.hpp>

#include <memory>
#include <string>

Expand Down Expand Up @@ -59,6 +61,19 @@ int8_t cuOptGetFloatSize() { return sizeof(cuopt_float_t); }

int8_t cuOptGetIntSize() { return sizeof(cuopt_int_t); }

cuopt_int_t cuOptGetVersion(cuopt_int_t* version_major,
cuopt_int_t* version_minor,
cuopt_int_t* version_patch)
{
if (version_major == nullptr || version_minor == nullptr || version_patch == nullptr) {
return CUOPT_INVALID_ARGUMENT;
}
*version_major = CUOPT_VERSION_MAJOR;
*version_minor = CUOPT_VERSION_MINOR;
*version_patch = CUOPT_VERSION_PATCH;
return CUOPT_SUCCESS;
}

cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* problem_ptr)
{
problem_and_stream_view_t* problem_and_stream = new problem_and_stream_view_t();
Expand Down Expand Up @@ -825,6 +840,24 @@ cuopt_int_t cuOptGetDualSolution(cuOptSolution solution, cuopt_float_t* dual_sol
}
}

cuopt_int_t cuOptGetDualObjectiveValue(cuOptSolution solution,
cuopt_float_t* dual_objective_value_ptr)
{
if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; }
if (dual_objective_value_ptr == nullptr) { return CUOPT_INVALID_ARGUMENT; }
solution_and_stream_view_t* solution_and_stream_view =
static_cast<solution_and_stream_view_t*>(solution);
if (solution_and_stream_view->is_mip) {
return CUOPT_INVALID_ARGUMENT;
} else {
optimization_problem_solution_t<cuopt_int_t, cuopt_float_t>* optimization_problem_solution =
static_cast<optimization_problem_solution_t<cuopt_int_t, cuopt_float_t>*>(
solution_and_stream_view->lp_solution_ptr);
*dual_objective_value_ptr = optimization_problem_solution->get_dual_objective_value();
return CUOPT_SUCCESS;
}
}

cuopt_int_t cuOptGetReducedCosts(cuOptSolution solution, cuopt_float_t* reduced_cost_ptr)
{
if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; }
Expand Down
6 changes: 6 additions & 0 deletions cpp/src/linear_programming/solver_solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ f_t optimization_problem_solution_t<i_t, f_t>::get_objective_value() const
return termination_stats_.primal_objective;
}

template <typename i_t, typename f_t>
f_t optimization_problem_solution_t<i_t, f_t>::get_dual_objective_value() const
{
return termination_stats_.dual_objective;
}

template <typename i_t, typename f_t>
rmm::device_uvector<f_t>& optimization_problem_solution_t<i_t, f_t>::get_primal_solution()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ __global__ void apply_objective_scaling_and_offset(f_t* objective,
{
if (threadIdx.x + blockIdx.x * blockDim.x > 0) { return; }

*objective = (objective_scaling_factor * *objective) + objective_offset;
*objective = objective_scaling_factor * (*objective + objective_offset);
}

template <typename i_t, typename f_t>
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_ABSOLUTE_TOLERANCE, &mip_settings.tolerances.absolute_tolerance, 0.0, 1e-1, 1e-4},
{CUOPT_MIP_RELATIVE_TOLERANCE, &mip_settings.tolerances.relative_tolerance, 0.0, 1e-1, 1e-4},
{CUOPT_MIP_INTEGRALITY_TOLERANCE, &mip_settings.tolerances.integrality_tolerance, 0.0, 1e-1, 1e-5},
{CUOPT_MIP_ABSOLUTE_GAP, &mip_settings.tolerances.absolute_mip_gap, 0.0, 1e-1, 1e-10},
{CUOPT_MIP_ABSOLUTE_GAP, &mip_settings.tolerances.absolute_mip_gap, 0.0, CUOPT_INFINITY, 1e-10},
{CUOPT_MIP_RELATIVE_GAP, &mip_settings.tolerances.relative_mip_gap, 0.0, 1e-1, 1e-4},
{CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.primal_infeasible_tolerance, 0.0, 1e-1, 1e-8},
{CUOPT_DUAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.dual_infeasible_tolerance, 0.0, 1e-1, 1e-8}
Expand Down
1 change: 0 additions & 1 deletion cpp/src/mip/presolve/trivial_presolve.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ void update_from_csr(problem_t<i_t, f_t>& pb)

// update objective_offset
pb.presolve_data.objective_offset +=
pb.presolve_data.objective_scaling_factor *
thrust::transform_reduce(handle_ptr->get_thrust_policy(),
thrust::counting_iterator<i_t>(0),
thrust::counting_iterator<i_t>(pb.n_variables),
Expand Down
22 changes: 16 additions & 6 deletions cpp/src/mip/problem/problem.cu
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,26 @@ void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t,

// Set variables bounds to default if not set and constraints bounds if user has set a row type
set_bounds_if_not_set(*this);

const bool is_mip = original_problem_ptr->get_problem_category() != problem_category_t::LP;
if (is_mip) {
variable_types =
rmm::device_uvector<var_t>(problem_.get_variable_types(), handle_ptr->get_stream());
// round bounds to integer for integer variables, note: do this before checking sanity
round_bounds(*this);
}

// check bounds sanity before, so that we can throw exceptions before going into asserts
check_bounds_sanity(*this);

// Check before any modifications
check_problem_representation(false, false);
// If maximization problem, convert the problem
if (maximize) convert_to_maximization_problem(*this);

const bool is_mip = original_problem_ptr->get_problem_category() != problem_category_t::LP;
if (is_mip) {
// Resize what is needed for MIP
raft::common::nvtx::range scope("trivial_presolve");
variable_types =
rmm::device_uvector<var_t>(problem_.get_variable_types(), handle_ptr->get_stream());
integer_indices.resize(n_variables, handle_ptr->get_stream());
is_binary_variable.resize(n_variables, handle_ptr->get_stream());
compute_n_integer_vars();
Expand All @@ -93,7 +100,9 @@ void problem_t<i_t, f_t>::op_problem_cstr_body(const optimization_problem_t<i_t,
}

template <typename i_t, typename f_t>
problem_t<i_t, f_t>::problem_t(const optimization_problem_t<i_t, f_t>& problem_)
problem_t<i_t, f_t>::problem_t(
const optimization_problem_t<i_t, f_t>& problem_,
const typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances_)
: original_problem_ptr(&problem_),
handle_ptr(problem_.get_handle_ptr()),
n_variables(problem_.get_n_variables()),
Expand Down Expand Up @@ -129,7 +138,8 @@ problem_t<i_t, f_t>::problem_t(const optimization_problem_t<i_t, f_t>& problem_)
related_variables_offsets(n_variables, problem_.get_handle_ptr()->get_stream()),
var_names(problem_.get_variable_names()),
row_names(problem_.get_row_names()),
objective_name(problem_.get_objective_name())
objective_name(problem_.get_objective_name()),
tolerances(tolerances_)
{
op_problem_cstr_body(problem_);
branch_and_bound_callback = nullptr;
Expand Down Expand Up @@ -1430,7 +1440,7 @@ void problem_t<i_t, f_t>::get_host_user_problem(
template <typename i_t, typename f_t>
f_t problem_t<i_t, f_t>::get_user_obj_from_solver_obj(f_t solver_obj)
{
return solver_obj * presolve_data.objective_scaling_factor + presolve_data.objective_offset;
return presolve_data.objective_scaling_factor * (solver_obj + presolve_data.objective_offset);
}

#if MIP_INSTANTIATE_FLOAT
Expand Down
3 changes: 2 additions & 1 deletion cpp/src/mip/problem/problem.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ constexpr bool USE_REL_TOLERANCE = true;
template <typename i_t, typename f_t>
class problem_t {
public:
problem_t(const optimization_problem_t<i_t, f_t>& problem);
problem_t(const optimization_problem_t<i_t, f_t>& problem,
const typename mip_solver_settings_t<i_t, f_t>::tolerances_t tolerances_ = {});
problem_t() = delete;
// copy constructor
problem_t(const problem_t<i_t, f_t>& problem);
Expand Down
20 changes: 20 additions & 0 deletions cpp/src/mip/problem/problem_helpers.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ static void convert_to_maximization_problem(detail::problem_t<i_t, f_t>& op_prob
// negating objective coeffs
op_problem.presolve_data.objective_scaling_factor =
-op_problem.presolve_data.objective_scaling_factor;

// Negate objective offset
op_problem.presolve_data.objective_offset = -op_problem.presolve_data.objective_offset;
}

/*
Expand Down Expand Up @@ -277,6 +280,23 @@ static bool check_constraint_bounds_sanity(const detail::problem_t<i_t, f_t>& pr
return !crossing_bounds_detected;
}

template <typename i_t, typename f_t>
static void round_bounds(detail::problem_t<i_t, f_t>& problem)
{
// round bounds to integer for integer variables
thrust::for_each(problem.handle_ptr->get_thrust_policy(),
thrust::make_counting_iterator(0),
thrust::make_counting_iterator(problem.n_variables),
[lb = make_span(problem.variable_lower_bounds),
ub = make_span(problem.variable_upper_bounds),
types = make_span(problem.variable_types)] __device__(i_t index) {
if (types[index] == var_t::INTEGER) {
lb[index] = ceil(lb[index]);
ub[index] = floor(ub[index]);
}
});
}

template <typename i_t, typename f_t>
static bool check_bounds_sanity(const detail::problem_t<i_t, f_t>& problem)
{
Expand Down
9 changes: 6 additions & 3 deletions cpp/src/mip/solution/solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ mip_solution_t<i_t, f_t> solution_t<i_t, f_t>::get_solution(bool output_feasible
if (output_feasible) {
// TODO we can streamline these info in class
f_t rel_mip_gap = compute_rel_mip_gap(h_user_obj, stats.solution_bound);
f_t abs_mip_gap = fabs(h_user_obj - stats.solution_bound);
f_t solution_bound = stats.solution_bound;
f_t max_constraint_violation = compute_max_constraint_violation();
f_t max_int_violation = compute_max_int_violation();
Expand All @@ -590,9 +591,11 @@ mip_solution_t<i_t, f_t> solution_t<i_t, f_t>::get_solution(bool output_feasible
max_variable_bound_violation,
num_nodes,
num_simplex_iterations);
const double optimality_threshold = problem_ptr->tolerances.relative_mip_gap;
auto term_reason = rel_mip_gap > optimality_threshold ? mip_termination_status_t::FeasibleFound
: mip_termination_status_t::Optimal;

const bool not_optimal = rel_mip_gap > problem_ptr->tolerances.relative_mip_gap &&
abs_mip_gap > problem_ptr->tolerances.absolute_mip_gap;
auto term_reason =
not_optimal ? mip_termination_status_t::FeasibleFound : mip_termination_status_t::Optimal;
if (is_problem_fully_reduced) { term_reason = mip_termination_status_t::Optimal; }
return mip_solution_t<i_t, f_t>(std::move(assignment),
problem_ptr->var_names,
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/mip/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ mip_solution_t<i_t, f_t> solve_mip(optimization_problem_t<i_t, f_t>& op_problem,
problem_checking_t<i_t, f_t>::check_initial_solution_representation(op_problem, settings);

// have solve, problem, solution, utils etc. in common dir
detail::problem_t<i_t, f_t> problem(op_problem);
detail::problem_t<i_t, f_t> problem(op_problem, settings.get_tolerances());
if (settings.user_problem_file != "") {
CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str());
problem.write_as_mps(settings.user_problem_file);
Expand Down
2 changes: 0 additions & 2 deletions cpp/src/mip/solver.cu
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ mip_solver_t<i_t, f_t>::mip_solver_t(const problem_t<i_t, f_t>& op_problem,
timer_(timer)
{
init_handler(op_problem.handle_ptr);

context.problem_ptr->tolerances = solver_settings.get_tolerances();
}

template <typename i_t, typename f_t>
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/utilities/cuda_helpers.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@

namespace cuopt {

#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 700)
#error "cuOpt is only supported on Volta and newer architectures"
#endif

/** helper macro for device inlined functions */
#define DI inline __device__
#define HDI inline __host__ __device__
Expand Down
17 changes: 17 additions & 0 deletions cpp/tests/linear_programming/pdlp_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,23 @@ TEST(pdlp_class, test_max_with_offset)
solution.get_additional_termination_information().primal_objective, 0.0, factor_tolerance);
}

TEST(pdlp_class, test_lp_no_constraints)
{
const raft::handle_t handle_{};

auto path = make_path_absolute("linear_programming/lp-model-no-constraints.mps");
cuopt::mps_parser::mps_data_model_t<int, double> op_problem =
cuopt::mps_parser::parse_mps<int, double>(path);

auto solver_settings = pdlp_solver_settings_t<int, double>{};

optimization_problem_solution_t<int, double> solution =
solve_lp(&handle_, op_problem, solver_settings);
EXPECT_EQ((int)solution.get_termination_status(), CUOPT_TERIMINATION_STATUS_OPTIMAL);
EXPECT_NEAR(
solution.get_additional_termination_information().primal_objective, 1.0, factor_tolerance);
}

} // namespace cuopt::linear_programming::test

CUOPT_TEST_PROGRAM_MAIN()
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
namespace cuopt::linear_programming::test {
constexpr double tolerance = 1e-6f;

std::string make_path_absolute(const std::string& file)
static std::string make_path_absolute(const std::string& file)
{
std::string rel_file{};
// assume relative paths are relative to RAPIDS_DATASET_ROOT_DIR
Expand Down
1 change: 1 addition & 0 deletions cpp/tests/mip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ ConfigureTest(DOC_EXAMPLE_TEST
)
ConfigureTest(UNIT_TEST
${CMAKE_CURRENT_SOURCE_DIR}/unit_test.cu
${CMAKE_CURRENT_SOURCE_DIR}/integer_with_real_bounds.cu
)
ConfigureTest(EMPTY_FIXED_PROBLEMS_TEST
${CMAKE_CURRENT_SOURCE_DIR}/empty_fixed_problems_test.cu
Expand Down
Loading