Skip to content
Merged
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
64 changes: 64 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ jobs:
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
script: ci/build_python.sh
upload-conda:
needs: [cpp-build, python-build]
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
wheel-build-cuopt-mps-parser:
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
Expand All @@ -60,6 +69,17 @@ jobs:
package-name: cuopt_mps_parser
package-type: python
append-cuda-suffix: false
wheel-publish-cuopt-mps-parser:
needs: wheel-build-cuopt-mps-parser
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
sha: ${{ inputs.sha }}
date: ${{ inputs.date }}
package-name: cuopt_mps_parser
package-type: python
wheel-build-libcuopt:
needs: wheel-build-cuopt-mps-parser
secrets: inherit
Expand All @@ -73,6 +93,17 @@ jobs:
package-name: libcuopt
package-type: cpp
matrix_filter: map(select((.CUDA_VER | startswith("12")) and .PY_VER == "3.12"))
wheel-publish-libcuopt:
needs: wheel-build-libcuopt
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
sha: ${{ inputs.sha }}
date: ${{ inputs.date }}
package-name: libcuopt
package-type: cpp
wheel-build-cuopt:
needs: [wheel-build-cuopt-mps-parser, wheel-build-libcuopt]
secrets: inherit
Expand All @@ -86,6 +117,17 @@ jobs:
script: ci/build_wheel_cuopt.sh
package-name: cuopt
package-type: python
wheel-publish-cuopt:
needs: wheel-build-cuopt
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
sha: ${{ inputs.sha }}
date: ${{ inputs.date }}
package-name: cuopt
package-type: python
wheel-build-cuopt-server:
needs: wheel-build-cuopt
secrets: inherit
Expand All @@ -99,6 +141,17 @@ jobs:
script: ci/build_wheel_cuopt_server.sh
package-name: cuopt_server
package-type: python
wheel-publish-cuopt-server:
needs: wheel-build-cuopt-server
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
sha: ${{ inputs.sha }}
date: ${{ inputs.date }}
package-name: cuopt_server
package-type: python
#docs-build:
# if: inputs.build_type == 'nightly' || github.ref_type == 'branch'
# needs: [python-build]
Expand Down Expand Up @@ -127,6 +180,17 @@ jobs:
package-name: cuopt_sh_client
package-type: python
append-cuda-suffix: false
wheel-publish-cuopt-sh-client:
needs: wheel-build-cuopt-sh-client
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
sha: ${{ inputs.sha }}
date: ${{ inputs.date }}
package-name: cuopt_sh_client
package-type: python
service-container:
if: inputs.build_type == 'nightly'
needs: [wheel-build-cuopt, wheel-build-cuopt-server]
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ jobs:
export DATE=$(date +%F)
export SHA=$(gh api -q '.commit.sha' "repos/nvidia/cuopt/branches/${CUOPT_BRANCH}")

gh workflow run build.yaml \
RUN_ID=$(gh workflow run build.yaml \
-f branch=${CUOPT_BRANCH} \
-f sha=${SHA} \
-f date=${DATE} \
-f build_type=nightly
-f build_type=nightly \
--json databaseId --jq '.databaseId')

# Wait for workflow to complete
gh run watch $RUN_ID

trigger-test:
runs-on: ubuntu-latest
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ For CUDA 12.x:
pip install --extra-index-url=https://pypi.nvidia.com cuopt-server-cu12==25.5.* cuopt-sh-client==25.5.* nvidia-cuda-runtime-cu12==12.8.*
```

Development wheels are available as nightlies, please update `--extra-index-url` to `https://pypi.anaconda.org/rapidsai-wheels-nightly/simple/` to install latest nightly packages.

### Conda

cuOpt can be installed with conda (via [miniforge](https://github.com/conda-forge/miniforge)) from the `nvidia` channel:
Expand All @@ -74,19 +76,22 @@ Users who are used to conda env based workflows would benefit with conda package
For CUDA 12.x:
```bash
conda install -c rapidsai -c conda-forge -c nvidia \
cuopt-server=25.05 cuopt-sh-client=25.05 python=3.12 cuda-version=12.8
cuopt-server=25.05.* cuopt-sh-client=25.05.* python=3.12 cuda-version=12.8
```

We also provide [nightly Conda packages](https://anaconda.org/rapidsai-nightly) built from the HEAD
of our latest development branch.
of our latest development branch. Just replace `-c rapidsai` with `-c rapidsai-nightly`.

### Container

Users can pull the cuOpt container from the NVIDIA container registry.

```bash
docker pull nvidia/cuopt:25.5.0-cuda12.8-py312
docker pull nvidia/cuopt:latest-cuda12.8-py312
```

Note: The ``latest`` tag is the latest stable release of cuOpt. If you want to use a specific version, you can use the ``<version>-cuda12.8-py312`` tag. For example, to use cuOpt 25.5.0, you can use the ``25.5.0-cuda12.8-py312`` tag. Please refer to `cuOpt dockerhub page <https://hub.docker.com/r/nvidia/cuopt>`_ for the list of available tags.

More information about the cuOpt container can be found [here](https://docs.nvidia.com/cuopt/user-guide/latest/cuopt-server/quick-start.html#container-from-docker-hub).

Users who are using cuOpt for quick testing or research can use the cuOpt container. Alternatively, users who are planning to plug cuOpt as a service in their workflow can quickly start with the cuOpt container. But users are required to build security layers around the service to safeguard the service from untrusted users.
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
#define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap"
#define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only"
#define CUOPT_MIP_SCALING "mip_scaling"
#define CUOPT_SOL_FILE "solution_file"
#define CUOPT_SOLUTION_FILE "solution_file"
#define CUOPT_NUM_CPU_THREADS "num_cpu_threads"
#define CUOPT_USER_PROBLEM_FILE "user_problem_file"

/* @brief LP/MIP termination status constants */
#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class mip_solver_settings_t {
bool log_to_console = true;
std::string log_file;
std::string sol_file;
std::string user_problem_file;

/** Initial primal solution */
std::shared_ptr<rmm::device_uvector<f_t>> initial_solution_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ class pdlp_solver_settings_t {
bool log_to_console{true};
std::string log_file{""};
std::string sol_file{""};
std::string user_problem_file{""};
bool per_constraint_residual{false};
bool crossover{false};
bool save_best_primal_so_far{false};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ struct solver_ret_t {
// Wrapper for solve to expose the API to cython.

std::unique_ptr<solver_ret_t> call_solve(cuopt::mps_parser::data_model_view_t<int, double>*,
linear_programming::solver_settings_t<int, double>*);
linear_programming::solver_settings_t<int, double>*,
unsigned int flags = cudaStreamNonBlocking);

std::pair<std::vector<std::unique_ptr<solver_ret_t>>, double> call_batch_solve(
std::vector<cuopt::mps_parser::data_model_view_t<int, double>*>,
Expand Down
6 changes: 4 additions & 2 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,10 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
global_variables::mutex_upper.unlock();
// We should be done here
uncrush_primal_solution(original_problem, original_lp, incumbent.x, solution.x);
solution.objective = incumbent.objective;
solution.lower_bound = lower_bound;
solution.objective = incumbent.objective;
solution.lower_bound = lower_bound;
solution.nodes_explored = 0;
solution.simplex_iterations = root_relax_soln.iterations;
settings.log.printf("Optimal solution found at root node. Objective %.16e. Time %.2f.\n",
compute_user_objective(original_lp, root_objective),
toc(start_time));
Expand Down
5 changes: 5 additions & 0 deletions cpp/src/linear_programming/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,11 @@ optimization_problem_solution_t<i_t, f_t> solve_lp(optimization_problem_t<i_t, f
problem.presolve_data.objective_offset,
problem.presolve_data.objective_scaling_factor);

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);
}

// Set the hyper-parameters based on the solver_settings
if (use_pdlp_solver_mode) { set_pdlp_solver_mode(settings); }

Expand Down
9 changes: 6 additions & 3 deletions cpp/src/linear_programming/utilities/cython_solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,13 @@ mip_ret_t call_solve_mip(

std::unique_ptr<solver_ret_t> call_solve(
cuopt::mps_parser::data_model_view_t<int, double>* data_model,
cuopt::linear_programming::solver_settings_t<int, double>* solver_settings)
cuopt::linear_programming::solver_settings_t<int, double>* solver_settings,
unsigned int flags)
{
raft::common::nvtx::range fun_scope("Call Solve");

cudaStream_t stream;
RAFT_CUDA_TRY(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking));
RAFT_CUDA_TRY(cudaStreamCreateWithFlags(&stream, flags));
const raft::handle_t handle_{stream};

auto op_problem = data_model_to_optimization_problem(data_model, solver_settings, &handle_);
Expand Down Expand Up @@ -283,9 +284,11 @@ std::pair<std::vector<std::unique_ptr<solver_ret_t>>, double> call_batch_solve(
solver_settings->set_parameter(CUOPT_METHOD, CUOPT_METHOD_PDLP);
}

// Use a default stream instead of a non-blocking to avoid invalid operations while some CUDA
// Graph might be capturing in another stream
#pragma omp parallel for num_threads(max_thread)
for (std::size_t i = 0; i < size; ++i)
list[i] = std::move(call_solve(data_models[i], solver_settings));
list[i] = std::move(call_solve(data_models[i], solver_settings, cudaStreamDefault));

auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_solver);
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/linear_programming/utilities/logger_init.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class init_logger_t {
// TODO save the defaul sink and restore it
cuopt::default_logger().sinks().push_back(
std::make_shared<rapids_logger::basic_file_sink_mt>(log_file, true));
cuopt::default_logger().set_pattern("%v");
cuopt::default_logger().flush_on(rapids_logger::level_enum::info);
}
}
~init_logger_t() { cuopt::reset_default_logger(); }
Expand Down
6 changes: 4 additions & 2 deletions cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
string_parameters = {
{CUOPT_LOG_FILE, &mip_settings.log_file, ""},
{CUOPT_LOG_FILE, &pdlp_settings.log_file, ""},
{CUOPT_SOL_FILE, &mip_settings.sol_file, ""},
{CUOPT_SOL_FILE, &pdlp_settings.sol_file, ""}
{CUOPT_SOLUTION_FILE, &mip_settings.sol_file, ""},
{CUOPT_SOLUTION_FILE, &pdlp_settings.sol_file, ""},
{CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""},
{CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""}
};
// clang-format on
}
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip/presolve/trivial_presolve.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ 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
6 changes: 4 additions & 2 deletions cpp/src/mip/problem/write_mps.cu
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
// NAME section
mps_file << "NAME " << original_problem_ptr->get_problem_name() << "\n";

if (maximize) { mps_file << "OBJSENSE\n MAXIMIZE\n"; }

// ROWS section
mps_file << "ROWS\n";
mps_file << " N " << (objective_name.empty() ? "OBJ" : objective_name) << "\n";
Expand Down Expand Up @@ -86,7 +88,7 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
// Write objective coefficient if non-zero
if (h_obj_coeffs[j] != 0.0) {
mps_file << " " << col_name << " " << (objective_name.empty() ? "OBJ" : objective_name)
<< " " << h_obj_coeffs[j] << "\n";
<< " " << (maximize ? -h_obj_coeffs[j] : h_obj_coeffs[j]) << "\n";
}

// Write constraint coefficients
Expand Down Expand Up @@ -146,7 +148,7 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
h_var_ub[j] == std::numeric_limits<f_t>::infinity()) {
mps_file << " FR BOUND1 " << col_name << "\n";
} else {
if (h_var_lb[j] != 0.0) {
if (h_var_lb[j] != 0.0 || h_obj_coeffs[j] == 0.0 || h_var_types[j] != var_t::CONTINUOUS) {
if (h_var_lb[j] == -std::numeric_limits<f_t>::infinity()) {
mps_file << " MI BOUND1 " << col_name << "\n";
} else {
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/mip/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ mip_solution_t<i_t, f_t> solve_mip(optimization_problem_t<i_t, f_t>& op_problem,

// have solve, problem, solution, utils etc. in common dir
detail::problem_t<i_t, f_t> problem(op_problem);
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);
}

// this is for PDLP, i think this should be part of pdlp solver
setup_device_symbols(op_problem.get_handle_ptr()->get_stream());
Expand Down
45 changes: 45 additions & 0 deletions cpp/tests/mip/doc_example_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <gtest/gtest.h>

#include <cstdint>
#include <filesystem>
#include <sstream>
#include <string>
#include <vector>
Expand Down Expand Up @@ -120,4 +121,48 @@ void test_mps_file()

TEST(docs, mixed_integer_linear_programming) { test_mps_file(); }

TEST(docs, user_problem_file)
{
const raft::handle_t handle_{};
mip_solver_settings_t<int, double> settings;
constexpr double test_time_limit = 1.;

// Create the problem from documentation example
auto problem = create_doc_example_problem();

EXPECT_FALSE(std::filesystem::exists("user_problem.mps"));

settings.time_limit = test_time_limit;
settings.user_problem_file = "user_problem.mps";
EXPECT_EQ(solve_mip(&handle_, problem, settings).get_termination_status(),
mip_termination_status_t::Optimal);

EXPECT_TRUE(std::filesystem::exists("user_problem.mps"));

cuopt::mps_parser::mps_data_model_t<int, double> problem2 =
cuopt::mps_parser::parse_mps<int, double>("user_problem.mps", false);

EXPECT_EQ(problem2.get_n_variables(), problem.get_n_variables());
EXPECT_EQ(problem2.get_n_constraints(), problem.get_n_constraints());
EXPECT_EQ(problem2.get_nnz(), problem.get_nnz());

settings.user_problem_file = "user_problem2.mps";
mip_solution_t<int, double> solution = solve_mip(&handle_, problem2, settings);
EXPECT_EQ(solution.get_termination_status(), mip_termination_status_t::Optimal);

double obj_val = solution.get_objective_value();
// Expected objective value from documentation example is approximately 303.5
EXPECT_NEAR(303.5, obj_val, 1.0);

// Get solution values
const auto& sol_values = solution.get_solution();
// x should be approximately 37 and integer
EXPECT_NEAR(37.0, sol_values.element(0, handle_.get_stream()), 0.1);
EXPECT_NEAR(std::round(sol_values.element(0, handle_.get_stream())),
sol_values.element(0, handle_.get_stream()),
settings.tolerances.integrality_tolerance); // Check x is integer
// y should be approximately 39.5
EXPECT_NEAR(39.5, sol_values.element(1, handle_.get_stream()), 0.1);
}

} // namespace cuopt::linear_programming::test
Loading
Loading