Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add box constraint interface for dense backend #238

Merged
merged 17 commits into from
Jun 27, 2023
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
6 changes: 3 additions & 3 deletions .github/workflows/ci-linux-osx-win-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ jobs:
echo $(whereis ccache)
echo $(which ccache)
cd build
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
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

- name: Configure [Conda/Windows-2019]
if: contains(matrix.os, 'windows-2019')
Expand All @@ -122,7 +122,7 @@ jobs:
git submodule update --init
mkdir build
cd build
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
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

- name: Configure [Conda/Windows-latest]
if: contains(matrix.os, 'windows-latest')
Expand All @@ -132,7 +132,7 @@ jobs:
git submodule update --init
mkdir build
cd build
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
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

- name: Build [Conda]
shell: bash -l {0}
Expand Down
2 changes: 2 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
[submodule "external/cereal"]
path = external/cereal
url = https://github.com/USCiLab/cereal.git
[submodule "cmake"]
url = https://github.com/jrl-umi3218/jrl-cmakemodules.git
7 changes: 4 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2022 INRIA
# Copyright (c) 2022-2023 INRIA
#

cmake_minimum_required(VERSION 3.10)
Expand Down Expand Up @@ -48,7 +48,7 @@ project(${PROJECT_NAME} ${PROJECT_ARGS})

include(${CMAKE_CURRENT_LIST_DIR}/cmake-module/ide.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake-module/apple.cmake)
if(APPLE) # Use the handmade approach
if(APPLE OR WIN32) # Use the handmade approach
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/find-external/OpenMP
${CMAKE_MODULE_PATH})
elseif(UNIX)
Expand Down Expand Up @@ -78,7 +78,8 @@ option(BUILD_BINDINGS_WITH_AVX2_SUPPORT "Build the bindings with AVX2 support."
option(BUILD_BINDINGS_WITH_AVX512_SUPPORT
"Build the bindings with AVX512 support." ON)
option(TEST_JULIA_INTERFACE "Run the julia examples as unittest" OFF)
option(BUILD_WITH_OPENMP_SUPPORT "Build the library with the OpenMP support" ON)
option(BUILD_WITH_OPENMP_SUPPORT "Build the library with the OpenMP support"
OFF)
cmake_dependent_option(
LINK_PYTHON_INTERFACE_TO_OPENMP "Link OpenMP to the Python interface" ON
BUILD_WITH_OPENMP_SUPPORT OFF)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ We are ready to integrate **ProxSuite** within other optimization ecosystems.
**Proxsuite** is versatile, offering through a unified API advanced algorithms specialized for efficiently exploiting problem structures:

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

**Proxsuite** is flexible:

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

**Proxsuite** has a dedicated feature for solving batch of QPs.
**Proxsuite** is extensible.
**Proxsuite** is reliable and extensively tested, showing the best performances on the hardest problems of the literature.
**Proxsuite** is supported and tested on Windows, Mac OS X, Unix and Linux.
Expand Down
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ macro(proxsuite_benchmark bench_name)
endmacro(proxsuite_benchmark)

proxsuite_benchmark(timings-lp)
proxsuite_benchmark(timings-box-constraints)

if(BUILD_WITH_OPENMP_SUPPORT)
proxsuite_benchmark(timings-parallel)
Expand Down
127 changes: 127 additions & 0 deletions benchmark/timings-box-constraints.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// Copyright (c) 2023 INRIA
//
#include <iostream>
#include <proxsuite/proxqp/dense/dense.hpp>
#include <proxsuite/proxqp/utils/random_qp_problems.hpp>

using T = double;
using I = long long;

using namespace proxsuite;
using namespace proxsuite::proxqp;

int
main(int /*argc*/, const char** /*argv*/)
{
Timer<T> timer;
int smooth = 100;

T sparsity_factor = 0.75;
T eps_abs = T(1e-9);
T elapsed_time = 0.0;
proxqp::utils::rand::set_seed(1);
std::cout << "Dense QP" << std::endl;
for (proxqp::isize dim = 100; dim <= 1000; dim = dim + 100) {

proxqp::isize n_eq(dim / 2);
proxqp::isize n_in(dim / 2);
std::cout << "dim: " << dim << " n_eq: " << n_eq << " n_in: " << n_in
<< " box: " << dim << std::endl;
T strong_convexity_factor(1.e-2);

proxqp::dense::Model<T> qp_random = proxqp::utils::dense_strongly_convex_qp(
dim, n_eq, n_in, sparsity_factor, strong_convexity_factor);
Eigen::Matrix<T, Eigen::Dynamic, 1> x_sol =
utils::rand::vector_rand<T>(dim);
Eigen::Matrix<T, Eigen::Dynamic, 1> delta(n_in);
for (proxqp::isize i = 0; i < n_in; ++i) {
delta(i) = utils::rand::uniform_rand();
}
qp_random.u = qp_random.C * x_sol + delta;
qp_random.b = qp_random.A * x_sol;
Eigen::Matrix<T, Eigen::Dynamic, 1> u_box(dim);
u_box.setZero();
Eigen::Matrix<T, Eigen::Dynamic, 1> l_box(dim);
l_box.setZero();
for (proxqp::isize i = 0; i < dim; ++i) {
T shift = utils::rand::uniform_rand();
u_box(i) = x_sol(i) + shift;
l_box(i) = x_sol(i) - shift;
}
using Mat =
Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
Mat C_enlarged(dim + n_in, dim);
C_enlarged.setZero();
C_enlarged.topLeftCorner(n_in, dim) = qp_random.C;
C_enlarged.bottomLeftCorner(dim, dim).diagonal().array() += 1.;
Eigen::Matrix<T, Eigen::Dynamic, 1> u_enlarged(n_in + dim);
Eigen::Matrix<T, Eigen::Dynamic, 1> l_enlarged(n_in + dim);
u_enlarged.head(n_in) = qp_random.u;
u_enlarged.tail(dim) = u_box;
l_enlarged.head(n_in) = qp_random.l;
l_enlarged.tail(dim) = l_box;

elapsed_time = 0.0;
timer.stop();
proxqp::dense::QP<T> qp{ dim, n_eq, n_in, true };
qp.settings.eps_abs = eps_abs;
qp.settings.eps_rel = 0;
qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS;
for (int j = 0; j < smooth; j++) {
timer.start();
qp.init(qp_random.H,
qp_random.g,
qp_random.A,
qp_random.b,
qp_random.C,
qp_random.l,
qp_random.u,
l_box,
u_box);
qp.solve();
timer.stop();
elapsed_time += timer.elapsed().user;
if (qp.results.info.pri_res > eps_abs ||
qp.results.info.dua_res > eps_abs) {
std::cout << "dual residual " << qp.results.info.dua_res
<< "; primal residual " << qp.results.info.pri_res
<< std::endl;
std::cout << "total number of iteration: " << qp.results.info.iter
<< std::endl;
}
}
std::cout << "timings QP with box constraints feature : \t"
<< elapsed_time * 1e-3 / smooth << " ms" << std::endl;

elapsed_time = 0.0;
proxqp::dense::QP<T> qp_compare{ dim, n_eq, dim + n_in, false };
qp_compare.settings.eps_abs = eps_abs;
qp_compare.settings.eps_rel = 0;
qp_compare.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS;
for (int j = 0; j < smooth; j++) {
timer.start();
qp_compare.init(qp_random.H,
qp_random.g,
qp_random.A,
qp_random.b,
C_enlarged,
l_enlarged,
u_enlarged);
qp_compare.solve();
timer.stop();
elapsed_time += timer.elapsed().user;

if (qp_compare.results.info.pri_res > eps_abs ||
qp_compare.results.info.dua_res > eps_abs) {
std::cout << "dual residual " << qp_compare.results.info.dua_res
<< "; primal residual " << qp_compare.results.info.pri_res
<< std::endl;
std::cout << "total number of iteration: "
<< qp_compare.results.info.iter << std::endl;
}
}
std::cout << "timings QP without box constraints feature : \t"
<< elapsed_time * 1e-3 / smooth << " ms" << std::endl;
}
}
4 changes: 2 additions & 2 deletions benchmark/timings-lp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ main(int /*argc*/, const char** /*argv*/)
<< std::endl;
}
}
std::cout << "timings LP: \t" << elapsed_time * 1e-3 / smooth << "ms"
std::cout << "timings LP: \t" << elapsed_time * 1e-3 / smooth << " ms"
<< std::endl;

elapsed_time = 0.0;
Expand Down Expand Up @@ -101,7 +101,7 @@ main(int /*argc*/, const char** /*argv*/)
<< qp_compare.results.info.iter << std::endl;
}
}
std::cout << "timings QP: \t" << elapsed_time * 1e-3 / smooth << "ms"
std::cout << "timings QP: \t" << elapsed_time * 1e-3 / smooth << " ms"
<< std::endl;
}
}
20 changes: 10 additions & 10 deletions benchmark/timings-parallel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ main(int /*argc*/, const char** /*argv*/)
}
timer.stop();
std::cout << "time to generate and initialize std::vector of dense qps: \t"
<< timer.elapsed().user * 1e-3 / smooth << "ms" << std::endl;
<< timer.elapsed().user * 1e-3 / smooth << " ms" << std::endl;

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

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

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

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

const size_t NUM_THREADS = (size_t)omp_get_max_threads();

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

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

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

const size_t NUM_THREADS = (size_t)omp_get_max_threads();

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

std::cout << "\nparallel using std::vector of sparse QPs" << std::endl;
Expand All @@ -314,7 +314,7 @@ main(int /*argc*/, const char** /*argv*/)
timer.stop();
std::cout << "mean solve time in parallel (" << num_threads
<< " threads):\t" << timer.elapsed().user * 1e-3 / smooth
<< "ms" << std::endl;
<< " ms" << std::endl;
}
}
}
3 changes: 3 additions & 0 deletions bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ if(UNIX)
set(PYTHON_COMPONENTS Development.Module)
endif()
include(../../cmake-module/python.cmake)
include(../../cmake-module/python-helpers.cmake)

findpython(REQUIRED Development.Module)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/external/pybind11)
Expand Down Expand Up @@ -136,4 +137,6 @@ foreach(python ${PYTHON_FILES})
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/proxsuite/${python}"
DESTINATION ${${PYWRAP}_INSTALL_DIR})
endforeach(python)

python_build_get_target(compile_pyc)
add_dependencies(python ${compile_pyc})
2 changes: 1 addition & 1 deletion bindings/python/src/expose-parallel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h> // For binding STL containers

PYBIND11_MAKE_OPAQUE(std::vector<proxsuite::proxqp::dense::QP<double>>);
PYBIND11_MAKE_OPAQUE(std::vector<proxsuite::proxqp::dense::QP<double>>)

namespace proxsuite {
namespace proxqp {
Expand Down
Loading