Skip to content

Commit 0845a21

Browse files
authored
Add root node presolve using Papilo (#234)
This PR implements: - Adapters to presolve/postolve before/after running the solver - Fetching of Papilo headers through Cmake - System install of boost for pip wheel - Runtime boolean parameter to enable the feature (true by default for MIP, false for LP). Notes: - LP is off by default due to some presolvers not supporting dual post solve. These presolvers can be disabled but reduction quality is impacted. - Post-solve status can appear as fail due to absolute/relative tolerances. Papilo uses absolute tolerance during post solve checks. Advice is to set relative tol in cuOpt to a very small value. - TBB is disabled. To be enabled in a follow up PR for parallel presolve. Fixes #277 Credits to: @rg20 Authors: - Hugo Linsenmaier (https://github.com/hlinsen) - Rajesh Gandham (https://github.com/rg20) - Ramakrishnap (https://github.com/rgsl888prabhu) - Trevor McKay (https://github.com/tmckayus) Approvers: - Alice Boucher (https://github.com/aliceb-nv) - Ramakrishnap (https://github.com/rgsl888prabhu) - Robert Maynard (https://github.com/robertmaynard) URL: #234
1 parent 148c04c commit 0845a21

File tree

46 files changed

+1808
-151
lines changed

Some content is hidden

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

46 files changed

+1808
-151
lines changed

benchmarks/linear_programming/cuopt/run_mip.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ int main(int argc, char* argv[])
360360

361361
std::string out_dir;
362362
std::string result_file;
363-
int batch_num;
363+
int batch_num = -1;
364364

365365
bool heuristics_only = program.get<std::string>("--heuristics-only")[0] == 't';
366366
int num_cpu_threads = program.get<int>("--num-cpu-threads");
@@ -392,8 +392,7 @@ int main(int argc, char* argv[])
392392
for (int i = 0; i < n_gpus; ++i) {
393393
gpu_queue.push(i);
394394
}
395-
int tests_ran = 0;
396-
int n_instances_solved = 0;
395+
int tests_ran = 0;
397396
std::vector<std::string> paths;
398397
if (run_selected) {
399398
for (const auto& instance : instances) {
@@ -421,11 +420,11 @@ int main(int argc, char* argv[])
421420

422421
bool static_dispatch = false;
423422
if (static_dispatch) {
424-
for (int i = 0; i < paths.size(); ++i) {
423+
for (size_t i = 0; i < paths.size(); ++i) {
425424
// TODO implement
426425
}
427426
} else {
428-
for (int i = 0; i < paths.size(); ++i) {
427+
for (size_t i = 0; i < paths.size(); ++i) {
429428
task_queue.push(paths[i]);
430429
}
431430
while (!task_queue.empty()) {

benchmarks/linear_programming/run_mps_files.sh

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# --mip-heuristics-only : Run mip heuristics only
3232
# --write-log-file : Write log file
3333
# --num-cpu-threads : Number of CPU threads to use
34+
# --presolve : Enable presolve (default: true for MIP problems, false for LP problems)
3435
# --batch-num : Batch number. This allows to split the work across multiple batches uniformly when resources are limited.
3536
# --n-batches : Number of batches
3637
# --log-to-console : Log to console
@@ -74,6 +75,7 @@ Optional Arguments:
7475
--mip-heuristics-only Run mip heuristics only
7576
--write-log-file Write log file
7677
--num-cpu-threads Number of CPU threads to use
78+
--presolve Enable presolve (default: true for MIP problems, false for LP problems)
7779
--batch-num Batch number
7880
--n-batches Number of batches
7981
--log-to-console Log to console
@@ -107,47 +109,62 @@ fi
107109
while [[ $# -gt 0 ]]; do
108110
case $1 in
109111
--path)
112+
echo "MPS_DIR: $2"
110113
MPS_DIR="$2"
111114
shift 2
112115
;;
113116
--ngpus)
117+
echo "GPU_COUNT: $2"
114118
GPU_COUNT="$2"
115119
shift 2
116120
;;
117121
--time-limit)
122+
echo "TIME_LIMIT: $2"
118123
TIME_LIMIT="$2"
119124
shift 2
120125
;;
121126
--output-dir)
127+
echo "OUTPUT_DIR: $2"
122128
OUTPUT_DIR="$2"
123129
shift 2
124130
;;
125131
--relaxation)
126-
echo "Running relaxation"
132+
echo "RELAXATION: true"
127133
RELAXATION=true
128134
shift
129135
;;
130136
--mip-heuristics-only)
137+
echo "MIP_HEURISTICS_ONLY: true"
131138
MIP_HEURISTICS_ONLY=true
132139
shift
133140
;;
134141
--write-log-file)
135-
WRITE_LOG_FILE="$2"
136-
shift 2
142+
echo "WRITE_LOG_FILE: true"
143+
WRITE_LOG_FILE=true
144+
shift
137145
;;
138146
--num-cpu-threads)
147+
echo "NUM_CPU_THREADS: $2"
139148
NUM_CPU_THREADS="$2"
140149
shift 2
141150
;;
151+
--presolve)
152+
echo "PRESOLVE: $2"
153+
PRESOLVE="$2"
154+
shift 2
155+
;;
142156
--batch-num)
157+
echo "BATCH_NUM: $2"
143158
BATCH_NUM="$2"
144159
shift 2
145160
;;
146161
--n-batches)
162+
echo "N_BATCHES: $2"
147163
N_BATCHES="$2"
148164
shift 2
149165
;;
150166
--log-to-console)
167+
echo "LOG_TO_CONSOLE: $2"
151168
LOG_TO_CONSOLE="$2"
152169
shift 2
153170
;;
@@ -173,6 +190,7 @@ RELAXATION=${RELAXATION:-false}
173190
MIP_HEURISTICS_ONLY=${MIP_HEURISTICS_ONLY:-false}
174191
WRITE_LOG_FILE=${WRITE_LOG_FILE:-false}
175192
NUM_CPU_THREADS=${NUM_CPU_THREADS:-1}
193+
PRESOLVE=${PRESOLVE:-true}
176194
BATCH_NUM=${BATCH_NUM:-0}
177195
N_BATCHES=${N_BATCHES:-1}
178196
LOG_TO_CONSOLE=${LOG_TO_CONSOLE:-true}
@@ -261,9 +279,8 @@ worker() {
261279
if [ "$RELAXATION" = true ]; then
262280
args="$args --relaxation"
263281
fi
264-
if [ "$LOG_TO_CONSOLE" = true ]; then
265-
args="$args --log-to-console $LOG_TO_CONSOLE"
266-
fi
282+
args="$args --log-to-console $LOG_TO_CONSOLE"
283+
args="$args --presolve $PRESOLVE"
267284

268285
CUDA_VISIBLE_DEVICES=$gpu_id cuopt_cli "$mps_file" --time-limit $TIME_LIMIT $args
269286
done
@@ -277,4 +294,4 @@ done
277294
wait
278295

279296
# Remove the index file
280-
rm -f "$INDEX_FILE"
297+
rm -f "$INDEX_FILE"

ci/build_wheel_libcuopt.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ source rapids-init-pip
2121
package_name="libcuopt"
2222
package_dir="python/libcuopt"
2323

24+
# Install Boost
25+
bash ci/utils/install_boost.sh
26+
2427
export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
2528

2629
# For pull requests we are enabling assert mode.

ci/utils/install_boost.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
3+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
set -euo pipefail
19+
20+
# Install Boost
21+
if [ -f /etc/os-release ]; then
22+
. /etc/os-release
23+
if [[ "$ID" == "rocky" ]]; then
24+
echo "Detected Rocky Linux. Installing Boost via dnf..."
25+
dnf install -y boost-devel
26+
if [[ "$(uname -m)" == "x86_64" ]]; then
27+
dnf install -y gcc-toolset-14-libquadmath-devel
28+
fi
29+
elif [[ "$ID" == "ubuntu" ]]; then
30+
echo "Detected Ubuntu. Installing Boost via apt..."
31+
apt-get update
32+
apt-get install -y libboost-dev
33+
else
34+
echo "Unknown OS: $ID. Please install Boost development libraries manually."
35+
exit 1
36+
fi
37+
else
38+
echo "/etc/os-release not found. Cannot determine OS. Please install Boost development libraries manually."
39+
exit 1
40+
fi

conda/environments/all_cuda-128_arch-aarch64.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ channels:
55
- rapidsai-nightly
66
- conda-forge
77
dependencies:
8+
- boost
89
- breathe
910
- c-compiler
1011
- ccache

conda/environments/all_cuda-128_arch-x86_64.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ channels:
55
- rapidsai-nightly
66
- conda-forge
77
dependencies:
8+
- boost
89
- breathe
910
- c-compiler
1011
- ccache

conda/recipes/libcuopt/recipe.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ cache:
4949

5050
requirements:
5151
build:
52+
- boost
5253
- ${{ compiler("c") }}
5354
- ${{ compiler("cxx") }}
5455
- ${{ compiler("cuda") }} =${{ cuda_version }}
@@ -68,6 +69,7 @@ cache:
6869
- libcurand-dev
6970
- libcusparse-dev
7071
- cuda-cudart-dev
72+
- boost
7173

7274
outputs:
7375
- package:
@@ -126,6 +128,7 @@ outputs:
126128
- ${{ stdlib("c") }}
127129
host:
128130
- ${{ pin_subpackage("libmps-parser", exact=True) }}
131+
- boost
129132
- cuda-version =${{ cuda_version }}
130133
- rapids-logger =0.1
131134
- librmm =${{ dep_minor_version }}
@@ -135,6 +138,7 @@ outputs:
135138
run:
136139
- ${{ pin_compatible("cuda-version", upper_bound="x", lower_bound="x") }}
137140
- ${{ pin_subpackage("libmps-parser", exact=True) }}
141+
- boost
138142
- librmm =${{ dep_minor_version }}
139143
- cuda-nvrtc
140144
ignore_run_exports:
@@ -146,6 +150,8 @@ outputs:
146150
- libcurand
147151
- libcusparse
148152
- librmm
153+
- libboost
154+
- libboost_iostreams
149155
tests:
150156
- package_contents:
151157
files:
@@ -173,6 +179,7 @@ outputs:
173179
host:
174180
- ${{ pin_subpackage("libcuopt", exact=True) }}
175181
- ${{ pin_subpackage("libmps-parser", exact=True) }}
182+
- boost
176183
- gmock ${{ gtest_version }}
177184
- gtest ${{ gtest_version }}
178185
- cuda-cudart-dev
@@ -192,6 +199,8 @@ outputs:
192199
- libcurand
193200
- libcusparse
194201
- librmm
202+
- libboost
203+
- libboost_iostreams
195204
about:
196205
homepage: ${{ load_from_file("python/cuopt/pyproject.toml").project.urls.Homepage }}
197206
license: ${{ load_from_file("python/cuopt/pyproject.toml").project.license.text }}

cpp/CMakeLists.txt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR)
16+
cmake_minimum_required(VERSION 3.30.4 FATAL_ERROR)
17+
18+
# Add our custom Find modules to the module path
19+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/thirdparty")
1720

1821
include(GNUInstallDirs)
1922

@@ -157,6 +160,21 @@ else()
157160
find_package(RMM REQUIRED)
158161
find_package(RAFT REQUIRED)
159162
endif()
163+
164+
FetchContent_Declare(
165+
papilo
166+
GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
167+
GIT_TAG "v2.4.3"
168+
SYSTEM
169+
)
170+
171+
set(BUILD_TESTING OFF CACHE BOOL "Disable test build for papilo")
172+
set(TBB OFF CACHE BOOL "Disable TBB")
173+
set(PAPILO_NO_BINARIES ON)
174+
option(LUSOL "Disable LUSOL" OFF)
175+
176+
FetchContent_MakeAvailable(papilo)
177+
160178
include(${rapids-cmake-dir}/cpm/rapids_logger.cmake)
161179
# generate logging macros
162180
rapids_cpm_rapids_logger(BUILD_EXPORT_SET cuopt-exports INSTALL_EXPORT_SET cuopt-exports)
@@ -204,6 +222,9 @@ target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld")
204222
add_library(cuopt::cuopt ALIAS cuopt)
205223
# ##################################################################################################
206224
# - include paths ---------------------------------------------------------------------------------
225+
226+
target_include_directories(cuopt SYSTEM PRIVATE "${papilo_SOURCE_DIR}/src" "${papilo_BINARY_DIR}")
227+
207228
target_include_directories(cuopt
208229
PRIVATE
209230
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty"
@@ -212,6 +233,7 @@ target_include_directories(cuopt
212233
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
213234
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
214235
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libmps_parser/include>"
236+
INTERFACE
215237
"$<INSTALL_INTERFACE:include>"
216238
)
217239

@@ -242,6 +264,7 @@ target_link_libraries(cuopt
242264
${CUOPT_PRIVATE_CUDA_LIBS}
243265
)
244266

267+
245268
# ##################################################################################################
246269
# - generate tests --------------------------------------------------------------------------------
247270
if(BUILD_TESTS)
@@ -357,6 +380,8 @@ target_link_libraries(cuopt_cli
357380
PUBLIC
358381
cuopt
359382
OpenMP::OpenMP_CXX
383+
PRIVATE
384+
papilo-core
360385
)
361386
set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH "$ORIGIN/../${lib_dir}")
362387

@@ -377,6 +402,8 @@ if(BUILD_BENCHMARKS)
377402
PUBLIC
378403
cuopt
379404
OpenMP::OpenMP_CXX
405+
PRIVATE
406+
papilo-core
380407
)
381408
endif()
382409

cpp/cuopt_cli.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,6 @@ int run_single_file(const std::string& file_path,
122122
(op_problem.get_problem_category() == cuopt::linear_programming::problem_category_t::MIP ||
123123
op_problem.get_problem_category() == cuopt::linear_programming::problem_category_t::IP);
124124

125-
bool sol_found = false;
126-
double obj_val = std::numeric_limits<double>::infinity();
127-
128125
auto initial_solution =
129126
initial_solution_file.empty()
130127
? std::vector<double>()
@@ -197,6 +194,11 @@ int main(int argc, char* argv[])
197194
.default_value(false)
198195
.implicit_value(true);
199196

197+
program.add_argument("--presolve")
198+
.help("enable/disable presolve (default: true for MIP problems, false for LP problems)")
199+
.default_value(true)
200+
.implicit_value(true);
201+
200202
std::map<std::string, std::string> arg_name_to_param_name;
201203
{
202204
// Add all solver settings as arguments

cpp/include/cuopt/linear_programming/constants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@
5050
#define CUOPT_LOG_FILE "log_file"
5151
#define CUOPT_LOG_TO_CONSOLE "log_to_console"
5252
#define CUOPT_CROSSOVER "crossover"
53+
#define CUOPT_PRESOLVE "presolve"
5354
#define CUOPT_MIP_ABSOLUTE_TOLERANCE "mip_absolute_tolerance"
5455
#define CUOPT_MIP_RELATIVE_TOLERANCE "mip_relative_tolerance"
5556
#define CUOPT_MIP_INTEGRALITY_TOLERANCE "mip_integrality_tolerance"
5657
#define CUOPT_MIP_ABSOLUTE_GAP "mip_absolute_gap"
5758
#define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap"
5859
#define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only"
5960
#define CUOPT_MIP_SCALING "mip_scaling"
61+
#define CUOPT_MIP_PRESOLVE "mip_presolve"
6062
#define CUOPT_SOLUTION_FILE "solution_file"
6163
#define CUOPT_NUM_CPU_THREADS "num_cpu_threads"
6264
#define CUOPT_USER_PROBLEM_FILE "user_problem_file"

0 commit comments

Comments
 (0)