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
36 changes: 28 additions & 8 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,35 @@ jobs:
export DATE=$(date +%F)
export SHA=$(gh api -q '.commit.sha' "repos/nvidia/cuopt/branches/${CUOPT_BRANCH}")

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

# Wait a short bit for the workflow to register (optional)
sleep 3

# Get the latest run ID for this workflow on this branch
RUN_ID=$(gh run list --workflow=build.yaml --branch="${CUOPT_BRANCH}" --json databaseId --limit 1 | jq -r '.[0].databaseId')

STATUS=$(gh run view $RUN_ID --json status,conclusion --jq '.status')
CONCLUSION=$(gh run view $RUN_ID --json status,conclusion --jq '.conclusion')

while [[ "$STATUS" != "completed" || "$CONCLUSION" == "null" ]]; do
echo "Status: $STATUS, Conclusion: $CONCLUSION — waiting 10 seconds..."
sleep 10
STATUS=$(gh run view $RUN_ID --json status,conclusion --jq '.status')
CONCLUSION=$(gh run view $RUN_ID --json status,conclusion --jq '.conclusion')
done

echo "Workflow run finished with conclusion: $CONCLUSION"

if [[ "$CONCLUSION" != "success" ]]; then
echo "Build did not succeed"
exit 1
fi

# Wait for workflow to complete
gh run watch $RUN_ID

trigger-test:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/[email protected]
with:
run_codecov: false
build_type: ${{ inputs.build_type }}
branch: ${{ inputs.branch }}
date: ${{ inputs.date }}
Expand Down
25 changes: 9 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,10 @@ source and contribute to its development. Other operating systems may be compati
currently tested.

Building NVIDIA cuOpt with the provided conda environment is recommended for users who wish to enable all
library features. The following instructions are for building with a conda environment. Dependencies
for a minimal build of NVIDIA cuOpt without using conda are also listed below.
library features. The following instructions are for building with a conda environment.

### General requirements

Compilers:

These will be installed while creating the Conda environment

* `gcc` version 13.0+
* `nvcc` version 12.8+
* `cmake` version 3.30.4+

CUDA/GPU Runtime:

* CUDA 12.8
Expand Down Expand Up @@ -107,11 +98,13 @@ cd $CUOPT_HOME

#### Building with a conda environment

**Note:** Using a conda environment is the easiest way to satisfy the library's dependencies.
**Note:** Building from source without conda is very difficult. We highly recommend that users build cuOpt inside a conda environment

- Create the conda development environment:

Please install conda if you don't have it already. You can install it from [https://docs.conda.io/en/latest/miniconda.html](https://docs.conda.io/en/latest/miniconda.html)
Please install conda if you don't have it already. You can install [miniforge](https://conda-forge.org/download/) or [miniconda](https://www.anaconda.com/docs/getting-started/miniconda/install#linux)

**Note:** We recommend using [mamba](https://mamba.readthedocs.io/en/latest/installation/mamba-installation.html) as the package manager for the conda environment. Mamba is faster and more efficient than conda. And it's the default package manager for miniforge. If you are using mamba just replace `conda` with `mamba` in the following commands.

```bash
# create the conda environment (assuming in base `cuopt` directory)
Expand Down Expand Up @@ -164,8 +157,8 @@ To run the C++ tests, run

```bash
cd $CUOPT_HOME/datasets && get_test_data.sh
cd $CUOPT_HOME/datasets/linear_programming && download_pdlp_test_dataset.sh
cd $CUOPT_HOME/datasets/mip && download_miplib_test_dataset.sh
cd $CUOPT_HOME && datasets/linear_programming/download_pdlp_test_dataset.sh
datasets/mip/download_miplib_test_dataset.sh
export RAPIDS_DATASET_ROOT_DIR=$CUOPT_HOME/datasets/
ctest --test-dir ${CUOPT_HOME}/cpp/build # libcuopt
```
Expand All @@ -176,8 +169,8 @@ To run python tests, run
```bash

cd $CUOPT_HOME/datasets && get_test_data.sh
cd $CUOPT_HOME/datasets/linear_programming && download_pdlp_test_dataset.sh
cd $CUOPT_HOME/datasets/mip && download_miplib_test_dataset.sh
cd $CUOPT_HOME && datasets/linear_programming/download_pdlp_test_dataset.sh
datasets/mip/download_miplib_test_dataset.sh
export RAPIDS_DATASET_ROOT_DIR=$CUOPT_HOME/datasets/
cd $CUOPT_HOME/python
pytest -v ${CUOPT_HOME}/python/cuopt/cuopt/tests
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cuOpt supports the following APIs:
- Linear Programming (LP)
- Mixed Integer Linear Programming (MILP)
- Routing (TSP, VRP, and PDP)

This repo is also hosted as a [COIN-OR](http://github.com/coin-or/cuopt/) project.

## Installation

Expand Down
2 changes: 1 addition & 1 deletion cpp/src/mip/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
ls.lp_optimal_exists = true;
if (lp_result.get_termination_status() == pdlp_termination_status_t::Optimal) {
// get lp user objective and pass it to set_new_user_bound
set_new_user_bound(problem_ptr->get_user_obj_from_solver_obj(lp_result.get_objective_value()));
set_new_user_bound(lp_result.get_objective_value());
} else if (lp_result.get_termination_status() == pdlp_termination_status_t::PrimalInfeasible) {
// PDLP's infeasibility detection isn't an exact method and might be subject to false positives.
// Issue a warning, and continue solving.
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/mip/presolve/bounds_presolve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ void bound_presolve_t<i_t, f_t>::calc_and_set_updated_constraint_bounds(problem_
min_act = make_span(upd.min_activity),
max_act = make_span(upd.max_activity),
cnst_lb = make_span(pb.constraint_lower_bounds),
cnst_ub = make_span(pb.constraint_upper_bounds)] __device__(i_t idx) -> i_t {
cnst_ub = make_span(pb.constraint_upper_bounds)] __device__(i_t idx) {
auto min_a = min_act[idx];
auto max_a = max_act[idx];
auto c_lb = cnst_lb[idx];
Expand Down
6 changes: 4 additions & 2 deletions cpp/src/mip/problem/problem.cu
Original file line number Diff line number Diff line change
Expand Up @@ -388,23 +388,25 @@ void problem_t<i_t, f_t>::check_problem_representation(bool check_transposed,
"Sizes for vectors related to the constraints are not the same.");

// Check the validity of bounds
cuopt_assert(
cuopt_expects(
thrust::all_of(handle_ptr->get_thrust_policy(),
thrust::make_counting_iterator<i_t>(0),
thrust::make_counting_iterator<i_t>(n_variables),
[variable_lower_bounds = variable_lower_bounds.data(),
variable_upper_bounds = variable_upper_bounds.data()] __device__(i_t idx) {
return variable_lower_bounds[idx] <= variable_upper_bounds[idx];
}),
error_type_t::ValidationError,
"Variable bounds are invalid");
cuopt_assert(
cuopt_expects(
thrust::all_of(handle_ptr->get_thrust_policy(),
thrust::make_counting_iterator<i_t>(0),
thrust::make_counting_iterator<i_t>(n_constraints),
[constraint_lower_bounds = constraint_lower_bounds.data(),
constraint_upper_bounds = constraint_upper_bounds.data()] __device__(i_t idx) {
return constraint_lower_bounds[idx] <= constraint_upper_bounds[idx];
}),
error_type_t::ValidationError,
"Constraints bounds are invalid");

if (check_mip_related_data) {
Expand Down
34 changes: 22 additions & 12 deletions cpp/tests/mip/termination_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@

namespace cuopt::linear_programming::test {

static std::pair<mip_termination_status_t, double> test_mps_file(std::string test_instance,
bool heuristics_only = true,
double time_limit = 10)
static std::tuple<mip_termination_status_t, double, double> test_mps_file(
std::string test_instance, bool heuristics_only = true, double time_limit = 10)
{
const raft::handle_t handle_{};

Expand All @@ -59,58 +58,69 @@ static std::pair<mip_termination_status_t, double> test_mps_file(std::string tes
settings.time_limit = time_limit;
settings.heuristics_only = heuristics_only;
mip_solution_t<int, double> solution = solve_mip(&handle_, problem, settings);
return std::make_pair(solution.get_termination_status(), solution.get_objective_value());
return std::make_tuple(solution.get_termination_status(),
solution.get_objective_value(),
solution.get_solution_bound());
}

TEST(termination_status, trivial_presolve_optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/trivial-presolve-optimality.mps");
auto [termination_status, obj_val, lb] = test_mps_file("mip/trivial-presolve-optimality.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, -1);
}

TEST(termination_status, presolve_optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/sudoku.mps");
auto [termination_status, obj_val, lb] = test_mps_file("mip/sudoku.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, 0);
}

TEST(termination_status, presolve_infeasible_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/presolve-infeasible.mps");
auto [termination_status, obj_val, lb] = test_mps_file("mip/presolve-infeasible.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible);
}

TEST(termination_status, feasible_found_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/gen-ip054.mps");
auto [termination_status, obj_val, lb] = test_mps_file("mip/gen-ip054.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::FeasibleFound);
}

TEST(termination_status, timeout_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps");
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps");
EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit);
}

TEST(termination_status, optimality_test)
{
auto [termination_status, obj_val] = test_mps_file("mip/bb_optimality.mps", false);
auto [termination_status, obj_val, lb] = test_mps_file("mip/bb_optimality.mps", false);
EXPECT_EQ(termination_status, mip_termination_status_t::Optimal);
EXPECT_EQ(obj_val, 2);
}

// Ensure the lower bound on maximization problems when BB times out has the right sign
TEST(termination_status, lower_bound_bb_timeout)
{
auto [termination_status, obj_val, lb] = test_mps_file("mip/cod105_max.mps", false, 0.5);
EXPECT_EQ(termination_status, mip_termination_status_t::FeasibleFound);
EXPECT_EQ(obj_val, 12);
EXPECT_GE(lb, obj_val);
}

TEST(termination_status, bb_infeasible_test)
{
// First, check that presolve doesn't reduce the problem to infeasibility
{
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", true, 1);
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps", true, 1);
EXPECT_EQ(termination_status, mip_termination_status_t::TimeLimit);
}
// Ensure that B&B proves the MIP infeasible
{
auto [termination_status, obj_val] = test_mps_file("mip/stein9inf.mps", false, 30);
auto [termination_status, obj_val, lb] = test_mps_file("mip/stein9inf.mps", false, 30);
EXPECT_EQ(termination_status, mip_termination_status_t::Infeasible);
}
}
Expand Down
5 changes: 2 additions & 3 deletions cpp/tests/mip/unit_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,11 @@ INSTANTIATE_TEST_SUITE_P(
MILPTests,
MILPTestParams,
testing::Values(
std::make_tuple(
true, true, true, cuopt::linear_programming::mip_termination_status_t::FeasibleFound),
std::make_tuple(true, true, true, cuopt::linear_programming::mip_termination_status_t::Optimal),
std::make_tuple(
false, true, false, cuopt::linear_programming::mip_termination_status_t::Optimal),
std::make_tuple(
true, false, true, cuopt::linear_programming::mip_termination_status_t::FeasibleFound),
true, false, true, cuopt::linear_programming::mip_termination_status_t::Optimal),
std::make_tuple(
false, false, false, cuopt::linear_programming::mip_termination_status_t::Optimal)));

Expand Down
Loading