Skip to content

Commit 1c2215d

Browse files
authored
Merge pull request #238 from Bambade/box_constraint
Add box constraint interface for dense backend
2 parents 12eea35 + dc5b650 commit 1c2215d

40 files changed

+2551
-387
lines changed

.github/workflows/ci-linux-osx-win-conda.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
echo $(whereis ccache)
113113
echo $(which ccache)
114114
cd build
115-
cmake .. -GNinja -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCHECK_RUNTIME_MALLOC:BOOL=ON -DCMAKE_CXX_STANDARD=${{ matrix.cxx_std }} -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_EXECUTABLE=$(which python3) -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON -DTEST_JULIA_INTERFACE:BOOL=ON -DOpenMP_ROOT=$CONDA_PREFIX
115+
cmake .. -GNinja -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCHECK_RUNTIME_MALLOC:BOOL=ON -DCMAKE_CXX_STANDARD=${{ matrix.cxx_std }} -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_EXECUTABLE=$(which python3) -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON -DTEST_JULIA_INTERFACE:BOOL=ON -DBUILD_WITH_OPENMP_SUPPORT:BOOL=ON -DOpenMP_ROOT=$CONDA_PREFIX
116116
117117
- name: Configure [Conda/Windows-2019]
118118
if: contains(matrix.os, 'windows-2019')
@@ -122,7 +122,7 @@ jobs:
122122
git submodule update --init
123123
mkdir build
124124
cd build
125-
cmake .. -G"Visual Studio 16 2019" -T "ClangCl" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX}/Library -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_SITELIB=${CONDA_PREFIX}/Lib/site-packages -DPYTHON_EXECUTABLE=${CONDA_PREFIX}/python.exe -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON
125+
cmake .. -G"Visual Studio 16 2019" -T "ClangCl" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX}/Library -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_SITELIB=${CONDA_PREFIX}/Lib/site-packages -DPYTHON_EXECUTABLE=${CONDA_PREFIX}/python.exe -DOpenMP_ROOT=$CONDA_PREFIX -DBUILD_WITH_OPENMP_SUPPORT:BOOL=OFF -DLINK_PYTHON_INTERFACE_TO_OPENMP:BOOL=ON -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON
126126
127127
- name: Configure [Conda/Windows-latest]
128128
if: contains(matrix.os, 'windows-latest')
@@ -132,7 +132,7 @@ jobs:
132132
git submodule update --init
133133
mkdir build
134134
cd build
135-
cmake .. -G"Visual Studio 17 2022" -T "v143" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX}/Library -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_SITELIB=${CONDA_PREFIX}/Lib/site-packages -DPYTHON_EXECUTABLE=${CONDA_PREFIX}/python.exe -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON
135+
cmake .. -G"Visual Studio 17 2022" -T "v143" -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX}/Library -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_PYTHON_INTERFACE:BOOL=ON -DPYTHON_SITELIB=${CONDA_PREFIX}/Lib/site-packages -DPYTHON_EXECUTABLE=${CONDA_PREFIX}/python.exe -DOpenMP_ROOT=$CONDA_PREFIX -DBUILD_WITH_OPENMP_SUPPORT:BOOL=ON -DLINK_PYTHON_INTERFACE_TO_OPENMP:BOOL=ON -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON
136136
137137
- name: Build [Conda]
138138
shell: bash -l {0}

.gitmodules

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
[submodule "external/cereal"]
88
path = external/cereal
99
url = https://github.com/USCiLab/cereal.git
10+
[submodule "cmake"]
11+
url = https://github.com/jrl-umi3218/jrl-cmakemodules.git

CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2022 INRIA
2+
# Copyright (c) 2022-2023 INRIA
33
#
44

55
cmake_minimum_required(VERSION 3.10)
@@ -48,7 +48,7 @@ project(${PROJECT_NAME} ${PROJECT_ARGS})
4848

4949
include(${CMAKE_CURRENT_LIST_DIR}/cmake-module/ide.cmake)
5050
include(${CMAKE_CURRENT_LIST_DIR}/cmake-module/apple.cmake)
51-
if(APPLE) # Use the handmade approach
51+
if(APPLE OR WIN32) # Use the handmade approach
5252
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/find-external/OpenMP
5353
${CMAKE_MODULE_PATH})
5454
elseif(UNIX)
@@ -78,7 +78,8 @@ option(BUILD_BINDINGS_WITH_AVX2_SUPPORT "Build the bindings with AVX2 support."
7878
option(BUILD_BINDINGS_WITH_AVX512_SUPPORT
7979
"Build the bindings with AVX512 support." ON)
8080
option(TEST_JULIA_INTERFACE "Run the julia examples as unittest" OFF)
81-
option(BUILD_WITH_OPENMP_SUPPORT "Build the library with the OpenMP support" ON)
81+
option(BUILD_WITH_OPENMP_SUPPORT "Build the library with the OpenMP support"
82+
OFF)
8283
cmake_dependent_option(
8384
LINK_PYTHON_INTERFACE_TO_OPENMP "Link OpenMP to the Python interface" ON
8485
BUILD_WITH_OPENMP_SUPPORT OFF)

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ We are ready to integrate **ProxSuite** within other optimization ecosystems.
3232
**Proxsuite** is versatile, offering through a unified API advanced algorithms specialized for efficiently exploiting problem structures:
3333

3434
- dense, sparse and matrix-free matrix factorization backends,
35-
- advanced warm-starting options (e.g., equality-constrained initial guess, warm-start or cold-start options from previous results).
35+
- advanced warm-starting options (e.g., equality-constrained initial guess, warm-start or cold-start options from previous results),
36+
- dedicated features for handling more efficiently box constraints, or linear programs.
3637

3738
**Proxsuite** is flexible:
3839

3940
- header only,
4041
- C++ 14/17/20 compliant,
4142
- Python and Julia bindings for easy code prototyping without sacrificing performance.
4243

44+
**Proxsuite** has a dedicated feature for solving batch of QPs.
4345
**Proxsuite** is extensible.
4446
**Proxsuite** is reliable and extensively tested, showing the best performances on the hardest problems of the literature.
4547
**Proxsuite** is supported and tested on Windows, Mac OS X, Unix and Linux.

benchmark/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ macro(proxsuite_benchmark bench_name)
1616
endmacro(proxsuite_benchmark)
1717

1818
proxsuite_benchmark(timings-lp)
19+
proxsuite_benchmark(timings-box-constraints)
1920

2021
if(BUILD_WITH_OPENMP_SUPPORT)
2122
proxsuite_benchmark(timings-parallel)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//
2+
// Copyright (c) 2023 INRIA
3+
//
4+
#include <iostream>
5+
#include <proxsuite/proxqp/dense/dense.hpp>
6+
#include <proxsuite/proxqp/utils/random_qp_problems.hpp>
7+
8+
using T = double;
9+
using I = long long;
10+
11+
using namespace proxsuite;
12+
using namespace proxsuite::proxqp;
13+
14+
int
15+
main(int /*argc*/, const char** /*argv*/)
16+
{
17+
Timer<T> timer;
18+
int smooth = 100;
19+
20+
T sparsity_factor = 0.75;
21+
T eps_abs = T(1e-9);
22+
T elapsed_time = 0.0;
23+
proxqp::utils::rand::set_seed(1);
24+
std::cout << "Dense QP" << std::endl;
25+
for (proxqp::isize dim = 100; dim <= 1000; dim = dim + 100) {
26+
27+
proxqp::isize n_eq(dim / 2);
28+
proxqp::isize n_in(dim / 2);
29+
std::cout << "dim: " << dim << " n_eq: " << n_eq << " n_in: " << n_in
30+
<< " box: " << dim << std::endl;
31+
T strong_convexity_factor(1.e-2);
32+
33+
proxqp::dense::Model<T> qp_random = proxqp::utils::dense_strongly_convex_qp(
34+
dim, n_eq, n_in, sparsity_factor, strong_convexity_factor);
35+
Eigen::Matrix<T, Eigen::Dynamic, 1> x_sol =
36+
utils::rand::vector_rand<T>(dim);
37+
Eigen::Matrix<T, Eigen::Dynamic, 1> delta(n_in);
38+
for (proxqp::isize i = 0; i < n_in; ++i) {
39+
delta(i) = utils::rand::uniform_rand();
40+
}
41+
qp_random.u = qp_random.C * x_sol + delta;
42+
qp_random.b = qp_random.A * x_sol;
43+
Eigen::Matrix<T, Eigen::Dynamic, 1> u_box(dim);
44+
u_box.setZero();
45+
Eigen::Matrix<T, Eigen::Dynamic, 1> l_box(dim);
46+
l_box.setZero();
47+
for (proxqp::isize i = 0; i < dim; ++i) {
48+
T shift = utils::rand::uniform_rand();
49+
u_box(i) = x_sol(i) + shift;
50+
l_box(i) = x_sol(i) - shift;
51+
}
52+
using Mat =
53+
Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
54+
Mat C_enlarged(dim + n_in, dim);
55+
C_enlarged.setZero();
56+
C_enlarged.topLeftCorner(n_in, dim) = qp_random.C;
57+
C_enlarged.bottomLeftCorner(dim, dim).diagonal().array() += 1.;
58+
Eigen::Matrix<T, Eigen::Dynamic, 1> u_enlarged(n_in + dim);
59+
Eigen::Matrix<T, Eigen::Dynamic, 1> l_enlarged(n_in + dim);
60+
u_enlarged.head(n_in) = qp_random.u;
61+
u_enlarged.tail(dim) = u_box;
62+
l_enlarged.head(n_in) = qp_random.l;
63+
l_enlarged.tail(dim) = l_box;
64+
65+
elapsed_time = 0.0;
66+
timer.stop();
67+
proxqp::dense::QP<T> qp{ dim, n_eq, n_in, true };
68+
qp.settings.eps_abs = eps_abs;
69+
qp.settings.eps_rel = 0;
70+
qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS;
71+
for (int j = 0; j < smooth; j++) {
72+
timer.start();
73+
qp.init(qp_random.H,
74+
qp_random.g,
75+
qp_random.A,
76+
qp_random.b,
77+
qp_random.C,
78+
qp_random.l,
79+
qp_random.u,
80+
l_box,
81+
u_box);
82+
qp.solve();
83+
timer.stop();
84+
elapsed_time += timer.elapsed().user;
85+
if (qp.results.info.pri_res > eps_abs ||
86+
qp.results.info.dua_res > eps_abs) {
87+
std::cout << "dual residual " << qp.results.info.dua_res
88+
<< "; primal residual " << qp.results.info.pri_res
89+
<< std::endl;
90+
std::cout << "total number of iteration: " << qp.results.info.iter
91+
<< std::endl;
92+
}
93+
}
94+
std::cout << "timings QP with box constraints feature : \t"
95+
<< elapsed_time * 1e-3 / smooth << " ms" << std::endl;
96+
97+
elapsed_time = 0.0;
98+
proxqp::dense::QP<T> qp_compare{ dim, n_eq, dim + n_in, false };
99+
qp_compare.settings.eps_abs = eps_abs;
100+
qp_compare.settings.eps_rel = 0;
101+
qp_compare.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS;
102+
for (int j = 0; j < smooth; j++) {
103+
timer.start();
104+
qp_compare.init(qp_random.H,
105+
qp_random.g,
106+
qp_random.A,
107+
qp_random.b,
108+
C_enlarged,
109+
l_enlarged,
110+
u_enlarged);
111+
qp_compare.solve();
112+
timer.stop();
113+
elapsed_time += timer.elapsed().user;
114+
115+
if (qp_compare.results.info.pri_res > eps_abs ||
116+
qp_compare.results.info.dua_res > eps_abs) {
117+
std::cout << "dual residual " << qp_compare.results.info.dua_res
118+
<< "; primal residual " << qp_compare.results.info.pri_res
119+
<< std::endl;
120+
std::cout << "total number of iteration: "
121+
<< qp_compare.results.info.iter << std::endl;
122+
}
123+
}
124+
std::cout << "timings QP without box constraints feature : \t"
125+
<< elapsed_time * 1e-3 / smooth << " ms" << std::endl;
126+
}
127+
}

benchmark/timings-lp.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ main(int /*argc*/, const char** /*argv*/)
7171
<< std::endl;
7272
}
7373
}
74-
std::cout << "timings LP: \t" << elapsed_time * 1e-3 / smooth << "ms"
74+
std::cout << "timings LP: \t" << elapsed_time * 1e-3 / smooth << " ms"
7575
<< std::endl;
7676

7777
elapsed_time = 0.0;
@@ -101,7 +101,7 @@ main(int /*argc*/, const char** /*argv*/)
101101
<< qp_compare.results.info.iter << std::endl;
102102
}
103103
}
104-
std::cout << "timings QP: \t" << elapsed_time * 1e-3 / smooth << "ms"
104+
std::cout << "timings QP: \t" << elapsed_time * 1e-3 / smooth << " ms"
105105
<< std::endl;
106106
}
107107
}

benchmark/timings-parallel.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ main(int /*argc*/, const char** /*argv*/)
6464
}
6565
timer.stop();
6666
std::cout << "time to generate and initialize std::vector of dense qps: \t"
67-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
67+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
6868

6969
timer.start();
7070
for (int j = 0; j < smooth; j++) {
@@ -90,7 +90,7 @@ main(int /*argc*/, const char** /*argv*/)
9090
}
9191
timer.stop();
9292
std::cout << "time to generate and initialize dense:BatchQP: \t\t\t"
93-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
93+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
9494

9595
for (int j = 0; j < smooth; j++) {
9696
std::vector<proxqp::sparse::QP<T, I>> qps;
@@ -118,7 +118,7 @@ main(int /*argc*/, const char** /*argv*/)
118118
}
119119
timer.stop();
120120
std::cout << "time to generate and initialize std::vector of sparse qps: \t"
121-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
121+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
122122

123123
timer.start();
124124
for (int j = 0; j < smooth; j++) {
@@ -145,7 +145,7 @@ main(int /*argc*/, const char** /*argv*/)
145145
}
146146
timer.stop();
147147
std::cout << "time to generate and initialize sparse:BatchQP: \t\t\t"
148-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
148+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
149149
}
150150

151151
{
@@ -203,7 +203,7 @@ main(int /*argc*/, const char** /*argv*/)
203203
}
204204
timer.stop();
205205
std::cout << "mean solve time in serial: \t\t\t"
206-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
206+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
207207

208208
const size_t NUM_THREADS = (size_t)omp_get_max_threads();
209209

@@ -216,7 +216,7 @@ main(int /*argc*/, const char** /*argv*/)
216216
timer.stop();
217217
std::cout << "mean solve time in parallel (" << num_threads
218218
<< " threads):\t" << timer.elapsed().user * 1e-3 / smooth
219-
<< "ms" << std::endl;
219+
<< " ms" << std::endl;
220220
}
221221

222222
std::cout << "\nparallel using std::vector of dense QPs" << std::endl;
@@ -228,7 +228,7 @@ main(int /*argc*/, const char** /*argv*/)
228228
timer.stop();
229229
std::cout << "mean solve time in parallel (" << num_threads
230230
<< " threads):\t" << timer.elapsed().user * 1e-3 / smooth
231-
<< "ms" << std::endl;
231+
<< " ms" << std::endl;
232232
}
233233
}
234234

@@ -289,7 +289,7 @@ main(int /*argc*/, const char** /*argv*/)
289289
}
290290
timer.stop();
291291
std::cout << "mean solve time in serial: \t\t\t"
292-
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
292+
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;
293293

294294
const size_t NUM_THREADS = (size_t)omp_get_max_threads();
295295

@@ -302,7 +302,7 @@ main(int /*argc*/, const char** /*argv*/)
302302
timer.stop();
303303
std::cout << "mean solve time in parallel (" << num_threads
304304
<< " threads):\t" << timer.elapsed().user * 1e-3 / smooth
305-
<< "ms" << std::endl;
305+
<< " ms" << std::endl;
306306
}
307307

308308
std::cout << "\nparallel using std::vector of sparse QPs" << std::endl;
@@ -314,7 +314,7 @@ main(int /*argc*/, const char** /*argv*/)
314314
timer.stop();
315315
std::cout << "mean solve time in parallel (" << num_threads
316316
<< " threads):\t" << timer.elapsed().user * 1e-3 / smooth
317-
<< "ms" << std::endl;
317+
<< " ms" << std::endl;
318318
}
319319
}
320320
}

bindings/python/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ if(UNIX)
22
set(PYTHON_COMPONENTS Development.Module)
33
endif()
44
include(../../cmake-module/python.cmake)
5+
include(../../cmake-module/python-helpers.cmake)
56

67
findpython(REQUIRED Development.Module)
78
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/external/pybind11)
@@ -136,4 +137,6 @@ foreach(python ${PYTHON_FILES})
136137
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/proxsuite/${python}"
137138
DESTINATION ${${PYWRAP}_INSTALL_DIR})
138139
endforeach(python)
140+
141+
python_build_get_target(compile_pyc)
139142
add_dependencies(python ${compile_pyc})

bindings/python/src/expose-parallel.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include <pybind11/stl.h>
1111
#include <pybind11/stl_bind.h> // For binding STL containers
1212

13-
PYBIND11_MAKE_OPAQUE(std::vector<proxsuite::proxqp::dense::QP<double>>);
13+
PYBIND11_MAKE_OPAQUE(std::vector<proxsuite::proxqp::dense::QP<double>>)
1414

1515
namespace proxsuite {
1616
namespace proxqp {

0 commit comments

Comments
 (0)