From 1783e03321c6fbd896c8fdf4e8760f4ac93411fd Mon Sep 17 00:00:00 2001 From: Bambade Date: Sat, 24 Jun 2023 17:28:09 +0200 Subject: [PATCH 01/17] add box constraint interface for dense backend --- README.md | 4 +- benchmark/CMakeLists.txt | 1 + benchmark/timings-box-constraints.cpp | 127 +++++ bindings/python/src/expose-parallel.hpp | 2 +- bindings/python/src/expose-qpobject.hpp | 92 +++- bindings/python/src/expose-solve.hpp | 86 +++ doc/2-PROXQP_API/2-ProxQP_api.md | 37 ++ doc/3-ProxQP_solve.md | 2 +- examples/cpp/init_dense_qp_with_box.cpp | 42 ++ .../cpp/loading_dense_qp_with_box_ineq.cpp | 29 + examples/cpp/solve_dense_qp.cpp | 25 + examples/cpp/solve_without_api.cpp | 41 ++ examples/cpp/update_dense_qp.cpp | 22 + examples/python/init_dense_qp_with_box.py | 44 ++ .../python/loading_dense_qp_with_box_ineq.py | 23 + examples/python/solve_dense_qp.py | 12 + examples/python/solve_without_api.py | 17 + examples/python/update_dense_qp.py | 10 + include/proxsuite/proxqp/dense/helpers.hpp | 75 ++- include/proxsuite/proxqp/dense/linesearch.hpp | 110 ++-- include/proxsuite/proxqp/dense/model.hpp | 24 +- .../proxqp/dense/preconditioner/ruiz.hpp | 281 ++++++---- include/proxsuite/proxqp/dense/solver.hpp | 326 ++++++++--- include/proxsuite/proxqp/dense/utils.hpp | 138 ++++- include/proxsuite/proxqp/dense/views.hpp | 13 +- include/proxsuite/proxqp/dense/workspace.hpp | 166 ++++-- include/proxsuite/proxqp/dense/wrapper.hpp | 512 +++++++++++++++++- include/proxsuite/proxqp/results.hpp | 14 +- test/src/dense_qp_eq.cpp | 3 - test/src/dense_qp_wrapper.cpp | 352 +++++++++++- test/src/dense_qp_wrapper.py | 218 ++++++++ test/src/sparse_ruiz_equilibration.cpp | 28 +- 32 files changed, 2515 insertions(+), 361 deletions(-) create mode 100644 benchmark/timings-box-constraints.cpp create mode 100644 examples/cpp/init_dense_qp_with_box.cpp create mode 100644 examples/cpp/loading_dense_qp_with_box_ineq.cpp create mode 100644 examples/python/init_dense_qp_with_box.py create mode 100644 examples/python/loading_dense_qp_with_box_ineq.py diff --git a/README.md b/README.md index e90ada191..a44b5761f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ 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: @@ -40,6 +41,7 @@ We are ready to integrate **ProxSuite** within other optimization ecosystems. - 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. diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 5240472ca..4cc052e4d 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -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) diff --git a/benchmark/timings-box-constraints.cpp b/benchmark/timings-box-constraints.cpp new file mode 100644 index 000000000..29e1b2c00 --- /dev/null +++ b/benchmark/timings-box-constraints.cpp @@ -0,0 +1,127 @@ +// +// Copyright (c) 2023 INRIA +// +#include +#include +#include + +using T = double; +using I = long long; + +using namespace proxsuite; +using namespace proxsuite::proxqp; + +int +main(int /*argc*/, const char** /*argv*/) +{ + Timer 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 qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + Eigen::Matrix x_sol = + utils::rand::vector_rand(dim); + Eigen::Matrix 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 u_box(dim); + u_box.setZero(); + Eigen::Matrix 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; + 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 u_enlarged(n_in + dim); + Eigen::Matrix 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 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 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; + } +} diff --git a/bindings/python/src/expose-parallel.hpp b/bindings/python/src/expose-parallel.hpp index 79c899930..4b5696093 100644 --- a/bindings/python/src/expose-parallel.hpp +++ b/bindings/python/src/expose-parallel.hpp @@ -10,7 +10,7 @@ #include #include // For binding STL containers -PYBIND11_MAKE_OPAQUE(std::vector>); +PYBIND11_MAKE_OPAQUE(std::vector>) namespace proxsuite { namespace proxqp { diff --git a/bindings/python/src/expose-qpobject.hpp b/bindings/python/src/expose-qpobject.hpp index 5996f14e8..583768a38 100644 --- a/bindings/python/src/expose-qpobject.hpp +++ b/bindings/python/src/expose-qpobject.hpp @@ -26,11 +26,15 @@ exposeQpObjectDense(pybind11::module_ m) { ::pybind11::class_>(m, "QP") - .def(::pybind11::init(), - pybind11::arg_v("n", 0, "primal dimension."), - pybind11::arg_v("n_eq", 0, "number of equality constraints."), - pybind11::arg_v("n_in", 0, "number of inequality constraints."), - "Default constructor using QP model dimensions.") // constructor + .def( + ::pybind11::init(), + pybind11::arg_v("n", 0, "primal dimension."), + pybind11::arg_v("n_eq", 0, "number of equality constraints."), + pybind11::arg_v("n_in", 0, "number of inequality constraints."), + pybind11::arg_v("box_constraints", + false, + "specify or not a QP with box inequality constraints."), + "Default constructor using QP model dimensions.") // constructor .def_readwrite( "results", &dense::QP::results, @@ -41,7 +45,38 @@ exposeQpObjectDense(pybind11::module_ m) "settings", &dense::QP::settings, "Settings of the solver.") .def_readwrite( "model", &dense::QP::model, "class containing the QP model") - + .def("is_box_constrained", + &dense::QP::is_box_constrained, + "precise whether or not the QP is designed with box constraints.") + .def("init", + static_cast::*)(optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + bool compute_preconditioner, + optional, + optional, + optional)>(&dense::QP::init), + "function for initialize the QP model.", + pybind11::arg_v("H", nullopt, "quadratic cost"), + pybind11::arg_v("g", nullopt, "linear cost"), + pybind11::arg_v("A", nullopt, "equality constraint matrix"), + pybind11::arg_v("b", nullopt, "equality constraint vector"), + pybind11::arg_v("C", nullopt, "inequality constraint matrix"), + pybind11::arg_v("l", nullopt, "upper inequality constraint vector"), + pybind11::arg_v("u", nullopt, "lower inequality constraint vector"), + pybind11::arg_v("compute_preconditioner", + true, + "execute the preconditioner for reducing " + "ill-conditioning and speeding up solver execution."), + pybind11::arg_v("rho", nullopt, "primal proximal parameter"), + pybind11::arg_v( + "mu_eq", nullopt, "dual equality constraint proximal parameter"), + pybind11::arg_v( + "mu_in", nullopt, "dual inequality constraint proximal parameter")) .def("init", static_cast::*)(optional>, optional>, @@ -50,6 +85,8 @@ exposeQpObjectDense(pybind11::module_ m) optional>, optional>, optional>, + optional>, + optional>, bool compute_preconditioner, optional, optional, @@ -62,6 +99,10 @@ exposeQpObjectDense(pybind11::module_ m) pybind11::arg_v("C", nullopt, "inequality constraint matrix"), pybind11::arg_v("l", nullopt, "upper inequality constraint vector"), pybind11::arg_v("u", nullopt, "lower inequality constraint vector"), + pybind11::arg_v( + "l_box", nullopt, "upper box inequality constraint vector"), + pybind11::arg_v( + "u_box", nullopt, "lower box inequality constraint vector"), pybind11::arg_v("compute_preconditioner", true, "execute the preconditioner for reducing " @@ -114,6 +155,45 @@ exposeQpObjectDense(pybind11::module_ m) "mu_eq", nullopt, "dual equality constraint proximal parameter"), pybind11::arg_v( "mu_in", nullopt, "dual inequality constraint proximal parameter")) + .def( + "update", + static_cast::*)(optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + bool update_preconditioner, + optional, + optional, + optional)>(&dense::QP::update), + "function used for updating matrix or vector entry of the model using " + "dense matrix entries.", + pybind11::arg_v("H", nullopt, "quadratic cost"), + pybind11::arg_v("g", nullopt, "linear cost"), + pybind11::arg_v("A", nullopt, "equality constraint matrix"), + pybind11::arg_v("b", nullopt, "equality constraint vector"), + pybind11::arg_v("C", nullopt, "inequality constraint matrix"), + pybind11::arg_v("l", nullopt, "upper inequality constraint vector"), + pybind11::arg_v("u", nullopt, "lower inequality constraint vector"), + pybind11::arg_v( + "l_box", nullopt, "upper box inequality constraint vector"), + pybind11::arg_v( + "u_box", nullopt, "lower box inequality constraint vector"), + pybind11::arg_v( + "update_preconditioner", + true, + "update the preconditioner considering new matrices entries for " + "reducing ill-conditioning and speeding up solver execution. If set up " + "to false, use previous derived preconditioner."), + pybind11::arg_v("rho", nullopt, "primal proximal parameter"), + pybind11::arg_v( + "mu_eq", nullopt, "dual equality constraint proximal parameter"), + pybind11::arg_v( + "mu_in", nullopt, "dual inequality constraint proximal parameter")) .def("cleanup", &dense::QP::cleanup, "function used for cleaning the workspace and result " diff --git a/bindings/python/src/expose-solve.hpp b/bindings/python/src/expose-solve.hpp index 3aef7d767..0d4c1cf7c 100644 --- a/bindings/python/src/expose-solve.hpp +++ b/bindings/python/src/expose-solve.hpp @@ -99,6 +99,92 @@ solveDenseQp(pybind11::module_ m) nullopt, "relative accuracy threshold used for the duality-gap " "stopping criterion.")); + + m.def( + "solve", + pybind11::overload_cast>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional>, + optional, + optional, + optional, + optional, + optional, + optional, + bool, + bool, + optional, + proxsuite::proxqp::InitialGuessStatus, + bool, + optional, + optional>(&dense::solve), + "Function for solving a QP problem using PROXQP sparse backend directly " + "without defining a QP object. It is possible to set up some of the solver " + "parameters (warm start, initial guess option, proximal step sizes, " + "absolute and relative accuracies, maximum number of iterations, " + "preconditioner execution).", + pybind11::arg_v("H", nullopt, "quadratic cost with dense format."), + pybind11::arg_v("g", nullopt, "linear cost"), + pybind11::arg_v( + "A", nullopt, "equality constraint matrix with dense format."), + pybind11::arg_v("b", nullopt, "equality constraint vector"), + pybind11::arg_v( + "C", nullopt, "inequality constraint matrix with dense format."), + pybind11::arg_v("l", nullopt, "lower inequality constraint vector"), + pybind11::arg_v("u", nullopt, "upper inequality constraint vector"), + pybind11::arg_v("l_box", nullopt, "lower box inequality constraint vector"), + pybind11::arg_v("u_box", nullopt, "upper box inequality constraint vector"), + pybind11::arg_v("x", nullopt, "primal warm start"), + pybind11::arg_v("y", nullopt, "dual equality warm start"), + pybind11::arg_v("z", nullopt, "dual inequality warm start"), + pybind11::arg_v( + "eps_abs", + nullopt, + "absolute accuracy level used for the solver stopping criterion."), + pybind11::arg_v("eps_rel", + nullopt, + "relative accuracy level used for the solver stopping " + "criterion. Deactivated in standard settings."), + pybind11::arg_v("rho", nullopt, "primal proximal parameter"), + pybind11::arg_v( + "mu_eq", nullopt, "dual equality constraint proximal parameter"), + pybind11::arg_v( + "mu_in", nullopt, "dual inequality constraint proximal parameter"), + pybind11::arg_v("verbose", + nullopt, + "verbose option to print information at each iteration."), + pybind11::arg_v("compute_preconditioner", + true, + "executes the default preconditioner for reducing ill " + "conditioning and speeding up the solver."), + pybind11::arg_v("compute_timings", false, "compute solver's timings."), + pybind11::arg_v("max_iter", nullopt, "maximum number of iteration."), + pybind11::arg_v( + "initial_guess", + proxsuite::proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS, + "maximum number of iteration."), + pybind11::arg_v( + "check_duality_gap", + false, + "if set to true, include the duality gap in absolute and relative " + "stopping criteria."), + pybind11::arg_v("eps_duality_gap_abs", + nullopt, + "absolute accuracy threshold used for the duality-gap " + "stopping criterion."), + pybind11::arg_v("eps_duality_gap_rel", + nullopt, + "relative accuracy threshold used for the duality-gap " + "stopping criterion.")); } } // namespace python diff --git a/doc/2-PROXQP_API/2-ProxQP_api.md b/doc/2-PROXQP_API/2-ProxQP_api.md index 470d1dfa8..6725c20b2 100644 --- a/doc/2-PROXQP_API/2-ProxQP_api.md +++ b/doc/2-PROXQP_API/2-ProxQP_api.md @@ -109,6 +109,24 @@ For loading ProxQP with dense backend it is as simple as the following code belo The dimensions of the problem (i.e., n is the dimension of primal variable x, n_eq the number of equality constraints, and n_in the number of inequality constraints) are used for allocating the space needed for the Qp object. The dense Qp object is templated by the floatting precision of the QP model (in the example above in C++ a double precision). Note that for model to be valid, the primal dimension (i.e., n) must be strictly positive. If it is not the case an assertion will be raised precising this issue. +The dense backend has also a specific feature for handling more efficiently box inequality constraints. To benefit from it, constructors are overloaded as follows + + + + + + + + + + +
examples/cpp/loading_dense_qp_with_box_constraints.cppexamples/python/loading_dense_qp_with_box_constraints.py
+ \include loading_dense_qp_with_box_constraints.cpp + + \include loading_dense_qp_with_box_constraints.py +
+ + For loading ProxQP with sparse backend they are two possibilities: * one can use as before the dimensions of the QP problem (i.e., n, n_eq and n_in) * or one can use the sparsity structure of the matrices defining the QP problem. More precisely, if H designs the quadratic cost of the model, A the equality constraint matrix, and C the inequality constraint matrix, one can pass in entry a boolean mask of these matrices (i.e., matrices with true value when one entry is non zero) for initializing the Qp object. @@ -149,6 +167,23 @@ Once you have defined a Qp object, the init method enables you setting up the QP +If you use the specific feature of the dense backend for handling box constraints, the init method uses simply as follows + + + + + + + + + + +
examples/cpp/init_dense_qp_with_box.cppexamples/python/init_dense_qp_with_box.py
+ \include init_dense_qp_with_box.cpp + + \include init_dense_qp_with_box.py +
+ Note that with its dense backend, ProxQP solver manipulates matrices in dense representations (in the same spirit, the solver with sparse backend manipulates entries in sparse format). In the example above the matrices are originally in sparse format, and eventually converted into dense format. Note that if some elements of your QP model are not defined (for example a QP without linear cost or inequality constraints), you can either pass a None argument, or a matrix with zero shape for specifying it. We provide an example below in cpp and python (for the dense case, it is similar with sparse backend). @@ -426,6 +461,8 @@ $$\begin{equation}\label{eq:approx_qp_sol_rel} \end{equation}$$ accordingly with the parameters eps_abs and eps_rel chosen by the user. +Note that if you use the dense backend and its specific feature for handling box inequality constraints, then the first $$n_in$$ elements of z correspond to multipliers associated to the linear inequality formed with $$C$$ matrix, whereas the last $$d$$ elements correspond to multipliers associated to the box inequality constraints (see for example solve_dense_qp.cpp or solve_dense_qp.py). + \subsection OverviewInfoClass The info subclass In this table you have on the three columns from left to right: the name of the info subclass item, its default value and then a short description of it. diff --git a/doc/3-ProxQP_solve.md b/doc/3-ProxQP_solve.md index 8e527c0e7..09410de8e 100644 --- a/doc/3-ProxQP_solve.md +++ b/doc/3-ProxQP_solve.md @@ -77,7 +77,7 @@ where $[z]_+$ and $[z]_-$ stand for the projection of z onto the positive and ne If if you don't want to pass through [ProxQP API](2-ProxQP_api.md), it is also possible to use one single solve function. We will show how to do so with examples. -You just need to call a "solve" function with in entry the model of the convex QP you want to solve. We show you below examples in C++ and python for ProxQP sparse and dense backends. Note that the sparse and dense solvers take respectivaly entries in sparse and dense formats. +You just need to call a "solve" function with in entry the model of the convex QP you want to solve. We show you below examples in C++ and python for ProxQP sparse and dense backends. Note that the sparse and dense solvers take respectivaly entries in sparse and dense formats. Note finally that the dense backend benefits from a feature enabling it to handle more efficiently box inequality constraints. We provide an example below as well of how using it (you can find more details about it in [ProxQP API](2-ProxQP_api.md)).
diff --git a/examples/cpp/init_dense_qp_with_box.cpp b/examples/cpp/init_dense_qp_with_box.cpp new file mode 100644 index 000000000..1649899a1 --- /dev/null +++ b/examples/cpp/init_dense_qp_with_box.cpp @@ -0,0 +1,42 @@ +#include // load the dense solver backend +#include // used for generating a random convex qp + +using namespace proxsuite::proxqp; +using T = double; + +int +main() +{ + isize dim = 10; + isize n_eq(dim / 4); + isize n_in(dim / 4); + // generate a random qp + T sparsity_factor(0.15); + T strong_convexity_factor(1.e-2); + dense::Model qp_random = utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + // specify some trivial box constraints + dense::Vec u_box(dim); + dense::Vec l_box(dim); + u_box.setZero(); + l_box.setZero(); + u_box.array() += 1.E10; + l_box.array() -= 1.E10; + + dense::QP qp(dim, n_eq, n_in, true); // create the QP object + // and specify with true you take into account box constraints + // in the model + // you can check that there are constraints with method is_box_constrained + std::cout << "the qp is box constrained : " << qp.is_box_constrained() + << std::endl; + // init the model + 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); // initialize the model +} diff --git a/examples/cpp/loading_dense_qp_with_box_ineq.cpp b/examples/cpp/loading_dense_qp_with_box_ineq.cpp new file mode 100644 index 000000000..6a266f839 --- /dev/null +++ b/examples/cpp/loading_dense_qp_with_box_ineq.cpp @@ -0,0 +1,29 @@ +#include "proxsuite/proxqp/dense/dense.hpp" + +using namespace proxsuite::proxqp; +using T = double; +int +main() +{ + dense::isize dim = 10; + dense::isize n_eq(dim / 4); + dense::isize n_in(dim / 4); + // we load here a QP model with + // n_eq equality constraints + // n_in generic type of inequality constraints + // and dim box inequality constraints + dense::QP qp(dim, n_eq, n_in, true); + // true specifies we take into accounts box constraints + // n_in are any other type of inequality constraints + + // Another example + + // we load here a QP model with + // n_eq equality constraints + // O generic type of inequality constraints + // and dim box inequality constraints + dense::QP qp2(dim, n_eq, 0, true); + // true specifies we take into accounts box constraints + // we don't need to precise n_in = dim, it is taken + // into account internally +} diff --git a/examples/cpp/solve_dense_qp.cpp b/examples/cpp/solve_dense_qp.cpp index 1c80cebb5..74a61f53d 100644 --- a/examples/cpp/solve_dense_qp.cpp +++ b/examples/cpp/solve_dense_qp.cpp @@ -35,4 +35,29 @@ main() std::cout << "optimal x: " << qp.results.x << std::endl; std::cout << "optimal y: " << qp.results.y << std::endl; std::cout << "optimal z: " << qp.results.z << std::endl; + + // Another example if you have box constraints (for the dense backend only for + // the moment) + dense::QP qp2(dim, n_eq, n_in, true); // create the QP object + // some trivial boxes + dense::Vec u_box(dim); + dense::Vec l_box(dim); + u_box.setZero(); + l_box.setZero(); + u_box.array() += 1.E10; + l_box.array() -= 1.E10; + qp2.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); // initialize the model with the boxes + qp2.solve(); // solve the problem + // An important note regarding the inequality multipliers + // auto z_ineq = qp.results.z.head(n_in); contains the multiplier associated + // to qp_random.C z_box = qp.results.z.tail(dim); the last dim elements + // correspond to multiplier associated to the box constraints } diff --git a/examples/cpp/solve_without_api.cpp b/examples/cpp/solve_without_api.cpp index 2351b0682..09d5aa0c3 100644 --- a/examples/cpp/solve_without_api.cpp +++ b/examples/cpp/solve_without_api.cpp @@ -50,4 +50,45 @@ main() << std::endl; std::cout << "optimal z from dense solver: " << results_dense_solver.z << std::endl; + + // Solve the problem using the dense backend using its feature for handling + // box constraints + + // some trivial boxes + Vec u_box(n); + Vec l_box(n); + u_box.setZero(); + l_box.setZero(); + u_box.array() += 1.E10; + l_box.array() -= 1.E10; + // make sure to specify at least next 9 variables of the solve function after + // u_box to make sure the overloading work and use the specific feature for + // handling more efficiently box constraints + Results results_dense_solver_box = dense::solve(H_dense, + g, + A_dense, + b, + C_dense, + l, + u, + l_box, + u_box, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt); + // print an optimal solution x,y and z + std::cout << "optimal x from dense solver: " << results_dense_solver.x + << std::endl; + std::cout << "optimal y from dense solver: " << results_dense_solver.y + << std::endl; + std::cout << "optimal z from dense solver: " << results_dense_solver.z + << std::endl; + // Note that the last n elements of z corresponds to the multipliers + // associated to the box constraints } diff --git a/examples/cpp/update_dense_qp.cpp b/examples/cpp/update_dense_qp.cpp index bc62c7cc1..e2f3ba5f1 100644 --- a/examples/cpp/update_dense_qp.cpp +++ b/examples/cpp/update_dense_qp.cpp @@ -36,4 +36,26 @@ main() std::cout << "optimal x: " << qp.results.x << std::endl; std::cout << "optimal y: " << qp.results.y << std::endl; std::cout << "optimal z: " << qp.results.z << std::endl; + // if you have boxes (dense backend only) you proceed the same way + dense::QP qp_box(dim, n_eq, n_in, true); // create the QP object + dense::Vec u_box(dim); + dense::Vec l_box(dim); + u_box.setZero(); + l_box.setZero(); + u_box.array() += 1.E10; + l_box.array() -= 1.E10; + qp_box.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_box.solve(); + u_box.array() += 1.E1; + l_box.array() -= 1.E1; + qp_box.update(qp2.H, qp2.g, qp2.A, qp2.b, qp2.C, qp2.l, qp2.u, l_box, u_box); + qp_box.solve(); } diff --git a/examples/python/init_dense_qp_with_box.py b/examples/python/init_dense_qp_with_box.py new file mode 100644 index 000000000..268e96e8b --- /dev/null +++ b/examples/python/init_dense_qp_with_box.py @@ -0,0 +1,44 @@ +import proxsuite +import numpy as np +import scipy.sparse as spa + + +def generate_mixed_qp(n, seed=1): + # A function for generating random convex qps + + np.random.seed(seed) + n_eq = int(n / 4) + n_in = int(n / 4) + m = n_eq + n_in + + P = spa.random( + n, n, density=0.075, data_rvs=np.random.randn, format="csc" + ).toarray() + P = (P + P.T) / 2.0 + + s = max(np.absolute(np.linalg.eigvals(P))) + P += (abs(s) + 1e-02) * spa.eye(n) + P = spa.coo_matrix(P) + q = np.random.randn(n) + A = spa.random(m, n, density=0.15, data_rvs=np.random.randn, format="csc").toarray() + v = np.random.randn(n) # Fictitious solution + delta = np.random.rand(m) # To get inequality + u = A @ v + l = -1.0e20 * np.ones(m) + + return P.toarray(), q, A[:n_eq, :], u[:n_eq], A[n_in:, :], u[n_in:], l[n_in:] + + +# load a qp object using qp problem dimensions +n = 10 +n_eq = 2 +n_in = 2 +qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) +# you can check that there are constraints with method is_box_constrained +print(f"the qp is box constrained: {qp.is_box_constrained()}") +# generate a random QP +H, g, A, b, C, u, l = generate_mixed_qp(n) +l_box = -np.ones(n) * 1.0e10 +u_box = np.ones(n) * 1.0e10 +# initialize the model of the problem to solve +qp.init(H, g, A, b, C, l, u, l_box, u_box) diff --git a/examples/python/loading_dense_qp_with_box_ineq.py b/examples/python/loading_dense_qp_with_box_ineq.py new file mode 100644 index 000000000..cf6af51c8 --- /dev/null +++ b/examples/python/loading_dense_qp_with_box_ineq.py @@ -0,0 +1,23 @@ +import proxsuite + +n = 10 +n_eq = 2 +n_in = 2 +# we load here a QP model with +# n_eq equality constraints +# n_in generic type of inequality constraints +# and dim box inequality constraints +qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) +# true specifies we take into accounts box constraints +# n_in are any other type of inequality constraints + +# Another example + +# we load here a QP model with +# n_eq equality constraints +# O generic type of inequality constraints +# and dim box inequality constraints +qp2 = proxsuite.proxqp.dense.QP(n, n_eq, 0, True) +# true specifies we take into accounts box constraints +# we don't need to precise n_in = dim, it is taken +# into account internally diff --git a/examples/python/solve_dense_qp.py b/examples/python/solve_dense_qp.py index 3d8b2382d..4eb04547c 100644 --- a/examples/python/solve_dense_qp.py +++ b/examples/python/solve_dense_qp.py @@ -46,3 +46,15 @@ def generate_mixed_qp(n, seed=1): print("optimal x: {}".format(qp.results.x)) print("optimal y: {}".format(qp.results.y)) print("optimal z: {}".format(qp.results.z)) +# Another example if you have box constraints (for the dense backend only for the moment) +qp2 = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) +l_box = -np.ones(n) * 1.0e10 +u_box = np.ones(n) * 1.0e10 +qp2.init(H, g, A, b, C, l, u, l_box, u_box) +qp2.solve() +# An important note regarding the inequality multipliers +z_ineq = qp.results.z[:n_in] # contains the multiplier associated to qp_random.C +z_box = qp.results.z[ + n_in: +] # the last dim elements correspond to multiplier associated to +# the box constraints diff --git a/examples/python/solve_without_api.py b/examples/python/solve_without_api.py index 7fd61eb58..9e84676fc 100644 --- a/examples/python/solve_without_api.py +++ b/examples/python/solve_without_api.py @@ -50,3 +50,20 @@ def generate_mixed_qp(n, seed=1): print("optimal x: {}".format(results.x)) print("optimal y: {}".format(results.y)) print("optimal z: {}".format(results.z)) + +# solve the problem using the dense backend using its feature for handling box constraints + +l_box = -np.ones(n) * 1.0e10 +u_box = np.ones(n) * 1.0e10 + +# make sure to specify l_box=l_box, u_box=u_box in order to make work the +# overloading +results_dense_solver_box = proxsuite.proxqp.dense.solve( + H.toarray(), g, A.toarray(), b, C.toarray(), l, u, l_box=l_box, u_box=u_box +) +# print an optimal solution +print("optimal x: {}".format(results_dense_solver_box.x)) +print("optimal y: {}".format(results_dense_solver_box.y)) +print("optimal z: {}".format(results_dense_solver_box.z)) +# Note that the last n elements of z corresponds to the multipliers associated to the box +# constraints diff --git a/examples/python/update_dense_qp.py b/examples/python/update_dense_qp.py index 790e294e1..106d8f544 100644 --- a/examples/python/update_dense_qp.py +++ b/examples/python/update_dense_qp.py @@ -49,3 +49,13 @@ def generate_mixed_qp(n, seed=1): print("optimal x: {}".format(qp.results.x)) print("optimal y: {}".format(qp.results.y)) print("optimal z: {}".format(qp.results.z)) +# if you have boxes (dense backend only) you proceed the same way +qp2 = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) +l_box = -np.ones(n) * 1.0e10 +u_box = np.ones(n) * 1.0e10 +qp2.init(H, g, A, b, C, l, u, l_box, u_box) +qp2.solve() +l_box += 1.0e1 +u_box -= 1.0e1 +qp2.update(H_new, g_new, A_new, b_new, C_new, l_new, u_new, l_box, u_box) +qp2.solve() diff --git a/include/proxsuite/proxqp/dense/helpers.hpp b/include/proxsuite/proxqp/dense/helpers.hpp index d194c3962..e5fc233e7 100644 --- a/include/proxsuite/proxqp/dense/helpers.hpp +++ b/include/proxsuite/proxqp/dense/helpers.hpp @@ -35,6 +35,7 @@ void compute_equality_constrained_initial_guess(Workspace& qpwork, const Settings& qpsettings, const Model& qpmodel, + const isize n_constraints, Results& qpresults) { @@ -46,6 +47,7 @@ compute_equality_constrained_initial_guess(Workspace& qpwork, qpmodel, qpresults, qpwork, + n_constraints, T(1), qpmodel.dim + qpmodel.n_eq); @@ -112,15 +114,17 @@ template void setup_equilibration(Workspace& qpwork, const Settings& qpsettings, + const bool box_constraints, preconditioner::RuizEquilibration& ruiz, bool execute_preconditioner) { QpViewBoxMut qp_scaled{ - { from_eigen, qpwork.H_scaled }, { from_eigen, qpwork.g_scaled }, - { from_eigen, qpwork.A_scaled }, { from_eigen, qpwork.b_scaled }, - { from_eigen, qpwork.C_scaled }, { from_eigen, qpwork.u_scaled }, - { from_eigen, qpwork.l_scaled } + { from_eigen, qpwork.H_scaled }, { from_eigen, qpwork.g_scaled }, + { from_eigen, qpwork.A_scaled }, { from_eigen, qpwork.b_scaled }, + { from_eigen, qpwork.C_scaled }, { from_eigen, qpwork.u_scaled }, + { from_eigen, qpwork.l_scaled }, { from_eigen, qpwork.i_scaled }, + { from_eigen, qpwork.l_box_scaled }, { from_eigen, qpwork.u_box_scaled }, }; proxsuite::linalg::veg::dynstack::DynStackMut stack{ @@ -132,6 +136,7 @@ setup_equilibration(Workspace& qpwork, qpsettings.preconditioner_max_iter, qpsettings.preconditioner_accuracy, qpsettings.problem_type, + box_constraints, stack); qpwork.correction_guess_rhs_g = infty_norm(qpwork.g_scaled); } @@ -186,8 +191,11 @@ update(optional> H, optional> C, optional> l, optional> u, + optional> l_box, + optional> u_box, Model& model, - Workspace& work) + Workspace& work, + const bool box_constraints) { // check the model is valid if (g != nullopt) { @@ -258,6 +266,15 @@ update(optional> H, if (l != nullopt) { model.l = l.value().eval(); } + if (u_box != nullopt && box_constraints) { + model.u_box = u_box.value(); + } // else qpmodel.u_box remains initialized to a matrix with zero elements or + // zero shape + + if (l_box != nullopt && box_constraints) { + model.l_box = l_box.value(); + } // else qpmodel.l_box remains initialized to a matrix with zero elements or + // zero shape if (H != nullopt || A != nullopt || C != nullopt) { work.refactorize = true; @@ -272,7 +289,7 @@ update(optional> H, if (C != nullopt) { model.C = C.value(); } - assert(model.is_valid()); + assert(model.is_valid(box_constraints)); } /*! * Setups the QP solver model. @@ -303,10 +320,13 @@ setup( // optional> C, optional> l, optional> u, + optional> l_box, + optional> u_box, Settings& qpsettings, Model& qpmodel, Workspace& qpwork, Results& qpresults, + const bool box_constraints, preconditioner::RuizEquilibration& ruiz, PreconditionerStatus preconditioner_status) { @@ -318,7 +338,7 @@ setup( // } else { qpresults.cleanup(qpsettings); } - qpwork.cleanup(); + qpwork.cleanup(box_constraints); break; } case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { @@ -328,7 +348,7 @@ setup( // } else { qpresults.cold_start(qpsettings); } - qpwork.cleanup(); + qpwork.cleanup(box_constraints); break; } case InitialGuessStatus::NO_INITIAL_GUESS: { @@ -337,7 +357,7 @@ setup( // } else { qpresults.cleanup(qpsettings); } - qpwork.cleanup(); + qpwork.cleanup(box_constraints); break; } case InitialGuessStatus::WARM_START: { @@ -348,13 +368,14 @@ setup( // } else { qpresults.cleanup(qpsettings); } - qpwork.cleanup(); + qpwork.cleanup(box_constraints); break; } case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { if (qpwork.refactorize || qpwork.proximal_parameter_update) { - qpwork.cleanup(); // meaningful for when there is an upate of the model - // and one wants to warm start with previous result + qpwork.cleanup(box_constraints); // meaningful for when there is an + // upate of the model and one wants to + // warm start with previous result qpwork.refactorize = true; } qpresults.cleanup_statistics(); @@ -392,7 +413,16 @@ setup( // qpmodel.l = l.value(); } // else qpmodel.l remains initialized to a matrix with zero elements or zero // shape - assert(qpmodel.is_valid()); + if (u_box != nullopt) { + qpmodel.u_box = u_box.value(); + } // else qpmodel.u_box remains initialized to a matrix with zero elements or + // zero shape + + if (l_box != nullopt) { + qpmodel.l_box = l_box.value(); + } // else qpmodel.l_box remains initialized to a matrix with zero elements or + // zero shape + assert(qpmodel.is_valid(box_constraints)); switch (qpsettings.problem_type) { case ProblemType::LP: break; @@ -414,19 +444,30 @@ setup( // .select(qpmodel.l, Eigen::Matrix::Zero(qpmodel.n_in).array() - T(1.E20)); + if (box_constraints) { + qpwork.u_box_scaled = + (qpmodel.u_box.array() <= T(1.E20)) + .select(qpmodel.u_box, + Eigen::Matrix::Zero(qpmodel.dim).array() + + T(1.E20)); + qpwork.l_box_scaled = + (qpmodel.l_box.array() >= T(-1.E20)) + .select(qpmodel.l_box, + Eigen::Matrix::Zero(qpmodel.dim).array() - + T(1.E20)); + } qpwork.dual_feasibility_rhs_2 = infty_norm(qpmodel.g); - switch (preconditioner_status) { case PreconditionerStatus::EXECUTE: - setup_equilibration(qpwork, qpsettings, ruiz, true); + setup_equilibration(qpwork, qpsettings, box_constraints, ruiz, true); break; case PreconditionerStatus::IDENTITY: - setup_equilibration(qpwork, qpsettings, ruiz, false); + setup_equilibration(qpwork, qpsettings, box_constraints, ruiz, false); break; case PreconditionerStatus::KEEP: // keep previous one - setup_equilibration(qpwork, qpsettings, ruiz, false); + setup_equilibration(qpwork, qpsettings, box_constraints, ruiz, false); break; } } diff --git a/include/proxsuite/proxqp/dense/linesearch.hpp b/include/proxsuite/proxqp/dense/linesearch.hpp index f7b7faa85..1e5a74969 100644 --- a/include/proxsuite/proxqp/dense/linesearch.hpp +++ b/include/proxsuite/proxqp/dense/linesearch.hpp @@ -11,7 +11,6 @@ #include "proxsuite/proxqp/dense/workspace.hpp" #include "proxsuite/proxqp/settings.hpp" #include - namespace proxsuite { namespace proxqp { namespace dense { @@ -53,6 +52,7 @@ gpdal_derivative_results(const Model& qpmodel, Results& qpresults, Workspace& qpwork, const Settings& qpsettings, + isize n_constraints, T alpha) -> PrimalDualDerivativeResult { @@ -120,33 +120,33 @@ gpdal_derivative_results(const Model& qpmodel, // (Adx-dy*mu_eq).dot(res_eq-y*mu_eq) // derive Cdx_act - qpwork.err.tail(qpmodel.n_in) = + qpwork.err.tail(n_constraints) = ((qpwork.primal_residual_in_scaled_up_plus_alphaCdx.array() > T(0.)) || (qpwork.primal_residual_in_scaled_low_plus_alphaCdx.array() < T(0.))) .select(qpwork.Cdx, - Eigen::Matrix::Zero(qpmodel.n_in)); + Eigen::Matrix::Zero(n_constraints)); - a += qpresults.info.mu_in_inv * qpwork.err.tail(qpmodel.n_in).squaredNorm() / + a += qpresults.info.mu_in_inv * qpwork.err.tail(n_constraints).squaredNorm() / qpsettings.alpha_gpdal; // contains now: a = dx.dot(H.dot(dx)) + rho * // norm(dx)**2 + (mu_eq_inv) * norm(Adx)**2 + nu*mu_eq_inv * // norm(Adx-dy*mu_eq)**2 + // norm(dw_act)**2 / (mu_in * (alpha_gpdal)) a += qpresults.info.mu_in * (1. - qpsettings.alpha_gpdal) * - qpwork.dw_aug.tail(qpmodel.n_in).squaredNorm(); + qpwork.dw_aug.tail(n_constraints).squaredNorm(); // add norm(z)**2 * mu_in * (1-alpha) // derive vector [w-u]_+ + [w-l]-- qpwork.active_part_z = (qpwork.primal_residual_in_scaled_up_plus_alphaCdx.array() > T(0.)) .select(qpwork.primal_residual_in_scaled_up, - Eigen::Matrix::Zero(qpmodel.n_in)) + + Eigen::Matrix::Zero(n_constraints)) + (qpwork.primal_residual_in_scaled_low_plus_alphaCdx.array() < T(0.)) .select(qpwork.primal_residual_in_scaled_low, - Eigen::Matrix::Zero(qpmodel.n_in)); + Eigen::Matrix::Zero(n_constraints)); b += qpresults.info.mu_in_inv * - qpwork.active_part_z.dot(qpwork.err.tail(qpmodel.n_in)) / + qpwork.active_part_z.dot(qpwork.err.tail(n_constraints)) / qpsettings.alpha_gpdal; // contains now: b = dx.dot(H.dot(x) + rho*(x-xe) + // g) + mu_eq_inv * Adx.dot(res_eq) + nu*mu_eq_inv * // (Adx-dy*mu_eq).dot(res_eq-y*mu_eq) + mu_in @@ -158,7 +158,7 @@ gpdal_derivative_results(const Model& qpmodel, // * Cdx_act.dot([Cx-u+ze*mu_in]_+ + [Cx-l+ze*mu_in]--) + nu*mu_in_inv // (Cdx_act-dz*mu_in).dot([Cx-u+ze*mu_in]_+ + [Cx-l+ze*mu_in]-- - z*mu_in) b += qpresults.info.mu_in * (1. - qpsettings.alpha_gpdal) * - qpwork.dw_aug.tail(qpmodel.n_in).dot(qpresults.z); + qpwork.dw_aug.tail(n_constraints).dot(qpresults.z); return { a, @@ -181,6 +181,7 @@ auto primal_dual_derivative_results(const Model& qpmodel, Results& qpresults, Workspace& qpwork, + isize n_constraints, T alpha) -> PrimalDualDerivativeResult { @@ -248,14 +249,14 @@ primal_dual_derivative_results(const Model& qpmodel, // (Adx-dy*mu_eq).dot(res_eq-y*mu_eq) // derive Cdx_act - qpwork.err.tail(qpmodel.n_in) = + qpwork.err.tail(n_constraints) = ((qpwork.primal_residual_in_scaled_up_plus_alphaCdx.array() > T(0.)) || (qpwork.primal_residual_in_scaled_low_plus_alphaCdx.array() < T(0.))) .select(qpwork.Cdx, - Eigen::Matrix::Zero(qpmodel.n_in)); + Eigen::Matrix::Zero(n_constraints)); a += qpresults.info.mu_in_inv * - qpwork.err.tail(qpmodel.n_in) + qpwork.err.tail(n_constraints) .squaredNorm(); // contains now: a = dx.dot(H.dot(dx)) + rho * // norm(dx)**2 + (mu_eq_inv) * norm(Adx)**2 + nu*mu_eq_inv * // norm(Adx-dy*mu_eq)**2 + mu_in * @@ -265,21 +266,21 @@ primal_dual_derivative_results(const Model& qpmodel, qpwork.active_part_z = (qpwork.primal_residual_in_scaled_up_plus_alphaCdx.array() > T(0.)) .select(qpwork.primal_residual_in_scaled_up, - Eigen::Matrix::Zero(qpmodel.n_in)) + + Eigen::Matrix::Zero(n_constraints)) + (qpwork.primal_residual_in_scaled_low_plus_alphaCdx.array() < T(0.)) .select(qpwork.primal_residual_in_scaled_low, - Eigen::Matrix::Zero(qpmodel.n_in)); + Eigen::Matrix::Zero(n_constraints)); b += qpresults.info.mu_in_inv * qpwork.active_part_z.dot(qpwork.err.tail( - qpmodel.n_in)); // contains now: b = dx.dot(H.dot(x) + rho*(x-xe) + + n_constraints)); // contains now: b = dx.dot(H.dot(x) + rho*(x-xe) + // g) + mu_eq_inv * Adx.dot(res_eq) + nu*mu_eq_inv * // (Adx-dy*mu_eq).dot(res_eq-y*mu_eq) + mu_in // * Cdx_act.dot([Cx-u+ze/mu]_+ + [Cx-l+ze*mu_in]--) // derive Cdx_act - dz*mu_in - qpwork.err.tail(qpmodel.n_in) -= - qpwork.dw_aug.tail(qpmodel.n_in) * qpresults.info.mu_in; + qpwork.err.tail(n_constraints) -= + qpwork.dw_aug.tail(n_constraints) * qpresults.info.mu_in; // derive [Cx-u+ze*mu_in]_+ + [Cx-l+ze*mu_in]-- -z*mu_in qpwork.active_part_z -= qpresults.z * qpresults.info.mu_in; @@ -287,14 +288,14 @@ primal_dual_derivative_results(const Model& qpmodel, // norm(Adx)**2 + nu*mu_eq_inv * norm(Adx-dy*mu_eq)**2 + mu_in_inv * // norm(Cdx_act)**2 + nu*mu_in_inv * norm(Cdx_act-dz*mu_in)**2 a += qpresults.info.nu * qpresults.info.mu_in_inv * - qpwork.err.tail(qpmodel.n_in).squaredNorm(); + qpwork.err.tail(n_constraints).squaredNorm(); // contains now b = dx.dot(H.dot(x) + rho*(x-xe) + g) + mu_eq_inv * // Adx.dot(res_eq) + nu*mu_eq_inv * (Adx-dy*mu_eq).dot(res_eq-y*mu_eq) + // mu_in_inv // * Cdx_act.dot([Cx-u+ze*mu_in]_+ + [Cx-l+ze*mu_in]--) + nu*mu_in_inv // (Cdx_act-dz*mu_in).dot([Cx-u+ze*mu_in]_+ + [Cx-l+ze*mu_in]-- - z*mu_in) b += qpresults.info.nu * qpresults.info.mu_in_inv * - qpwork.err.tail(qpmodel.n_in).dot(qpwork.active_part_z); + qpwork.err.tail(n_constraints).dot(qpwork.active_part_z); return { a, @@ -315,7 +316,8 @@ void primal_dual_ls(const Model& qpmodel, Results& qpresults, Workspace& qpwork, - const Settings& qpsettings) + const Settings& qpsettings, + const isize n_constraints) { /* @@ -367,7 +369,7 @@ primal_dual_ls(const Model& qpmodel, // 1.1 add solutions of equations C(x+alpha dx)-l +ze/mu_in = 0 and C(x+alpha // dx)-u +ze/mu_in = 0 - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpwork.Cdx(i) != 0.) { alpha_ = @@ -399,12 +401,12 @@ primal_dual_ls(const Model& qpmodel, switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: { auto res = gpdal_derivative_results( - qpmodel, qpresults, qpwork, qpsettings, T(0)); + qpmodel, qpresults, qpwork, qpsettings, n_constraints, T(0)); qpwork.alpha = -res.b / res.a; } break; case MeritFunctionType::PDAL: { - auto res = - primal_dual_derivative_results(qpmodel, qpresults, qpwork, T(0)); + auto res = primal_dual_derivative_results( + qpmodel, qpresults, qpwork, n_constraints, T(0)); qpwork.alpha = -res.b / res.a; } break; } @@ -440,11 +442,12 @@ primal_dual_ls(const Model& qpmodel, switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: gr = gpdal_derivative_results( - qpmodel, qpresults, qpwork, qpsettings, alpha_) + qpmodel, qpresults, qpwork, qpsettings, n_constraints, alpha_) .grad; break; case MeritFunctionType::PDAL: - gr = primal_dual_derivative_results(qpmodel, qpresults, qpwork, alpha_) + gr = primal_dual_derivative_results( + qpmodel, qpresults, qpwork, n_constraints, alpha_) .grad; break; } @@ -469,15 +472,19 @@ primal_dual_ls(const Model& qpmodel, if (alpha_last_neg == T(0)) { switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: - last_neg_grad = - gpdal_derivative_results( - qpmodel, qpresults, qpwork, qpsettings, alpha_last_neg) - .grad; + last_neg_grad = gpdal_derivative_results(qpmodel, + qpresults, + qpwork, + qpsettings, + n_constraints, + alpha_last_neg) + .grad; break; case MeritFunctionType::PDAL: - last_neg_grad = primal_dual_derivative_results( - qpmodel, qpresults, qpwork, alpha_last_neg) - .grad; + last_neg_grad = + primal_dual_derivative_results( + qpmodel, qpresults, qpwork, n_constraints, alpha_last_neg) + .grad; break; } } @@ -489,8 +496,13 @@ primal_dual_ls(const Model& qpmodel, */ switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: { - PrimalDualDerivativeResult res = gpdal_derivative_results( - qpmodel, qpresults, qpwork, qpsettings, 2 * alpha_last_neg + 1); + PrimalDualDerivativeResult res = + gpdal_derivative_results(qpmodel, + qpresults, + qpwork, + qpsettings, + n_constraints, + 2 * alpha_last_neg + 1); auto& a = res.a; auto& b = res.b; // grad = a * alpha + b @@ -499,7 +511,7 @@ primal_dual_ls(const Model& qpmodel, } break; case MeritFunctionType::PDAL: { PrimalDualDerivativeResult res = primal_dual_derivative_results( - qpmodel, qpresults, qpwork, 2 * alpha_last_neg + 1); + qpmodel, qpresults, qpwork, n_constraints, 2 * alpha_last_neg + 1); auto& a = res.a; auto& b = res.b; // grad = a * alpha + b @@ -534,6 +546,7 @@ template void active_set_change(const Model& qpmodel, Results& qpresults, + const isize n_constraints, Workspace& qpwork) { @@ -593,14 +606,13 @@ active_set_change(const Model& qpmodel, proxsuite::linalg::veg::dynstack::DynStackMut stack{ proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() }; - { auto _planned_to_delete = stack.make_new_for_overwrite( - proxsuite::linalg::veg::Tag{}, isize(qpmodel.n_in)); + proxsuite::linalg::veg::Tag{}, isize(n_constraints)); isize* planned_to_delete = _planned_to_delete.ptr_mut(); isize planned_to_delete_count = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpwork.current_bijection_map(i) < qpwork.n_c) { if (!qpwork.active_inequalities(i)) { // delete current_bijection_map(i) @@ -609,13 +621,13 @@ active_set_change(const Model& qpmodel, qpwork.current_bijection_map(i) + qpmodel.dim + qpmodel.n_eq; ++planned_to_delete_count; - for (isize j = 0; j < qpmodel.n_in; j++) { + for (isize j = 0; j < n_constraints; j++) { if (qpwork.new_bijection_map(j) > qpwork.new_bijection_map(i)) { qpwork.new_bijection_map(j) -= 1; } } n_c_f -= 1; - qpwork.new_bijection_map(i) = qpmodel.n_in - 1; + qpwork.new_bijection_map(i) = n_constraints - 1; } } } @@ -630,21 +642,21 @@ active_set_change(const Model& qpmodel, { auto _planned_to_add = stack.make_new_for_overwrite( - proxsuite::linalg::veg::Tag{}, qpmodel.n_in); + proxsuite::linalg::veg::Tag{}, n_constraints); auto planned_to_add = _planned_to_add.ptr_mut(); isize planned_to_add_count = 0; T mu_in_neg(-qpresults.info.mu_in); isize n_c = n_c_f; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpwork.active_inequalities(i)) { if (qpwork.new_bijection_map(i) >= n_c_f) { // add at the end planned_to_add[planned_to_add_count] = i; ++planned_to_add_count; - for (isize j = 0; j < qpmodel.n_in; j++) { + for (isize j = 0; j < n_constraints; j++) { if (qpwork.new_bijection_map(j) < qpwork.new_bijection_map(i) && qpwork.new_bijection_map(j) >= n_c_f) { qpwork.new_bijection_map(j) += 1; @@ -664,7 +676,14 @@ active_set_change(const Model& qpmodel, for (isize k = 0; k < planned_to_add_count; ++k) { isize index = planned_to_add[k]; auto col = new_cols.col(k); - col.head(n) = (qpwork.C_scaled.row(index)); + if (index >= qpmodel.n_in) { + col.head(n).setZero(); + // I_scaled = ED which is the diagonal matrix + // col[index-qpmodel.n_in] = ruiz.delta[index-qpmodel.n_in]; + col[index - qpmodel.n_in] = qpwork.i_scaled[index - qpmodel.n_in]; + } else { + col.head(n) = (qpwork.C_scaled.row(index)); + } col.tail(n_eq + n_c_f).setZero(); col[n + n_eq + n_c + k] = mu_in_neg; } @@ -674,7 +693,6 @@ active_set_change(const Model& qpmodel, qpwork.constraints_changed = true; } } - qpwork.n_c = n_c_f; qpwork.current_bijection_map = qpwork.new_bijection_map; qpwork.dw_aug.setZero(); diff --git a/include/proxsuite/proxqp/dense/model.hpp b/include/proxsuite/proxqp/dense/model.hpp index 4dd1b02f2..af0d27e19 100644 --- a/include/proxsuite/proxqp/dense/model.hpp +++ b/include/proxsuite/proxqp/dense/model.hpp @@ -31,6 +31,8 @@ struct Model Vec b; Vec u; Vec l; + Vec u_box; + Vec l_box; ///// model sizes isize dim; @@ -44,7 +46,7 @@ struct Model * @param n_eq number of equality constraints. * @param n_in number of inequality constraints. */ - Model(isize dim, isize n_eq, isize n_in) + Model(isize dim, isize n_eq, isize n_in, bool box_constraints = false) : H(dim, dim) , g(dim) , A(n_eq, dim) @@ -73,6 +75,17 @@ struct Model // problem is only lower bounded) l.fill(-infinite_bound_value); // in case it appears l is nullopt (i.e., the // problem is only upper bounded) + + if (box_constraints) { + u_box.resize(dim); + l_box.resize(dim); + u_box.fill( + +infinite_bound_value); // in case it appears u is nullopt (i.e., the + // problem is only lower bounded) + l_box.fill( + -infinite_bound_value); // in case it appears l is nullopt (i.e., the + // problem is only upper bounded) + } } proxsuite::proxqp::sparse::SparseModel to_sparse() @@ -85,7 +98,7 @@ struct Model return res; } - bool is_valid() + bool is_valid(const bool box_constraints) { #define PROXSUITE_CHECK_SIZE(size, expected_size) \ if (size != 0) { \ @@ -99,6 +112,10 @@ struct Model PROXSUITE_CHECK_SIZE(b.size(), n_eq); PROXSUITE_CHECK_SIZE(u.size(), n_in); PROXSUITE_CHECK_SIZE(l.size(), n_in); + if (box_constraints) { + PROXSUITE_CHECK_SIZE(u_box.size(), dim); + PROXSUITE_CHECK_SIZE(l_box.size(), dim); + } if (H.size()) { PROXSUITE_CHECK_SIZE(H.rows(), dim); PROXSUITE_CHECK_SIZE(H.cols(), dim); @@ -129,7 +146,8 @@ operator==(const Model& model1, const Model& model2) model1.H == model2.H && model1.g == model2.g && model1.A == model2.A && model1.b == model2.b && model1.C == model2.C && model1.l == model2.l && - model1.u == model2.u; + model1.u == model2.u && model1.l_box == model2.l_box && + model1.u_box == model2.u_box; return value; } diff --git a/include/proxsuite/proxqp/dense/preconditioner/ruiz.hpp b/include/proxsuite/proxqp/dense/preconditioner/ruiz.hpp index 818f7bdf0..c853172d2 100644 --- a/include/proxsuite/proxqp/dense/preconditioner/ruiz.hpp +++ b/include/proxsuite/proxqp/dense/preconditioner/ruiz.hpp @@ -12,7 +12,7 @@ #include #include #include - +#include #include namespace proxsuite { @@ -36,9 +36,9 @@ ruiz_scale_qp_in_place( // isize max_iter, Symmetry sym, ProblemType problem_type, + const bool box_constraints, proxsuite::linalg::veg::dynstack::DynStackMut stack) -> T { - T c(1); auto S = delta_.to_eigen(); auto H = qp.H.to_eigen(); @@ -49,6 +49,11 @@ ruiz_scale_qp_in_place( // auto u = qp.u.to_eigen(); auto l = qp.l.to_eigen(); + auto l_box = qp.l_box.to_eigen(); + auto u_box = qp.u_box.to_eigen(); + auto i_scaled = qp.I.to_eigen(); + i_scaled.setOnes(); + static constexpr T machine_eps = std::numeric_limits::epsilon(); /* * compute equilibration parameters and scale in place the qp following @@ -60,11 +65,13 @@ ruiz_scale_qp_in_place( // isize n = qp.H.rows; isize n_eq = qp.A.rows; isize n_in = qp.C.rows; + isize n_constraints(n_in); + if (box_constraints) { + n_constraints += n; + } T gamma = T(1); - - LDLT_TEMP_VEC(T, delta, n + n_eq + n_in, stack); - + LDLT_TEMP_VEC(T, delta, n + n_eq + n_constraints, stack); i64 iter = 1; while (infty_norm((1 - delta.array()).matrix()) > epsilon) { if (logger_ptr != nullptr) { @@ -85,10 +92,9 @@ ruiz_scale_qp_in_place( // switch (problem_type) { case ProblemType::LP: for (isize k = 0; k < n; ++k) { - T aux = sqrt(std::max({ - n_eq > 0 ? infty_norm(A.col(k)) : T(0), - n_in > 0 ? infty_norm(C.col(k)) : T(0), - })); + T aux = sqrt(std::max({ n_eq > 0 ? infty_norm(A.col(k)) : T(0), + n_in > 0 ? infty_norm(C.col(k)) : T(0), + box_constraints ? i_scaled[k] : T(0) })); if (aux == T(0)) { delta(k) = T(1); } else { @@ -100,12 +106,12 @@ ruiz_scale_qp_in_place( // for (isize k = 0; k < n; ++k) { switch (sym) { case Symmetry::upper: { // upper triangular part - T aux = sqrt(std::max({ - infty_norm(H.col(k).head(k)), - infty_norm(H.row(k).tail(n - k)), - n_eq > 0 ? infty_norm(A.col(k)) : T(0), - n_in > 0 ? infty_norm(C.col(k)) : T(0), - })); + T aux = + sqrt(std::max({ infty_norm(H.col(k).head(k)), + infty_norm(H.row(k).tail(n - k)), + n_eq > 0 ? infty_norm(A.col(k)) : T(0), + n_in > 0 ? infty_norm(C.col(k)) : T(0), + box_constraints ? i_scaled[k] : T(0) })); if (aux == T(0)) { delta(k) = T(1); } else { @@ -115,12 +121,12 @@ ruiz_scale_qp_in_place( // } case Symmetry::lower: { // lower triangular part - T aux = sqrt(std::max({ - infty_norm(H.col(k).head(k)), - infty_norm(H.col(k).tail(n - k)), - n_eq > 0 ? infty_norm(A.col(k)) : T(0), - n_in > 0 ? infty_norm(C.col(k)) : T(0), - })); + T aux = + sqrt(std::max({ infty_norm(H.col(k).head(k)), + infty_norm(H.col(k).tail(n - k)), + n_eq > 0 ? infty_norm(A.col(k)) : T(0), + n_in > 0 ? infty_norm(C.col(k)) : T(0), + box_constraints ? i_scaled[k] : T(0) })); if (aux == T(0)) { delta(k) = T(1); } else { @@ -130,11 +136,11 @@ ruiz_scale_qp_in_place( // } case Symmetry::general: { - T aux = sqrt(std::max({ - infty_norm(H.col(k)), - n_eq > 0 ? infty_norm(A.col(k)) : T(0), - n_in > 0 ? infty_norm(C.col(k)) : T(0), - })); + T aux = + sqrt(std::max({ infty_norm(H.col(k)), + n_eq > 0 ? infty_norm(A.col(k)) : T(0), + n_in > 0 ? infty_norm(C.col(k)) : T(0), + box_constraints ? i_scaled[k] : T(0) })); if (aux == T(0)) { delta(k) = T(1); } else { @@ -162,17 +168,29 @@ ruiz_scale_qp_in_place( // delta(k + n + n_eq) = T(1) / (aux + machine_eps); } } + if (box_constraints) { + for (isize k = 0; k < n; ++k) { + delta(k + n + n_eq + n_in) = T(1) / sqrt(i_scaled[k] + machine_eps); + } + } } { // normalize A and C A = delta.segment(n, n_eq).asDiagonal() * A * delta.head(n).asDiagonal(); - C = delta.tail(n_in).asDiagonal() * C * delta.head(n).asDiagonal(); + C = delta.segment(n + n_eq, n_in).asDiagonal() * C * + delta.head(n).asDiagonal(); + if (box_constraints) { + i_scaled.array() *= delta.head(n).array(); + i_scaled.array() *= delta.tail(n).array(); + u_box.array() *= delta.tail(n).array(); + l_box.array() *= delta.tail(n).array(); + } // normalize vectors g.array() *= delta.head(n).array(); - b.array() *= delta.middleRows(n, n_eq).array(); - u.array() *= delta.tail(n_in).array(); - l.array() *= delta.tail(n_in).array(); + b.array() *= delta.segment(n, n_eq).array(); + u.array() *= delta.segment(n + n_eq, n_in).array(); + l.array() *= delta.segment(n + n_eq, n_in).array(); // normalize H switch (problem_type) { @@ -263,6 +281,8 @@ struct RuizEquilibration Vec delta; T c; isize dim; + isize n_eq; + isize n_in; T epsilon; i64 max_iter; Symmetry sym; @@ -271,7 +291,8 @@ struct RuizEquilibration /*! * Default constructor. * @param dim primal variable dimension. - * @param n_eq_in number of equality and inequality constraints. + * @param n_eq_constraints number of equality and inequality constraints + * (n_in+n_constraints if box constraints are present, or n_in). * @param epsilon_ accuracy required for stopping the ruiz equilibration * algorithm. * @param max_iter_ maximum number of ruiz equilibration iterations. @@ -279,14 +300,18 @@ struct RuizEquilibration * @param logger parameter for printing or not intermediary results. */ explicit RuizEquilibration(isize dim_, - isize n_eq_in, + isize n_eq_, + isize n_in_, + bool box_constraints, T epsilon_ = T(1e-3), i64 max_iter_ = 10, Symmetry sym_ = Symmetry::general, std::ostream* logger = nullptr) - : delta(Vec::Ones(dim_ + n_eq_in)) + : delta(Vec::Ones(dim_ + n_eq_ + n_in_ + (box_constraints ? dim_ : 0))) , c(1) , dim(dim_) + , n_eq(n_eq_) + , n_in(n_in_) , epsilon(epsilon_) , max_iter(max_iter_) , sym(sym_) @@ -312,10 +337,15 @@ struct RuizEquilibration static auto scale_qp_in_place_req(proxsuite::linalg::veg::Tag tag, isize n, isize n_eq, - isize n_in) + isize n_in, + bool box_constraints) -> proxsuite::linalg::veg::dynstack::StackReq { - return proxsuite::linalg::dense::temp_vec_req(tag, n + n_eq + n_in); + if (box_constraints) { + return proxsuite::linalg::dense::temp_vec_req(tag, 2 * n + n_eq + n_in); + } else { + return proxsuite::linalg::dense::temp_vec_req(tag, n + n_eq + n_in); + } } // H_new = c * head @ H @ head @@ -337,6 +367,7 @@ struct RuizEquilibration const isize max_iter, const T epsilon, const ProblemType problem_type, + const bool box_constraints, proxsuite::linalg::veg::dynstack::DynStackMut stack) { if (execute_preconditioner) { @@ -348,6 +379,7 @@ struct RuizEquilibration max_iter, sym, problem_type, + box_constraints, stack); } else { @@ -358,90 +390,75 @@ struct RuizEquilibration auto C = qp.C.to_eigen(); auto u = qp.u.to_eigen(); auto l = qp.l.to_eigen(); + auto l_box = qp.l_box.to_eigen(); + auto u_box = qp.u_box.to_eigen(); + auto i_scaled = qp.I.to_eigen(); // it is a vector isize n = qp.H.rows; isize n_eq = qp.A.rows; isize n_in = qp.C.rows; // normalize A and C A = delta.segment(n, n_eq).asDiagonal() * A * delta.head(n).asDiagonal(); - C = delta.tail(n_in).asDiagonal() * C * delta.head(n).asDiagonal(); + C = delta.segment(n + n_eq, n_in).asDiagonal() * C * + delta.head(n).asDiagonal(); // normalize H - switch (sym) { - case Symmetry::upper: { - // upper triangular part - for (isize j = 0; j < n; ++j) { - H.col(j).head(j + 1) *= delta(j); - } - // normalisation des lignes - for (isize i = 0; i < n; ++i) { - H.row(i).tail(n - i) *= delta(i); - } - break; - } - case Symmetry::lower: { - // lower triangular part - for (isize j = 0; j < n; ++j) { - H.col(j).tail(n - j) *= delta(j); - } - // normalisation des lignes - for (isize i = 0; i < n; ++i) { - H.row(i).head(i + 1) *= delta(i); + switch (problem_type) { + case ProblemType::QP: + switch (sym) { + case Symmetry::upper: { + // upper triangular part + for (isize j = 0; j < n; ++j) { + H.col(j).head(j + 1) *= delta(j); + } + // normalisation des lignes + for (isize i = 0; i < n; ++i) { + H.row(i).tail(n - i) *= delta(i); + } + break; + } + case Symmetry::lower: { + // lower triangular part + for (isize j = 0; j < n; ++j) { + H.col(j).tail(n - j) *= delta(j); + } + // normalisation des lignes + for (isize i = 0; i < n; ++i) { + H.row(i).head(i + 1) *= delta(i); + } + break; + } + case Symmetry::general: { + // all matrix + H = delta.head(n).asDiagonal() * H * delta.head(n).asDiagonal(); + break; + } + default: + break; } break; - } - case Symmetry::general: { - // all matrix - H = delta.head(n).asDiagonal() * H * delta.head(n).asDiagonal(); - break; - } - default: + + case ProblemType::LP: break; } // normalize vectors g.array() *= delta.head(n).array(); b.array() *= delta.segment(n, n_eq).array(); - l.array() *= delta.tail(n_in).array(); - u.array() *= delta.tail(n_in).array(); + l.array() *= delta.segment(n + n_eq, n_in).array(); + u.array() *= delta.segment(n + n_eq, n_in).array(); + + if (box_constraints) { + u_box.array() *= delta.tail(n).array(); + l_box.array() *= delta.tail(n).array(); + i_scaled.array() *= delta.tail(n).array(); + i_scaled.array() *= delta.head(n).array(); + } g *= c; H *= c; } } - /*! - * Scales the qp performing the ruiz equilibrator algorithm considering user - * options. - * @param qp qp model. - * @param scaled_qp qp to be scaled. - * @param tmp_delta_preallocated temporary variable used for performing the - * equilibration. - */ - void scale_qp(const QpViewBox qp, - QpViewBoxMut scaled_qp, - bool execute_preconditioner, - VectorViewMut tmp_delta_preallocated) const - { - - /* - * scaled_qp is scaled, whereas first qp is not - * the procedure computes as well equilibration parameters using default - * parameters - */ - - scaled_qp.H.to_eigen() = qp.H.to_eigen(); - scaled_qp.A.to_eigen() = qp.A.to_eigen(); - scaled_qp.C.to_eigen() = qp.C.to_eigen(); - scaled_qp.g.to_eigen() = qp.g.to_eigen(); - scaled_qp.b.to_eigen() = qp.b.to_eigen(); - scaled_qp.d.to_eigen() = qp.d.to_eigen(); - - scale_qp_in_place(scaled_qp, - execute_preconditioner, - tmp_delta_preallocated, - epsilon, - max_iter); - } // modifies variables in place /*! * Scales a primal variable in place. @@ -467,9 +484,8 @@ struct RuizEquilibration */ void scale_dual_in_place_eq(VectorViewMut dual) const { - dual.to_eigen().array() = - dual.as_const().to_eigen().array() / - delta.middleRows(dim, dual.to_eigen().size()).array() * c; + dual.to_eigen().array() = dual.as_const().to_eigen().array() / + delta.middleRows(dim, n_eq).array() * c; } /*! * Scales a dual inequality constrained variable in place. @@ -478,7 +494,7 @@ struct RuizEquilibration void scale_dual_in_place_in(VectorViewMut dual) const { dual.to_eigen().array() = dual.as_const().to_eigen().array() / - delta.tail(dual.to_eigen().size()).array() * c; + delta.segment(dim + n_eq, n_in).array() * c; } /*! * Unscales a primal variable in place. @@ -497,15 +513,32 @@ struct RuizEquilibration dual.to_eigen().array() = dual.as_const().to_eigen().array() * delta.tail(delta.size() - dim).array() / c; } + /*! + * Unscales a dual variable in place for box inequality constraints. + * @param dual dual variable (includes equalities constraints only). + */ + void unscale_box_dual_in_place_in(VectorViewMut dual) const + { + dual.to_eigen().array() = + delta.tail(dim).array() * dual.as_const().to_eigen().array() / c; + } + /*! + * scales a dual variable in place for box inequality constraints. + * @param dual dual variable (includes equalities constraints only). + */ + void scale_box_dual_in_place_in(VectorViewMut dual) const + { + dual.to_eigen().array() = + dual.as_const().to_eigen().array() / delta.tail(dim).array() * c; + } /*! * Unscales a dual equality constrained variable in place. * @param dual dual variable (includes equalities constraints only). */ void unscale_dual_in_place_eq(VectorViewMut dual) const { - dual.to_eigen().array() = - dual.as_const().to_eigen().array() * - delta.middleRows(dim, dual.to_eigen().size()).array() / c; + dual.to_eigen().array() = dual.as_const().to_eigen().array() * + delta.middleRows(dim, n_eq).array() / c; } /*! * Unscales a dual inequality constrained variable in place. @@ -514,7 +547,7 @@ struct RuizEquilibration void unscale_dual_in_place_in(VectorViewMut dual) const { dual.to_eigen().array() = dual.as_const().to_eigen().array() * - delta.tail(dual.to_eigen().size()).array() / c; + delta.segment(dim + n_eq, n_in).array() / c; } // modifies residuals in place /*! @@ -533,8 +566,7 @@ struct RuizEquilibration */ void scale_primal_residual_in_place_eq(VectorViewMut primal_eq) const { - primal_eq.to_eigen().array() *= - delta.middleRows(dim, primal_eq.to_eigen().size()).array(); + primal_eq.to_eigen().array() *= delta.middleRows(dim, n_eq).array(); } /*! * Scales a primal inequality constraint residual in place. @@ -542,8 +574,15 @@ struct RuizEquilibration */ void scale_primal_residual_in_place_in(VectorViewMut primal_in) const { - primal_in.to_eigen().array() *= - delta.tail(primal_in.to_eigen().size()).array(); + primal_in.to_eigen().array() *= delta.segment(dim + n_eq, n_in).array(); + } + /*! + * Scales a primal box inequality constraint residual in place. + * @param primal primal inequality constraint residual. + */ + void scale_box_primal_residual_in_place_in(VectorViewMut primal_in) const + { + primal_in.to_eigen().array() *= delta.tail(dim).array(); } /*! * Scales a dual residual in place. @@ -562,14 +601,21 @@ struct RuizEquilibration { primal.to_eigen().array() /= delta.tail(delta.size() - dim).array(); } + /*! + * Unscales a primal equality constraint residual in place. + * @param primal primal equality constraint residual. + */ + void unscale_box_primal_residual_in_place(VectorViewMut primal) const + { + primal.to_eigen().array() /= delta.tail(dim).array(); + } /*! * Unscales a primal equality constraint residual in place. * @param primal primal equality constraint residual. */ void unscale_primal_residual_in_place_eq(VectorViewMut primal_eq) const { - primal_eq.to_eigen().array() /= - delta.middleRows(dim, primal_eq.to_eigen().size()).array(); + primal_eq.to_eigen().array() /= delta.middleRows(dim, n_eq).array(); } /*! * Unscales a primal inequality constraint residual in place. @@ -577,8 +623,15 @@ struct RuizEquilibration */ void unscale_primal_residual_in_place_in(VectorViewMut primal_in) const { - primal_in.to_eigen().array() /= - delta.tail(primal_in.to_eigen().size()).array(); + primal_in.to_eigen().array() /= delta.middleRows(dim + n_eq, n_in).array(); + } + /*! + * Unscales a primal inequality constraint residual in place. + * @param primal primal inequality constraint residual. + */ + void unscale_box_primal_residual_in_place_in(VectorViewMut primal_in) const + { + primal_in.to_eigen().array() /= delta.tail(dim).array(); } /*! * Unscales a dual residual in place. diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 2265843d5..9073fa8ef 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -40,6 +40,7 @@ void refactorize(const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const isize n_constraints, T rho_new) { @@ -64,11 +65,18 @@ refactorize(const Model& qpmodel, LDLT_TEMP_MAT(T, new_cols, n + n_eq + n_c, n_c, stack); T mu_in_neg(-qpresults.info.mu_in); - for (isize i = 0; i < n_in; ++i) { + for (isize i = 0; i < n_constraints; ++i) { isize j = qpwork.current_bijection_map[i]; if (j < n_c) { auto col = new_cols.col(j); - col.head(n) = qpwork.C_scaled.row(i); + if (i >= n_in) { + // I_scaled = D which is the diagonal matrix + // scaling x + // col(i-n_in) = ruiz.delta[i-qpmodel.n_in]; + col(i - n_in) = qpwork.i_scaled[i - qpmodel.n_in]; + } else { + col.head(n) = qpwork.C_scaled.row(i); + } col.segment(n, n_eq + n_c).setZero(); col(n + n_eq + j) = mu_in_neg; } @@ -148,6 +156,7 @@ iterative_residual(const Model& qpmodel, Results& qpresults, const Settings& qpsettings, Workspace& qpwork, + const isize n_constraints, isize inner_pb_dim) { auto& Hdx = qpwork.Hdx; @@ -170,14 +179,36 @@ iterative_residual(const Model& qpmodel, ATdy.noalias() = qpwork.A_scaled.transpose() * qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq); qpwork.err.head(qpmodel.dim).noalias() -= ATdy; - for (isize i = 0; i < qpmodel.n_in; i++) { + if (n_constraints > qpmodel.n_in) { + // there are box constraints + qpwork.active_part_z.tail(qpmodel.dim) = qpwork.dw_aug.head(qpmodel.dim); + qpwork.active_part_z.tail(qpmodel.dim).array() *= qpwork.i_scaled.array(); + // ruiz.unscale_primal_in_place(VectorViewMut{from_eigen,qpwork.active_part_z.tail(qpmodel.dim)}); + } + for (isize i = 0; i < n_constraints; i++) { isize j = qpwork.current_bijection_map(i); if (j < qpwork.n_c) { - qpwork.err.head(qpmodel.dim).noalias() -= - qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * qpwork.C_scaled.row(i); - qpwork.err(qpmodel.dim + qpmodel.n_eq + j) -= - (qpwork.C_scaled.row(i).dot(qpwork.dw_aug.head(qpmodel.dim)) - - qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * qpresults.info.mu_in); + if (i >= qpmodel.n_in) { + // I_scaled * dz_box_scaled = unscale_primally(dz_box) + qpwork.err(i - qpmodel.n_in) -= + // qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * + // ruiz.delta(i-qpmodel.n_in); + qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * + qpwork.i_scaled(i - qpmodel.n_in); + // I_scaled * dx_scaled = dx_unscaled + qpwork.err(qpmodel.dim + qpmodel.n_eq + j) -= + (qpwork.active_part_z[i] - + qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * + qpresults.info.mu_in); + } else { + qpwork.err.head(qpmodel.dim).noalias() -= + qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * + qpwork.C_scaled.row(i); + qpwork.err(qpmodel.dim + qpmodel.n_eq + j) -= + (qpwork.C_scaled.row(i).dot(qpwork.dw_aug.head(qpmodel.dim)) - + qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + j) * + qpresults.info.mu_in); + } } } Adx.noalias() = qpwork.A_scaled * qpwork.dw_aug.head(qpmodel.dim); @@ -204,6 +235,7 @@ iterative_solve_with_permut_fact( // const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const isize n_constraints, T eps, isize inner_pb_dim) { @@ -218,7 +250,8 @@ iterative_solve_with_permut_fact( // }; qpwork.ldl.solve_in_place(qpwork.dw_aug.head(inner_pb_dim), stack); - iterative_residual(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim); + iterative_residual( + qpmodel, qpresults, qpsettings, qpwork, n_constraints, inner_pb_dim); ++it; T preverr = infty_norm(qpwork.err.head(inner_pb_dim)); @@ -233,7 +266,8 @@ iterative_solve_with_permut_fact( // qpwork.dw_aug.head(inner_pb_dim) += qpwork.err.head(inner_pb_dim); qpwork.err.head(inner_pb_dim).setZero(); - iterative_residual(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim); + iterative_residual( + qpmodel, qpresults, qpsettings, qpwork, n_constraints, inner_pb_dim); if (infty_norm(qpwork.err.head(inner_pb_dim)) > preverr) { it_stability += 1; @@ -249,14 +283,15 @@ iterative_solve_with_permut_fact( // if (infty_norm(qpwork.err.head(inner_pb_dim)) >= std::max(eps, qpsettings.eps_refact)) { - refactorize(qpmodel, qpresults, qpwork, qpresults.info.rho); + refactorize(qpmodel, qpresults, qpwork, n_constraints, qpresults.info.rho); it = 0; it_stability = 0; qpwork.dw_aug.head(inner_pb_dim) = qpwork.rhs.head(inner_pb_dim); qpwork.ldl.solve_in_place(qpwork.dw_aug.head(inner_pb_dim), stack); - iterative_residual(qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim); + iterative_residual( + qpmodel, qpresults, qpsettings, qpwork, n_constraints, inner_pb_dim); preverr = infty_norm(qpwork.err.head(inner_pb_dim)); ++it; @@ -271,7 +306,7 @@ iterative_solve_with_permut_fact( // qpwork.err.head(inner_pb_dim).setZero(); iterative_residual( - qpmodel, qpresults, qpsettings, qpwork, inner_pb_dim); + qpmodel, qpresults, qpsettings, qpwork, n_constraints, inner_pb_dim); if (infty_norm(qpwork.err.head(inner_pb_dim)) > preverr) { it_stability += 1; @@ -448,12 +483,30 @@ compute_inner_loop_saddle_point(const Model& qpmodel, qpwork.active_part_z -= qpsettings.alpha_gpdal * qpresults.z * qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ + + // qpwork.active_part_z.head(qpmodel.n_in) -= + // qpsettings.alpha_gpdal * qpresults.z.head(qpmodel.n_in) * + // qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ + // if (box_constraints){ + // qpwork.active_part_z.tail(qpmodel.dim) -= + // qpsettings.alpha_gpdal * qpresults.z.tail(qpmodel.dim) * + // qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ + // } break; case MeritFunctionType::PDAL: qpwork.active_part_z -= qpresults.z * qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ // + [Cx-l+z_prev*mu_in]- - z*mu_in + // qpwork.active_part_z.head(qpmodel.n_in) -= + // qpresults.z.head(qpmodel.n_in) * + // qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ + // // + [Cx-l+z_prev*mu_in]- - z*mu_in + // if (box_constraints){ + // qpwork.active_part_z.tail(qpmodel.dim) -= + // qpresults.z.tail(qpmodel.dim) * + // qpresults.info.mu_in; // contains now : [Cx-u+z_prev*mu_in]+ + // } break; } @@ -487,6 +540,8 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, T eps) { @@ -508,16 +563,22 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, isize inner_pb_dim = qpmodel.dim + qpmodel.n_eq + numactive_inequalities; qpwork.rhs.setZero(); qpwork.dw_aug.setZero(); - - linesearch::active_set_change(qpmodel, qpresults, qpwork); - + linesearch::active_set_change(qpmodel, qpresults, n_constraints, qpwork); qpwork.rhs.head(qpmodel.dim) = -qpwork.dual_residual_scaled; + if (box_constraints) { + // use active_part_z as tmp variable in order to unscale primarilly z + // as I_scaled.T * z_scaled = unscale_primarilly_(z_scaled) + qpwork.active_part_z.tail(qpmodel.dim) = qpresults.z.tail(qpmodel.dim); + qpwork.active_part_z.tail(qpmodel.dim).array() *= qpwork.i_scaled.array(); + // ruiz.unscale_primal_in_place(VectorViewMut{from_eigen,qpwork.active_part_z.tail(qpmodel.dim)}); + } + qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq) = -qpwork.primal_residual_eq_scaled; switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { isize j = qpwork.current_bijection_map(i); if (j < qpwork.n_c) { if (qpwork.active_set_up(i)) { @@ -530,14 +591,18 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, qpresults.z(i) * qpresults.info.mu_in * qpsettings.alpha_gpdal; } } else { - qpwork.rhs.head(qpmodel.dim) += - qpresults.z(i) * - qpwork.C_scaled.row(i); // unactive unrelevant columns + // unactive unrelevant columns + if (i >= qpmodel.n_in) { + qpwork.rhs(i - qpmodel.n_in) += qpwork.active_part_z(i); + } else { + qpwork.rhs.head(qpmodel.dim) += + qpresults.z(i) * qpwork.C_scaled.row(i); + } } } break; case MeritFunctionType::PDAL: - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { isize j = qpwork.current_bijection_map(i); if (j < qpwork.n_c) { if (qpwork.active_set_up(i)) { @@ -550,9 +615,13 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, qpresults.z(i) * qpresults.info.mu_in; } } else { - qpwork.rhs.head(qpmodel.dim) += - qpresults.z(i) * - qpwork.C_scaled.row(i); // unactive unrelevant columns + if (i >= qpmodel.n_in) { + // unactive unrelevant columns + qpwork.rhs(i - qpmodel.n_in) += qpwork.active_part_z(i); + } else { + qpwork.rhs.head(qpmodel.dim) += + qpresults.z(i) * qpwork.C_scaled.row(i); + } } } break; @@ -562,11 +631,12 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, qpmodel, qpresults, qpwork, + n_constraints, eps, inner_pb_dim); // use active_part_z as a temporary variable to derive unpermutted dz step - for (isize j = 0; j < qpmodel.n_in; ++j) { + for (isize j = 0; j < n_constraints; ++j) { isize i = qpwork.current_bijection_map(j); if (i < qpwork.n_c) { qpwork.active_part_z(j) = qpwork.dw_aug(qpmodel.dim + qpmodel.n_eq + i); @@ -574,7 +644,7 @@ primal_dual_semi_smooth_newton_step(const Settings& qpsettings, qpwork.active_part_z(j) = -qpresults.z(j); } } - qpwork.dw_aug.tail(qpmodel.n_in) = qpwork.active_part_z; + qpwork.dw_aug.tail(n_constraints) = qpwork.active_part_z; } /*! * Performs the Newton semismooth algorithm to minimize the primal-dual @@ -594,6 +664,8 @@ primal_dual_newton_semi_smooth(const Settings& qpsettings, const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, preconditioner::RuizEquilibration& ruiz, T eps_int) { @@ -621,18 +693,45 @@ primal_dual_newton_semi_smooth(const Settings& qpsettings, proxsuite::linalg::veg::dynstack::DynStackMut stack{ proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() }; - primal_dual_semi_smooth_newton_step( - qpsettings, qpmodel, qpresults, qpwork, eps_int); + primal_dual_semi_smooth_newton_step(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + eps_int); auto& Hdx = qpwork.Hdx; auto& Adx = qpwork.Adx; auto& Cdx = qpwork.Cdx; auto& ATdy = qpwork.CTz; - auto dx = qpwork.dw_aug.head(qpmodel.dim); auto dy = qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq); - auto dz = qpwork.dw_aug.segment(qpmodel.dim + qpmodel.n_eq, qpmodel.n_in); - Cdx.noalias() = qpwork.C_scaled * dx; + auto dz = qpwork.dw_aug.tail(n_constraints); + LDLT_TEMP_VEC(T, CTdz, qpmodel.dim, stack); + if (qpmodel.n_in > 0) { + Cdx.head(qpmodel.n_in).noalias() = qpwork.C_scaled * dx; + CTdz.noalias() = qpwork.C_scaled.transpose() * dz.head(qpmodel.n_in); + } + if (box_constraints) { + // use active_part_z as tmp variable in order to unscale primarilly dz + // as I_scaled.T * dz_scaled = unscale_primarilly_(dz_scaled) + + // qpwork.active_part_z.tail(qpmodel.dim) = dz.tail(qpmodel.dim); + // ruiz.unscale_primal_in_place(VectorViewMut{from_eigen,qpwork.active_part_z.tail(qpmodel.dim)}); + // CTdz.noalias() += qpwork.active_part_z.tail(qpmodel.dim); + // Cdx.tail(qpmodel.dim) = dx; + // // I_scaled * dx_scaled = dx_unscaled + // ruiz.unscale_primal_in_place(VectorViewMut{from_eigen, + // Cdx.tail(qpmodel.dim)}); + + qpwork.active_part_z.tail(qpmodel.dim) = dz.tail(qpmodel.dim); + qpwork.active_part_z.tail(qpmodel.dim).array() *= qpwork.i_scaled.array(); + CTdz.noalias() += qpwork.active_part_z.tail(qpmodel.dim); + + Cdx.tail(qpmodel.dim) = dx; + Cdx.tail(qpmodel.dim).array() *= qpwork.i_scaled.array(); + } switch (qpsettings.merit_function_type) { case MeritFunctionType::GPDAL: Cdx.noalias() += @@ -641,10 +740,9 @@ primal_dual_newton_semi_smooth(const Settings& qpsettings, case MeritFunctionType::PDAL: break; } - LDLT_TEMP_VEC(T, CTdz, qpmodel.dim, stack); - CTdz.noalias() = qpwork.C_scaled.transpose() * dz; - if (qpmodel.n_in > 0) { - linesearch::primal_dual_ls(qpmodel, qpresults, qpwork, qpsettings); + if (qpmodel.n_in > 0 || box_constraints) { + linesearch::primal_dual_ls( + qpmodel, qpresults, qpwork, qpsettings, n_constraints); } auto alpha = qpwork.alpha; @@ -717,7 +815,9 @@ primal_dual_newton_semi_smooth(const Settings& qpsettings, VectorViewMut{ from_eigen, dy }, VectorViewMut{ from_eigen, dz }, qpwork, + qpmodel, qpsettings, + box_constraints, ruiz); bool is_dual_infeasible = @@ -728,6 +828,7 @@ primal_dual_newton_semi_smooth(const Settings& qpsettings, qpwork, qpsettings, qpmodel, + box_constraints, ruiz); if (is_primal_infeasible) { @@ -776,6 +877,7 @@ qp_solve( // const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const bool box_constraints, preconditioner::RuizEquilibration& ruiz) { /*** TEST WITH MATRIX FULL OF NAN FOR DEBUG @@ -786,7 +888,10 @@ qp_solve( // std::cout << "test " << test << std::endl; */ PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); - + isize n_constraints(qpmodel.n_in); + if (box_constraints) { + n_constraints += qpmodel.dim; + } if (qpsettings.compute_timings) { qpwork.timer.stop(); qpwork.timer.start(); @@ -798,29 +903,33 @@ qp_solve( // // executed (and without any intermediary model update) switch (qpsettings.initial_guess) { case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { - qpwork.cleanup(); + qpwork.cleanup(box_constraints); qpresults.cleanup(qpsettings); break; } case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { // keep solutions but restart workspace and results - qpwork.cleanup(); + qpwork.cleanup(box_constraints); qpresults.cold_start(qpsettings); ruiz.scale_primal_in_place( { proxsuite::proxqp::from_eigen, qpresults.x }); ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } break; } case InitialGuessStatus::NO_INITIAL_GUESS: { - qpwork.cleanup(); + qpwork.cleanup(box_constraints); qpresults.cleanup(qpsettings); break; } case InitialGuessStatus::WARM_START: { - qpwork.cleanup(); + qpwork.cleanup(box_constraints); qpresults.cold_start( qpsettings); // because there was already a solve, // precond was already computed if set so @@ -831,7 +940,11 @@ qp_solve( // ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } break; } case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { @@ -842,7 +955,11 @@ qp_solve( // ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } break; } } @@ -862,27 +979,32 @@ qp_solve( // qpwork.u_scaled = qpmodel.u; qpwork.l_scaled = qpmodel.l; proxsuite::proxqp::dense::setup_equilibration( - qpwork, qpsettings, ruiz, false); // reuse previous equilibration + qpwork, + qpsettings, + box_constraints, + ruiz, + false); // reuse previous equilibration proxsuite::proxqp::dense::setup_factorization( qpwork, qpmodel, qpsettings, qpresults); } switch (qpsettings.initial_guess) { case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { compute_equality_constrained_initial_guess( - qpwork, qpsettings, qpmodel, qpresults); + qpwork, qpsettings, qpmodel, n_constraints, qpresults); break; } case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { //!\ TODO in a quicker way qpwork.n_c = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpresults.z[i] != 0) { qpwork.active_inequalities[i] = true; } else { qpwork.active_inequalities[i] = false; } } - linesearch::active_set_change(qpmodel, qpresults, qpwork); + linesearch::active_set_change( + qpmodel, qpresults, n_constraints, qpwork); break; } case InitialGuessStatus::NO_INITIAL_GUESS: { @@ -891,14 +1013,15 @@ qp_solve( // case InitialGuessStatus::WARM_START: { //!\ TODO in a quicker way qpwork.n_c = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpresults.z[i] != 0) { qpwork.active_inequalities[i] = true; } else { qpwork.active_inequalities[i] = false; } } - linesearch::active_set_change(qpmodel, qpresults, qpwork); + linesearch::active_set_change( + qpmodel, qpresults, n_constraints, qpwork); break; } case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { @@ -916,7 +1039,7 @@ qp_solve( // proxsuite::proxqp::dense::setup_factorization( qpwork, qpmodel, qpsettings, qpresults); compute_equality_constrained_initial_guess( - qpwork, qpsettings, qpmodel, qpresults); + qpwork, qpsettings, qpmodel, n_constraints, qpresults); break; } case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { @@ -929,17 +1052,22 @@ qp_solve( // ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } setup_factorization(qpwork, qpmodel, qpsettings, qpresults); qpwork.n_c = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpresults.z[i] != 0) { qpwork.active_inequalities[i] = true; } else { qpwork.active_inequalities[i] = false; } } - linesearch::active_set_change(qpmodel, qpresults, qpwork); + linesearch::active_set_change( + qpmodel, qpresults, n_constraints, qpwork); break; } case InitialGuessStatus::NO_INITIAL_GUESS: { @@ -953,17 +1081,22 @@ qp_solve( // ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } setup_factorization(qpwork, qpmodel, qpsettings, qpresults); qpwork.n_c = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpresults.z[i] != 0) { qpwork.active_inequalities[i] = true; } else { qpwork.active_inequalities[i] = false; } } - linesearch::active_set_change(qpmodel, qpresults, qpwork); + linesearch::active_set_change( + qpmodel, qpresults, n_constraints, qpwork); break; } case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { @@ -976,26 +1109,30 @@ qp_solve( // ruiz.scale_dual_in_place_eq( { proxsuite::proxqp::from_eigen, qpresults.y }); ruiz.scale_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z }); + { proxsuite::proxqp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } if (qpwork.refactorize) { // refactorization only when one of the // matrices has changed or one proximal // parameter has changed setup_factorization(qpwork, qpmodel, qpsettings, qpresults); qpwork.n_c = 0; - for (isize i = 0; i < qpmodel.n_in; i++) { + for (isize i = 0; i < n_constraints; i++) { if (qpresults.z[i] != 0) { qpwork.active_inequalities[i] = true; } else { qpwork.active_inequalities[i] = false; } } - linesearch::active_set_change(qpmodel, qpresults, qpwork); + linesearch::active_set_change( + qpmodel, qpresults, n_constraints, qpwork); break; } } } } - T bcl_eta_ext_init = pow(T(0.1), qpsettings.alpha_bcl); T bcl_eta_ext = bcl_eta_ext_init; T bcl_eta_in(1); @@ -1023,6 +1160,7 @@ qp_solve( // qpresults, qpwork, ruiz, + box_constraints, primal_feasibility_lhs, primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0, @@ -1033,6 +1171,7 @@ qp_solve( // qpwork, qpmodel, qpsettings, + box_constraints, ruiz, dual_feasibility_lhs, dual_feasibility_rhs_0, @@ -1040,6 +1179,7 @@ qp_solve( // dual_feasibility_rhs_3, rhs_duality_gap, duality_gap); + qpresults.info.pri_res = primal_feasibility_lhs; qpresults.info.dua_res = dual_feasibility_lhs; qpresults.info.duality_gap = duality_gap; @@ -1073,8 +1213,11 @@ qp_solve( // ruiz.unscale_dual_in_place_eq( VectorViewMut{ from_eigen, qpresults.y }); ruiz.unscale_dual_in_place_in( - VectorViewMut{ from_eigen, qpresults.z }); - + VectorViewMut{ from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); + } { // EigenAllowAlloc _{}; qpresults.info.objValue = 0; @@ -1098,7 +1241,12 @@ qp_solve( // << " | rho=" << qpresults.info.rho << std::endl; ruiz.scale_primal_in_place(VectorViewMut{ from_eigen, qpresults.x }); ruiz.scale_dual_in_place_eq(VectorViewMut{ from_eigen, qpresults.y }); - ruiz.scale_dual_in_place_in(VectorViewMut{ from_eigen, qpresults.z }); + ruiz.scale_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); + } } if (is_primal_feasible && is_dual_feasible) { if (qpsettings.check_duality_gap) { @@ -1121,9 +1269,16 @@ qp_solve( // // primal dual version from gill and robinson - ruiz.scale_primal_residual_in_place_in(VectorViewMut{ - from_eigen, - qpwork.primal_residual_in_scaled_up }); // contains now scaled(Cx) + ruiz.scale_primal_residual_in_place_in( + VectorViewMut{ from_eigen, + qpwork.primal_residual_in_scaled_up.head( + qpmodel.n_in) }); // contains now scaled(Cx) + if (box_constraints) { + ruiz.scale_box_primal_residual_in_place_in( + VectorViewMut{ from_eigen, + qpwork.primal_residual_in_scaled_up.tail( + qpmodel.dim) }); // contains now scaled(x) + } qpwork.primal_residual_in_scaled_up += qpwork.z_prev * qpresults.info.mu_in; // contains now scaled(Cx+z_prev*mu_in) @@ -1136,28 +1291,46 @@ qp_solve( // break; } qpwork.primal_residual_in_scaled_low = qpwork.primal_residual_in_scaled_up; - qpwork.primal_residual_in_scaled_up -= + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) -= qpwork.u_scaled; // contains now scaled(Cx-u+z_prev*mu_in) - qpwork.primal_residual_in_scaled_low -= + qpwork.primal_residual_in_scaled_low.head(qpmodel.n_in) -= qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_in) - primal_dual_newton_semi_smooth( - qpsettings, qpmodel, qpresults, qpwork, ruiz, bcl_eta_in); + if (box_constraints) { + // qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= + // qpmodel.u_box; // contains now scaled(Cx-u+z_prev*mu_in) + // qpwork.primal_residual_in_scaled_low.tail(qpmodel.dim) -= + // qpmodel.l_box; // contains now scaled(Cx-l+z_prev*mu_in) + + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= + qpwork.u_box_scaled; // contains now scaled(Cx-u+z_prev*mu_in) + qpwork.primal_residual_in_scaled_low.tail(qpmodel.dim) -= + qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + } + + primal_dual_newton_semi_smooth(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + ruiz, + bcl_eta_in); if (qpresults.info.status == QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE || qpresults.info.status == QPSolverOutput::PROXQP_DUAL_INFEASIBLE) { // certificate of infeasibility qpresults.x = qpwork.dw_aug.head(qpmodel.dim); qpresults.y = qpwork.dw_aug.segment(qpmodel.dim, qpmodel.n_eq); - qpresults.z = qpwork.dw_aug.tail(qpmodel.n_in); + qpresults.z = qpwork.dw_aug.tail(n_constraints); break; } - T primal_feasibility_lhs_new(primal_feasibility_lhs); global_primal_residual(qpmodel, qpresults, qpwork, ruiz, + box_constraints, primal_feasibility_lhs_new, primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0, @@ -1177,6 +1350,7 @@ qp_solve( // qpwork, qpmodel, qpsettings, + box_constraints, ruiz, dual_feasibility_lhs_new, dual_feasibility_rhs_0, @@ -1243,6 +1417,7 @@ qp_solve( // qpwork, qpmodel, qpsettings, + box_constraints, ruiz, dual_feasibility_lhs_new, dual_feasibility_rhs_0, @@ -1286,7 +1461,12 @@ qp_solve( // ruiz.unscale_primal_in_place(VectorViewMut{ from_eigen, qpresults.x }); ruiz.unscale_dual_in_place_eq(VectorViewMut{ from_eigen, qpresults.y }); - ruiz.unscale_dual_in_place_in(VectorViewMut{ from_eigen, qpresults.z }); + ruiz.unscale_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); + } { // EigenAllowAlloc _{}; diff --git a/include/proxsuite/proxqp/dense/utils.hpp b/include/proxsuite/proxqp/dense/utils.hpp index ed306022b..4e73739c6 100644 --- a/include/proxsuite/proxqp/dense/utils.hpp +++ b/include/proxsuite/proxqp/dense/utils.hpp @@ -137,6 +137,7 @@ global_primal_residual(const Model& qpmodel, const Results& qpresults, Workspace& qpwork, const preconditioner::RuizEquilibration& ruiz, + const bool box_constraints, T& primal_feasibility_lhs, T& primal_feasibility_eq_rhs_0, T& primal_feasibility_in_rhs_0, @@ -157,18 +158,39 @@ global_primal_residual(const Model& qpmodel, // primal_residual_in_scaled_u = unscaled(Cx) // primal_residual_in_scaled_l = unscaled([Cx - u]+ + [Cx - l]-) qpwork.primal_residual_eq_scaled.noalias() = qpwork.A_scaled * qpresults.x; - qpwork.primal_residual_in_scaled_up.noalias() = qpwork.C_scaled * qpresults.x; - + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in).noalias() = + qpwork.C_scaled * qpresults.x; + if (box_constraints) { + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) = qpresults.x; + // qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim).array() *= + // qpwork.i_scaled.array(); + ruiz.unscale_primal_in_place(VectorViewMut{ + from_eigen, qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) }); + // ruiz.unscale_box_primal_residual_in_place_in(VectorViewMut{from_eigen, + // qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim)}); + } ruiz.unscale_primal_residual_in_place_eq( VectorViewMut{ from_eigen, qpwork.primal_residual_eq_scaled }); primal_feasibility_eq_rhs_0 = infty_norm(qpwork.primal_residual_eq_scaled); - ruiz.unscale_primal_residual_in_place_in( - VectorViewMut{ from_eigen, qpwork.primal_residual_in_scaled_up }); - primal_feasibility_in_rhs_0 = infty_norm(qpwork.primal_residual_in_scaled_up); - - qpwork.primal_residual_in_scaled_low = - helpers::positive_part(qpwork.primal_residual_in_scaled_up - qpmodel.u) + - helpers::negative_part(qpwork.primal_residual_in_scaled_up - qpmodel.l); + ruiz.unscale_primal_residual_in_place_in(VectorViewMut{ + from_eigen, qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) }); + primal_feasibility_in_rhs_0 = + infty_norm(qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in)); + qpwork.primal_residual_in_scaled_low.head(qpmodel.n_in) = + helpers::positive_part( + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - qpmodel.u) + + helpers::negative_part( + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - qpmodel.l); + if (box_constraints) { + primal_feasibility_in_rhs_0 = std::max( + primal_feasibility_in_rhs_0, + infty_norm(qpwork.primal_residual_in_scaled_low.tail(qpmodel.dim))); + qpwork.primal_residual_in_scaled_low.tail(qpmodel.dim) = + helpers::positive_part( + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - qpmodel.u_box) + + helpers::negative_part( + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - qpmodel.l_box); + } qpwork.primal_residual_eq_scaled -= qpmodel.b; primal_feasibility_in_lhs = infty_norm(qpwork.primal_residual_in_scaled_low); @@ -203,7 +225,9 @@ global_primal_residual_infeasibility( VectorViewMut dy, VectorViewMut dz, Workspace& qpwork, + const Model& qpmodel, const Settings& qpsettings, + const bool box_constraints, const preconditioner::RuizEquilibration& ruiz) { @@ -223,10 +247,24 @@ global_primal_residual_infeasibility( ruiz.unscale_dual_residual_in_place(ATdy); ruiz.unscale_dual_residual_in_place(CTdz); T eq_inf = dy.to_eigen().dot(qpwork.b_scaled); - T in_inf = helpers::positive_part(dz.to_eigen()).dot(qpwork.u_scaled) - - helpers::negative_part(dz.to_eigen()).dot(qpwork.l_scaled); + T in_inf = helpers::positive_part(dz.to_eigen().head(qpmodel.n_in)) + .dot(qpwork.u_scaled) - + helpers::negative_part(dz.to_eigen().head(qpmodel.n_in)) + .dot(qpwork.l_scaled); ruiz.unscale_dual_in_place_eq(dy); - ruiz.unscale_dual_in_place_in(dz); + ruiz.unscale_dual_in_place_in( + VectorViewMut{ from_eigen, dz.to_eigen().head(qpmodel.n_in) }); + if (box_constraints) { + // in_inf+= + // helpers::positive_part(dz.to_eigen().tail(qpmodel.dim)).dot(qpmodel.u) - + // helpers::negative_part(dz.to_eigen().tail(qpmodel.dim)).dot(qpmodel.l); + in_inf += helpers::positive_part(dz.to_eigen().tail(qpmodel.dim)) + .dot(qpwork.u_box_scaled) - + helpers::negative_part(dz.to_eigen().tail(qpmodel.dim)) + .dot(qpwork.l_box_scaled); + ruiz.unscale_box_dual_in_place_in( + VectorViewMut{ from_eigen, dz.to_eigen().tail(qpmodel.dim) }); + } T bound_y = qpsettings.eps_primal_inf * infty_norm(dy.to_eigen()); T bound_z = qpsettings.eps_primal_inf * infty_norm(dz.to_eigen()); @@ -263,6 +301,7 @@ global_dual_residual_infeasibility( Workspace& qpwork, const Settings& qpsettings, const Model& qpmodel, + const bool box_constraints, const preconditioner::RuizEquilibration& ruiz) { @@ -301,7 +340,20 @@ global_dual_residual_infeasibility( first_cond = first_cond && Cdx_i <= bound; } } - + if (box_constraints) { + for (i64 iter = 0; iter < qpmodel.dim; ++iter) { + T dx_i = dx.to_eigen()[iter]; + // if (qpmodel.u_box[iter] <= 1.E20 && qpmodel.l_box[iter] >= -1.E20) { + if (qpwork.u_box_scaled[iter] <= 1.E20 && + qpwork.l_box_scaled[iter] >= -1.E20) { + first_cond = first_cond && dx_i <= bound && dx_i >= bound_neg; + } else if (qpwork.u_box_scaled[iter] > 1.E20) { + first_cond = first_cond && dx_i >= bound_neg; + } else if (qpwork.l_box_scaled[iter] < -1.E20) { + first_cond = first_cond && dx_i <= bound; + } + } + } bound *= ruiz.c; bound_neg *= ruiz.c; bool second_cond_alt1 = @@ -334,6 +386,7 @@ global_dual_residual(Results& qpresults, Workspace& qpwork, const Model& qpmodel, const Settings& qpsettings, + const bool box_constraints, const preconditioner::RuizEquilibration& ruiz, T& dual_feasibility_lhs, T& dual_feasibility_rhs_0, @@ -350,6 +403,7 @@ global_dual_residual(Results& qpresults, // dual_residual_scaled = scaled(Hx + g + ATy + CTz) qpwork.dual_residual_scaled = qpwork.g_scaled; + switch (qpsettings.problem_type) { case ProblemType::LP: dual_feasibility_rhs_0 = 0; @@ -383,11 +437,25 @@ global_dual_residual(Results& qpresults, VectorViewMut{ from_eigen, qpwork.CTz }); dual_feasibility_rhs_1 = infty_norm(qpwork.CTz); - qpwork.CTz.noalias() = qpwork.C_scaled.transpose() * qpresults.z; + qpwork.CTz.noalias() = + qpwork.C_scaled.transpose() * qpresults.z.head(qpmodel.n_in); qpwork.dual_residual_scaled += qpwork.CTz; ruiz.unscale_dual_residual_in_place( VectorViewMut{ from_eigen, qpwork.CTz }); dual_feasibility_rhs_3 = infty_norm(qpwork.CTz); + if (box_constraints) { + qpwork.CTz.noalias() = qpresults.z.tail(qpmodel.dim); + // FALSE : we should add I_scaled * z_scaled and I_scaled = D, so we + // unscaled z_scaled to unscale then the dual residual + // ruiz.unscale_primal_in_place(VectorViewMut{from_eigen,qpwork.CTz}); + qpwork.CTz.array() *= qpwork.i_scaled.array(); + + qpwork.dual_residual_scaled += qpwork.CTz; + ruiz.unscale_dual_residual_in_place( + VectorViewMut{ from_eigen, qpwork.CTz }); + dual_feasibility_rhs_3 = + std::max(infty_norm(qpwork.CTz), dual_feasibility_rhs_3); + } ruiz.unscale_dual_residual_in_place( VectorViewMut{ from_eigen, qpwork.dual_residual_scaled }); @@ -403,21 +471,51 @@ global_dual_residual(Results& qpresults, duality_gap += by; ruiz.scale_dual_in_place_eq(VectorViewMut{ from_eigen, qpresults.y }); - ruiz.unscale_dual_in_place_in(VectorViewMut{ from_eigen, qpresults.z }); + ruiz.unscale_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.head(qpmodel.n_in) }); - const T zu = - helpers::select(qpwork.active_set_up, qpresults.z, 0) + T zu = + helpers::select(qpwork.active_set_up.head(qpmodel.n_in), + qpresults.z.head(qpmodel.n_in), + 0) .dot(helpers::at_most(qpmodel.u, helpers::infinite_bound::value())); rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zu)); duality_gap += zu; - const T zl = - helpers::select(qpwork.active_set_low, qpresults.z, 0) + T zl = + helpers::select(qpwork.active_set_low.head(qpmodel.n_in), + qpresults.z.head(qpmodel.n_in), + 0) .dot(helpers::at_least(qpmodel.l, -helpers::infinite_bound::value())); rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zl)); duality_gap += zl; - ruiz.scale_dual_in_place_in(VectorViewMut{ from_eigen, qpresults.z }); + ruiz.scale_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.head(qpmodel.n_in) }); + + if (box_constraints) { + ruiz.unscale_box_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); + + zu = helpers::select(qpwork.active_set_up.tail(qpmodel.dim), + qpresults.z.tail(qpmodel.dim), + 0) + .dot(helpers::at_most(qpmodel.u_box, + helpers::infinite_bound::value())); + rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zu)); + duality_gap += zu; + + zl = helpers::select(qpwork.active_set_low.tail(qpmodel.dim), + qpresults.z.tail(qpmodel.dim), + 0) + .dot(helpers::at_least(qpmodel.l_box, + -helpers::infinite_bound::value())); + rhs_duality_gap = std::max(rhs_duality_gap, std::abs(zl)); + duality_gap += zl; + + ruiz.scale_box_dual_in_place_in( + VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); + } } } // namespace dense diff --git a/include/proxsuite/proxqp/dense/views.hpp b/include/proxsuite/proxqp/dense/views.hpp index ed6d75c2c..c9324da9f 100644 --- a/include/proxsuite/proxqp/dense/views.hpp +++ b/include/proxsuite/proxqp/dense/views.hpp @@ -1361,6 +1361,9 @@ struct QpViewBox MatrixView C; VectorView u; VectorView l; + VectorView I; + VectorView u_box; + VectorView l_box; }; template @@ -1398,13 +1401,15 @@ struct QpViewBoxMut MatrixViewMut C; VectorViewMut u; VectorViewMut l; + VectorViewMut I; + VectorViewMut l_box; + VectorViewMut u_box; VEG_INLINE constexpr auto as_const() const noexcept -> QpViewBox { - return { - H.as_const(), g.as_const(), A.as_const(), b.as_const(), - C.as_const(), u.as_const(), l.as_const(), - }; + return { H.as_const(), g.as_const(), A.as_const(), b.as_const(), + C.as_const(), u.as_const(), l.as_const(), I.as_const(), + u_box.as_const(), l_box.as_const() }; } }; diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index 8734d8ced..284458571 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -40,6 +40,10 @@ struct Workspace Vec u_scaled; Vec l_scaled; + Vec u_box_scaled; + Vec l_box_scaled; + Vec i_scaled; + ///// Initial variable loading Vec x_prev; @@ -100,7 +104,10 @@ struct Workspace * @param n_eq number of equality constraints. * @param n_in number of inequality constraints. */ - Workspace(isize dim = 0, isize n_eq = 0, isize n_in = 0) + Workspace(isize dim = 0, + isize n_eq = 0, + isize n_in = 0, + bool box_constraints = false) : // // ruiz(preconditioner::RuizEquilibration{dim, n_eq + n_in}), ldl{} @@ -114,30 +121,11 @@ struct Workspace , l_scaled(n_in) , x_prev(dim) , y_prev(n_eq) - , z_prev(n_in) , kkt(dim + n_eq, dim + n_eq) - , current_bijection_map(n_in) - , new_bijection_map(n_in) - , active_set_up(n_in) - , active_set_low(n_in) - , active_inequalities(n_in) , Hdx(dim) - , Cdx(n_in) , Adx(n_eq) - , active_part_z(n_in) - , dw_aug(dim + n_eq + n_in) - , rhs(dim + n_eq + n_in) - , err(dim + n_eq + n_in) - , - - dual_residual_scaled(dim) + , dual_residual_scaled(dim) , primal_residual_eq_scaled(n_eq) - , primal_residual_in_scaled_up(n_in) - , primal_residual_in_scaled_low(n_in) - , - - primal_residual_in_scaled_up_plus_alphaCdx(n_in) - , primal_residual_in_scaled_low_plus_alphaCdx(n_in) , CTz(dim) , constraints_changed(false) , dirty(false) @@ -146,30 +134,107 @@ struct Workspace , is_initialized(false) { - ldl.reserve_uninit(dim + n_eq + n_in); - ldl_stack.resize_for_overwrite( - proxsuite::linalg::veg::dynstack::StackReq( - - proxsuite::linalg::dense::Ldlt::factorize_req(dim + n_eq + n_in) | - - (proxsuite::linalg::dense::temp_vec_req( - proxsuite::linalg::veg::Tag{}, n_eq + n_in) & - proxsuite::linalg::veg::dynstack::StackReq{ - isize{ sizeof(isize) } * (n_eq + n_in), alignof(isize) } & - proxsuite::linalg::dense::Ldlt::diagonal_update_req( - dim + n_eq + n_in, n_eq + n_in)) | - - (proxsuite::linalg::dense::temp_mat_req( - proxsuite::linalg::veg::Tag{}, dim + n_eq + n_in, n_in) & - proxsuite::linalg::dense::Ldlt::insert_block_at_req( - dim + n_eq + n_in, n_in)) | - - proxsuite::linalg::dense::Ldlt::solve_in_place_req(dim + n_eq + - n_in)) - .alloc_req()); + if (box_constraints) { + u_box_scaled.resize(dim); + u_box_scaled.setZero(); + l_box_scaled.resize(dim); + l_box_scaled.setZero(); + i_scaled.resize(dim); + i_scaled.setOnes(); + + z_prev.resize(dim + n_in); + ldl.reserve_uninit(dim + n_eq + n_in + dim); + ldl_stack.resize_for_overwrite( + proxsuite::linalg::veg::dynstack::StackReq( + + proxsuite::linalg::dense::Ldlt::factorize_req(dim + n_eq + n_in + + dim) | + + (proxsuite::linalg::dense::temp_vec_req( + proxsuite::linalg::veg::Tag{}, n_eq + n_in + dim) & + proxsuite::linalg::veg::dynstack::StackReq{ + isize{ sizeof(isize) } * (n_eq + n_in + dim), alignof(isize) } & + proxsuite::linalg::dense::Ldlt::diagonal_update_req( + dim + n_eq + n_in + dim, n_eq + n_in + dim)) | + + (proxsuite::linalg::dense::temp_mat_req( + proxsuite::linalg::veg::Tag{}, + dim + n_eq + n_in + dim, + n_in + dim) & + proxsuite::linalg::dense::Ldlt::insert_block_at_req( + dim + n_eq + n_in + dim, n_in + dim)) | + + proxsuite::linalg::dense::Ldlt::solve_in_place_req(dim + n_eq + + n_in + dim)) + + .alloc_req()); + + current_bijection_map.resize(n_in + dim); + new_bijection_map.resize(n_in + dim); + for (isize i = 0; i < n_in + dim; i++) { + current_bijection_map(i) = i; + new_bijection_map(i) = i; + } + active_set_up.resize(n_in + dim); + active_set_low.resize(n_in + dim); + active_inequalities.resize(n_in + dim); + active_part_z.resize(n_in + dim); + dw_aug.resize(dim + n_eq + n_in + dim); + rhs.resize(dim + n_eq + n_in + dim); + err.resize(dim + n_eq + n_in + dim); + primal_residual_in_scaled_up.resize(dim + n_in); + primal_residual_in_scaled_low.resize(dim + n_in); + primal_residual_in_scaled_up_plus_alphaCdx.resize(dim + n_in); + primal_residual_in_scaled_low_plus_alphaCdx.resize(dim + n_in); + Cdx.resize(n_in + dim); + alphas.reserve(2 * n_in + 2 * dim); + } else { + z_prev.resize(n_in); + ldl.reserve_uninit(dim + n_eq + n_in); + ldl_stack.resize_for_overwrite( + proxsuite::linalg::veg::dynstack::StackReq( + + proxsuite::linalg::dense::Ldlt::factorize_req(dim + n_eq + n_in) | + + (proxsuite::linalg::dense::temp_vec_req( + proxsuite::linalg::veg::Tag{}, n_eq + n_in) & + proxsuite::linalg::veg::dynstack::StackReq{ + isize{ sizeof(isize) } * (n_eq + n_in), alignof(isize) } & + proxsuite::linalg::dense::Ldlt::diagonal_update_req( + dim + n_eq + n_in, n_eq + n_in)) | + + (proxsuite::linalg::dense::temp_mat_req( + proxsuite::linalg::veg::Tag{}, dim + n_eq + n_in, n_in) & + proxsuite::linalg::dense::Ldlt::insert_block_at_req( + dim + n_eq + n_in, n_in)) | + + proxsuite::linalg::dense::Ldlt::solve_in_place_req(dim + n_eq + + n_in)) + + .alloc_req()); + + current_bijection_map.resize(n_in); + new_bijection_map.resize(n_in); + for (isize i = 0; i < n_in; i++) { + current_bijection_map(i) = i; + new_bijection_map(i) = i; + } + active_set_up.resize(n_in); + active_set_low.resize(n_in); + active_inequalities.resize(n_in); + active_part_z.resize(n_in); + dw_aug.resize(dim + n_eq + n_in); + rhs.resize(dim + n_eq + n_in); + err.resize(dim + n_eq + n_in); + primal_residual_in_scaled_up.resize(n_in); + primal_residual_in_scaled_low.resize(n_in); + primal_residual_in_scaled_up_plus_alphaCdx.resize(n_in); + primal_residual_in_scaled_low_plus_alphaCdx.resize(n_in); + Cdx.resize(n_in); + alphas.reserve(2 * n_in); + } - alphas.reserve(2 * n_in); H_scaled.setZero(); g_scaled.setZero(); A_scaled.setZero(); @@ -181,10 +246,6 @@ struct Workspace y_prev.setZero(); z_prev.setZero(); kkt.setZero(); - for (isize i = 0; i < n_in; i++) { - current_bijection_map(i) = i; - new_bijection_map(i) = i; - } Hdx.setZero(); Cdx.setZero(); Adx.setZero(); @@ -211,9 +272,10 @@ struct Workspace /*! * Clean-ups solver's workspace. */ - void cleanup() + void cleanup(const bool box_constraints) { isize n_in = C_scaled.rows(); + isize dim = H_scaled.rows(); H_scaled.setZero(); g_scaled.setZero(); A_scaled.setZero(); @@ -243,12 +305,16 @@ struct Workspace x_prev.setZero(); y_prev.setZero(); z_prev.setZero(); - - for (isize i = 0; i < n_in; i++) { + isize n_constraints(n_in); + if (box_constraints) { + n_constraints += dim; + } + for (isize i = 0; i < n_constraints; i++) { current_bijection_map(i) = i; new_bijection_map(i) = i; active_inequalities(i) = false; } + constraints_changed = false; dirty = false; refactorize = false; diff --git a/include/proxsuite/proxqp/dense/wrapper.hpp b/include/proxsuite/proxqp/dense/wrapper.hpp index 06f574d4c..e63028c56 100644 --- a/include/proxsuite/proxqp/dense/wrapper.hpp +++ b/include/proxsuite/proxqp/dense/wrapper.hpp @@ -80,11 +80,36 @@ Qp.results.y + qp.C.transpose() * Qp.results.z) .lpNorm(); template struct QP { +private: + bool box_constraints; + +public: Results results; Settings settings; Model model; Workspace work; preconditioner::RuizEquilibration ruiz; + + /*! + * Default constructor using QP model dimensions. + * @param _dim primal variable dimension. + * @param _n_eq number of equality constraints. + * @param _n_in number of inequality constraints. + * @param _box_constraints specify that there are (or not) box constraints. + */ + QP(isize _dim, isize _n_eq, isize _n_in, bool _box_constraints) + : box_constraints(_box_constraints) + , results(_dim, _n_eq, _n_in, _box_constraints) + , settings() + , model(_dim, _n_eq, _n_in, _box_constraints) + , work(_dim, _n_eq, _n_in, _box_constraints) + , ruiz(preconditioner::RuizEquilibration{ _dim, + _n_eq, + _n_in, + _box_constraints }) + { + work.timer.stop(); + } /*! * Default constructor using QP model dimensions. * @param _dim primal variable dimension. @@ -92,14 +117,19 @@ struct QP * @param _n_in number of inequality constraints. */ QP(isize _dim, isize _n_eq, isize _n_in) - : results(_dim, _n_eq, _n_in) + : box_constraints(false) + , results(_dim, _n_eq, _n_in, false) , settings() - , model(_dim, _n_eq, _n_in) - , work(_dim, _n_eq, _n_in) - , ruiz(preconditioner::RuizEquilibration{ _dim, _n_eq + _n_in }) + , model(_dim, _n_eq, _n_in, false) + , work(_dim, _n_eq, _n_in, false) + , ruiz(preconditioner::RuizEquilibration{ _dim, _n_eq, _n_in, false }) { work.timer.stop(); } + /*! + * Default constructor using QP model dimensions. + */ + bool is_box_constrained() const { return box_constraints; }; /*! * Setups the QP model (with dense matrix format) and equilibrates it if * specified by the user. @@ -128,11 +158,18 @@ struct QP optional mu_eq = nullopt, optional mu_in = nullopt) { + PROXSUITE_THROW_PRETTY( + box_constraints == true, + std::invalid_argument, + "wrong model setup: the QP object is designed with box " + "constraints, but is initialized without lower or upper box " + "inequalities."); // dense case if (settings.compute_timings) { work.timer.stop(); work.timer.start(); } + settings.compute_preconditioner = compute_preconditioner; // check the model is valid if (g != nullopt && g.value().size() != 0) { PROXSUITE_CHECK_ARGUMENT_SIZE( @@ -227,6 +264,7 @@ struct QP } proxsuite::proxqp::dense::update_proximal_parameters( settings, results, work, rho, mu_eq, mu_in); + typedef optional> optional_VecRef; proxsuite::proxqp::dense::setup(H, g, A, @@ -234,10 +272,212 @@ struct QP C, l, u, + optional_VecRef(nullopt), + optional_VecRef(nullopt), settings, model, work, results, + box_constraints, + ruiz, + preconditioner_status); + work.is_initialized = true; + if (settings.compute_timings) { + results.info.setup_time = work.timer.elapsed().user; // in microseconds + } + }; + /*! + * Setups the QP model (with dense matrix format) and equilibrates it if + * specified by the user. + * @param H quadratic cost input defining the QP model. + * @param g linear cost input defining the QP model. + * @param A equality constraint matrix input defining the QP model. + * @param b equality constraint vector input defining the QP model. + * @param C inequality constraint matrix input defining the QP model. + * @param l lower inequality constraint vector input defining the QP model. + * @param u upper inequality constraint vector input defining the QP model. + * @param l_box lower box inequality constraint vector input defining the QP + * model. + * @param u_box uppper box inequality constraint vector input defining the QP + * model. + * @param compute_preconditioner boolean parameter for executing or not the + * preconditioner. + * @param rho proximal step size wrt primal variable. + * @param mu_eq proximal step size wrt equality constrained multiplier. + * @param mu_in proximal step size wrt inequality constrained multiplier. + */ + void init(optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> l_box, + optional> u_box, + bool compute_preconditioner = true, + optional rho = nullopt, + optional mu_eq = nullopt, + optional mu_in = nullopt) + { + + // dense case + if (settings.compute_timings) { + work.timer.stop(); + work.timer.start(); + } + settings.compute_preconditioner = compute_preconditioner; + PROXSUITE_THROW_PRETTY( + box_constraints == false && (l_box != nullopt || u_box != nullopt), + std::invalid_argument, + "wrong model setup: the QP object is designed without box " + "constraints, but is initialized with lower or upper box inequalities."); + if (l_box != nullopt && l_box.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE(l_box.value().size(), + model.dim, + "the dimension wrt the primal variable x " + "variable for initializing l_box " + "is not valid."); + } else { + l_box.reset(); + } + if (u_box != nullopt && u_box.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE(u_box.value().size(), + model.dim, + "the dimension wrt the primal variable x " + "variable for initializing u_box " + "is not valid."); + } else { + l_box.reset(); + } + // check the model is valid + if (g != nullopt && g.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + g.value().size(), + model.dim, + "the dimension wrt the primal variable x variable for initializing g " + "is not valid."); + } else { + g.reset(); + } + if (b != nullopt && b.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + b.value().size(), + model.n_eq, + "the dimension wrt equality constrained variables for initializing b " + "is not valid."); + } else { + b.reset(); + } + if (u != nullopt && u.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + u.value().size(), + model.n_in, + "the dimension wrt inequality constrained variables for initializing u " + "is not valid."); + } else { + u.reset(); + } + if (u_box != nullopt && u_box.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + u_box.value().size(), + model.dim, + "the dimension wrt box inequality constrained variables for " + "initializing u_box " + "is not valid."); + } else { + u_box.reset(); + } + if (l != nullopt && l.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + l.value().size(), + model.n_in, + "the dimension wrt inequality constrained variables for initializing l " + "is not valid."); + } else { + l.reset(); + } + if (l_box != nullopt && l_box.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + l_box.value().size(), + model.dim, + "the dimension wrt box inequality constrained variables for " + "initializing l_box " + "is not valid."); + } else { + l_box.reset(); + } + if (H != nullopt && H.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + H.value().rows(), + model.dim, + "the row dimension for initializing H is not valid."); + PROXSUITE_CHECK_ARGUMENT_SIZE( + H.value().cols(), + model.dim, + "the column dimension for initializing H is not valid."); + } else { + H.reset(); + } + if (A != nullopt && A.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + A.value().rows(), + model.n_eq, + "the row dimension for initializing A is not valid."); + PROXSUITE_CHECK_ARGUMENT_SIZE( + A.value().cols(), + model.dim, + "the column dimension for initializing A is not valid."); + } else { + A.reset(); + } + if (C != nullopt && C.value().size() != 0) { + PROXSUITE_CHECK_ARGUMENT_SIZE( + C.value().rows(), + model.n_in, + "the row dimension for initializing C is not valid."); + PROXSUITE_CHECK_ARGUMENT_SIZE( + C.value().cols(), + model.dim, + "the column dimension for initializing C is not valid."); + } else { + C.reset(); + } + if (settings.initial_guess == + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT) { + work.refactorize = + true; // necessary for the first solve (then refactorize only if there + // is an update of the matrices) + } else { + work.refactorize = false; + } + work.proximal_parameter_update = false; + if (settings.compute_timings) { + work.timer.stop(); + work.timer.start(); + } + PreconditionerStatus preconditioner_status; + if (compute_preconditioner) { + preconditioner_status = proxsuite::proxqp::PreconditionerStatus::EXECUTE; + } else { + preconditioner_status = proxsuite::proxqp::PreconditionerStatus::IDENTITY; + } + proxsuite::proxqp::dense::update_proximal_parameters( + settings, results, work, rho, mu_eq, mu_in); + proxsuite::proxqp::dense::setup(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + settings, + model, + work, + results, + box_constraints, ruiz, preconditioner_status); work.is_initialized = true; @@ -275,6 +515,13 @@ struct QP optional mu_eq = nullopt, optional mu_in = nullopt) { + PROXSUITE_THROW_PRETTY( + box_constraints == true, + std::invalid_argument, + "wrong model setup: the QP object is designed without box " + "constraints, but the update does not include lower or upper box " + "inequalities."); + settings.update_preconditioner = update_preconditioner; if (!work.is_initialized) { init(H, g, A, b, C, l, u, update_preconditioner, rho, mu_eq, mu_in); return; @@ -296,7 +543,19 @@ struct QP !(H == nullopt && g == nullopt && A == nullopt && b == nullopt && C == nullopt && u == nullopt && l == nullopt); if (matrix_update) { - proxsuite::proxqp::dense::update(H, g, A, b, C, l, u, model, work); + typedef optional> optional_VecRef; + proxsuite::proxqp::dense::update(H, + g, + A, + b, + C, + l, + u, + optional_VecRef(nullopt), + optional_VecRef(nullopt), + model, + work, + box_constraints); } proxsuite::proxqp::dense::update_proximal_parameters( settings, results, work, rho, mu_eq, mu_in); @@ -311,10 +570,13 @@ struct QP optional_MatRef(nullopt), optional_VecRef(nullopt), optional_VecRef(nullopt), + optional_VecRef(nullopt), + optional_VecRef(nullopt), settings, model, work, results, + box_constraints, ruiz, preconditioner_status); @@ -322,7 +584,111 @@ struct QP results.info.setup_time = work.timer.elapsed().user; // in microseconds } }; + /*! + * Updates the QP model (with dense matrix format) and re-equilibrates it if + * specified by the user. + * @param H quadratic cost input defining the QP model. + * @param g linear cost input defining the QP model. + * @param A equality constraint matrix input defining the QP model. + * @param b equality constraint vector input defining the QP model. + * @param C inequality constraint matrix input defining the QP model. + * @param l lower inequality constraint vector input defining the QP model. + * @param u upper inequality constraint vector input defining the QP model. + * @param l_box lower inequality constraint vector input defining the QP + * model. + * @param u_box upper inequality constraint vector input defining the QP + * model. + * @param update_preconditioner bool parameter for updating or not the + * preconditioner and the associated scaled model. + * @param rho proximal step size wrt primal variable. + * @param mu_eq proximal step size wrt equality constrained multiplier. + * @param mu_in proximal step size wrt inequality constrained multiplier. + * @note The init method should be called before update. If it has not been + * done before, init is called depending on the is_initialized flag. + */ + void update(optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> l_box, + optional> u_box, + bool update_preconditioner = true, + optional rho = nullopt, + optional mu_eq = nullopt, + optional mu_in = nullopt) + { + PROXSUITE_THROW_PRETTY( + box_constraints == false && (l_box != nullopt || u_box != nullopt), + std::invalid_argument, + "wrong model setup: the QP object is designed without box " + "constraints, but the update includes lower or upper box inequalities."); + settings.update_preconditioner = update_preconditioner; + if (!work.is_initialized) { + init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + update_preconditioner, + rho, + mu_eq, + mu_in); + return; + } + // dense case + work.refactorize = false; + work.proximal_parameter_update = false; + if (settings.compute_timings) { + work.timer.stop(); + work.timer.start(); + } + PreconditionerStatus preconditioner_status; + if (update_preconditioner) { + preconditioner_status = proxsuite::proxqp::PreconditionerStatus::EXECUTE; + } else { + preconditioner_status = proxsuite::proxqp::PreconditionerStatus::KEEP; + } + const bool matrix_update = + !(H == nullopt && g == nullopt && A == nullopt && b == nullopt && + C == nullopt && u == nullopt && l == nullopt && u_box == nullopt && + l_box == nullopt); + if (matrix_update) { + proxsuite::proxqp::dense::update( + H, g, A, b, C, l, u, l_box, u_box, model, work, box_constraints); + } + proxsuite::proxqp::dense::update_proximal_parameters( + settings, results, work, rho, mu_eq, mu_in); + typedef optional> optional_MatRef; + typedef optional> optional_VecRef; + proxsuite::proxqp::dense::setup(/* avoid double assignation */ + optional_MatRef(nullopt), + optional_VecRef(nullopt), + optional_MatRef(nullopt), + optional_VecRef(nullopt), + optional_MatRef(nullopt), + optional_VecRef(nullopt), + optional_VecRef(nullopt), + optional_VecRef(nullopt), + optional_VecRef(nullopt), + settings, + model, + work, + results, + box_constraints, + ruiz, + preconditioner_status); + if (settings.compute_timings) { + results.info.setup_time = work.timer.elapsed().user; // in microseconds + } + }; /*! * Solves the QP problem using PRXOQP algorithm. */ @@ -333,6 +699,7 @@ struct QP model, results, work, + box_constraints, ruiz); }; /*! @@ -351,6 +718,7 @@ struct QP model, results, work, + box_constraints, ruiz); }; /*! @@ -359,7 +727,7 @@ struct QP void cleanup() { results.cleanup(settings); - work.cleanup(); + work.cleanup(box_constraints); } }; /*! @@ -367,7 +735,7 @@ struct QP * object, with matrices defined by Dense Eigen matrices. It is possible to set * up some of the solver parameters (warm start, initial guess option, proximal * step sizes, absolute and relative accuracies, maximum number of iterations, - * preconditioner execution). + * preconditioner execution). There are no box constraints in the model. * @param H quadratic cost input defining the QP model. * @param g linear cost input defining the QP model. * @param A equality constraint matrix input defining the QP model. @@ -439,7 +807,7 @@ solve( n_in = C.value().rows(); } - QP Qp(n, n_eq, n_in); + QP Qp(n, n_eq, n_in, false); Qp.settings.initial_guess = initial_guess; Qp.settings.check_duality_gap = check_duality_gap; @@ -467,13 +835,139 @@ solve( return Qp.results; } +/*! + * Solves the QP problem using PROXQP algorithm without the need to define a QP + * object, with matrices defined by Dense Eigen matrices. It is possible to set + * up some of the solver parameters (warm start, initial guess option, proximal + * step sizes, absolute and relative accuracies, maximum number of iterations, + * preconditioner execution). + * @param H quadratic cost input defining the QP model. + * @param g linear cost input defining the QP model. + * @param A equality constraint matrix input defining the QP model. + * @param b equality constraint vector input defining the QP model. + * @param C inequality constraint matrix input defining the QP model. + * @param l lower inequality constraint vector input defining the QP model. + * @param u upper inequality constraint vector input defining the QP model. + * @param l_box lower box inequality constraint vector input defining the QP + * model. + * @param u_box upper box inequality constraint vector input defining the QP + * model. + * @param x primal warm start. + * @param y dual equality constraint warm start. + * @param z dual inequality constraint warm start. The upper part must contain a + * warm start for inequality constraints wrt C matrix, whereas the latter wrt + * the box inequalities. + * @param verbose if set to true, the solver prints more information about each + * iteration. + * @param compute_preconditioner bool parameter for executing or not the + * preconditioner. + * @param compute_timings boolean parameter for computing the solver timings. + * @param rho proximal step size wrt primal variable. + * @param mu_eq proximal step size wrt equality constrained multiplier. + * @param mu_in proximal step size wrt inequality constrained multiplier. + * @param eps_abs absolute accuracy threshold. + * @param eps_rel relative accuracy threshold. + * @param max_iter maximum number of iteration. + * @param initial_guess initial guess option for warm starting or not the + * initial iterate values. + * @param check_duality_gap If set to true, include the duality gap in absolute + * and relative stopping criteria. + * @param eps_duality_gap_abs absolute accuracy threshold for the duality-gap + * criterion. + * @param eps_duality_gap_rel relative accuracy threshold for the duality-gap + * criterion. + */ +template +proxqp::Results +solve( + optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> l_box, + optional> u_box, + optional> x = nullopt, + optional> y = nullopt, + optional> z = nullopt, + optional eps_abs = nullopt, + optional eps_rel = nullopt, + optional rho = nullopt, + optional mu_eq = nullopt, + optional mu_in = nullopt, + optional verbose = nullopt, + bool compute_preconditioner = true, + bool compute_timings = false, + optional max_iter = nullopt, + proxsuite::proxqp::InitialGuessStatus initial_guess = + proxsuite::proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS, + bool check_duality_gap = false, + optional eps_duality_gap_abs = nullopt, + optional eps_duality_gap_rel = nullopt) +{ + isize n(0); + isize n_eq(0); + isize n_in(0); + if (H != nullopt) { + n = H.value().rows(); + } + if (A != nullopt) { + n_eq = A.value().rows(); + } + if (C != nullopt) { + n_in = C.value().rows(); + } + + QP Qp(n, n_eq, n_in, true); + Qp.settings.initial_guess = initial_guess; + Qp.settings.check_duality_gap = check_duality_gap; + + if (eps_abs != nullopt) { + Qp.settings.eps_abs = eps_abs.value(); + } + if (eps_rel != nullopt) { + Qp.settings.eps_rel = eps_rel.value(); + } + if (verbose != nullopt) { + Qp.settings.verbose = verbose.value(); + } + if (max_iter != nullopt) { + Qp.settings.max_iter = max_iter.value(); + } + if (eps_duality_gap_abs != nullopt) { + Qp.settings.eps_duality_gap_abs = eps_duality_gap_abs.value(); + } + if (eps_duality_gap_rel != nullopt) { + Qp.settings.eps_duality_gap_rel = eps_duality_gap_rel.value(); + } + Qp.settings.compute_timings = compute_timings; + Qp.init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + compute_preconditioner, + rho, + mu_eq, + mu_in); + Qp.solve(x, y, z); + + return Qp.results; +} template bool operator==(const QP& qp1, const QP& qp2) { bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && - qp1.results == qp2.results; + qp1.results == qp2.results && + qp1.is_box_constrained() == qp2.is_box_constrained(); return value; } diff --git a/include/proxsuite/proxqp/results.hpp b/include/proxsuite/proxqp/results.hpp index 3080a3a0e..e4c8858f5 100644 --- a/include/proxsuite/proxqp/results.hpp +++ b/include/proxsuite/proxqp/results.hpp @@ -79,12 +79,18 @@ struct Results * @param n_eq dimension of the number of equality constraints. * @param n_in dimension of the number of inequality constraints. */ - Results(isize dim = 0, isize n_eq = 0, isize n_in = 0) + Results(isize dim = 0, + isize n_eq = 0, + isize n_in = 0, + bool box_constraints = false) : x(dim) , y(n_eq) - , z(n_in) { - + if (box_constraints) { + z.resize(dim + n_in); + } else { + z.resize(n_in); + } x.setZero(); y.setZero(); z.setZero(); @@ -193,8 +199,6 @@ bool operator==(const Results& results1, const Results& results2) { bool value = results1.x == results2.x && results1.y == results2.y && - // results1.z == results2.z && results1.active_constraints == - // results2.active_constraints && results1.z == results2.z && results1.info == results2.info; return value; } diff --git a/test/src/dense_qp_eq.cpp b/test/src/dense_qp_eq.cpp index 732c7456d..d12eda84b 100644 --- a/test/src/dense_qp_eq.cpp +++ b/test/src/dense_qp_eq.cpp @@ -11,7 +11,6 @@ using T = double; using namespace proxsuite; - DOCTEST_TEST_CASE("qp: start from solution using the wrapper framework") { proxqp::isize dim = 30; @@ -54,7 +53,6 @@ DOCTEST_TEST_CASE("qp: start from solution using the wrapper framework") DOCTEST_CHECK((H * qp.results.x + g + A.transpose() * qp.results.y) .lpNorm() <= eps_abs); } - DOCTEST_TEST_CASE("sparse random strongly convex qp with equality constraints " "and increasing dimension with the wrapper API") { @@ -102,7 +100,6 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality constraints " << std::endl; } } - DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " "linar cost and increasing dimension using wrapper API") { diff --git a/test/src/dense_qp_wrapper.cpp b/test/src/dense_qp_wrapper.cpp index 9e8373501..a9c438010 100644 --- a/test/src/dense_qp_wrapper.cpp +++ b/test/src/dense_qp_wrapper.cpp @@ -159,7 +159,6 @@ DOCTEST_TEST_CASE( std::cout << "setup timing " << qp.results.info.setup_time << " solve time " << qp.results.info.solve_time << std::endl; } - DOCTEST_TEST_CASE( "ProxQP::dense: sparse random strongly convex qp with equality and " "inequality constraints: test update H") @@ -6798,4 +6797,355 @@ TEST_CASE("ProxQP::dense: init must be called before update") .lpNorm(); CHECK(dua_res <= eps_abs); CHECK(pri_res <= eps_abs); +} +// test of the box constraints interface +TEST_CASE("ProxQP::dense: check ordering of z when there are box constraints") +{ + dense::isize n_test(1000); + double sparsity_factor = 1.; + T eps_abs = T(1e-9); + dense::isize dim = 15; + + // mixing ineq and box constraints + for (isize i = 0; i < n_test; i++) { + utils::rand::set_seed(i); + dense::isize n_eq(dim / 4); + dense::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + // ineq and boxes + Eigen::Matrix x_sol = + utils::rand::vector_rand(dim); + Eigen::Matrix 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 u_box(dim); + u_box.setZero(); + Eigen::Matrix 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; + } + /////////////////// for debuging + // using Mat = + // Eigen::Matrix; + // 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 u_enlarged(n_in+dim); + // Eigen::Matrix 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; + // std::cout << "n " << dim << " n_eq " << n_eq << " n_in "<< n_in << + // std::endl; std::cout << "=================qp compare" << std::endl; + // proxqp::dense::QP 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.max_iter = 10; + // qp_compare.settings.max_iter_in = 10; + // qp_compare.settings.verbose = true; + // qp_compare.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + // qp_compare.init(qp_random.H, + // qp_random.g, + // qp_random.A, + // qp_random.b, + // C_enlarged, + // l_enlarged, + // u_enlarged, + // true); + // qp_compare.solve(); + // std::cout << "=================qp compare end" << std::endl; + //////////////// + + dense::QP 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; + 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, + true); + qp.solve(); + + T pri_res = std::max( + (qp_random.A * qp.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp.results.x - u_box) + + helpers::negative_part(qp.results.x - l_box)) + .lpNorm()); + T dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z.head(n_in) + + qp.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + } + // idem but without ineq constraints + for (isize i = 0; i < n_test; i++) { + utils::rand::set_seed(i); + dense::isize n_eq(dim / 4); + dense::isize n_in(0); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + // ineq and boxes + Eigen::Matrix x_sol = + utils::rand::vector_rand(dim); + Eigen::Matrix 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 u_box(dim); + u_box.setZero(); + Eigen::Matrix 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; + } + + dense::QP 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; + + 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(); + + T pri_res = std::max( + (qp_random.A * qp.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp.results.x - u_box) + + helpers::negative_part(qp.results.x - l_box)) + .lpNorm()); + T dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z.head(n_in) + + qp.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + } + // idem but without ineq and without eq constraints + for (isize i = 0; i < n_test; i++) { + dense::isize n_eq(0); + dense::isize n_in(0); + T strong_convexity_factor(1.e-2); + using Mat = + Eigen::Matrix; + Mat eye(dim, dim); + eye.setZero(); + eye.diagonal().array() += 1.; + + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + // ineq and boxes + Eigen::Matrix x_sol = + utils::rand::vector_rand(dim); + Eigen::Matrix 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 u_box(dim); + u_box.setZero(); + Eigen::Matrix 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; + } + // make a qp to compare + dense::QP qp_compare(dim, n_eq, dim, false); + qp_compare.settings.eps_abs = eps_abs; + qp_compare.settings.eps_rel = 0; + qp_compare.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp_compare.settings.compute_preconditioner = true; + qp_compare.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + eye, + l_box, + u_box, + true); + + qp_compare.solve(); + + T pri_res = std::max( + (qp_random.A * qp_compare.results.x - qp_random.b) + .lpNorm(), + (helpers::positive_part(qp_random.C * qp_compare.results.x - + qp_random.u) + + helpers::negative_part(qp_random.C * qp_compare.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp_compare.results.x - u_box) + + helpers::negative_part(qp_compare.results.x - l_box)) + .lpNorm()); + T dua_res = (qp_random.H * qp_compare.results.x + qp_random.g + + qp_random.C.transpose() * qp_compare.results.z.head(n_in) + + qp_random.A.transpose() * qp_compare.results.y + + qp_compare.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + // ineq and boxes + dense::QP 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; + qp.settings.compute_preconditioner = true; + 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, + true); + + qp.solve(); + + pri_res = std::max( + (qp_random.A * qp.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp.results.x - u_box) + + helpers::negative_part(qp.results.x - l_box)) + .lpNorm()); + dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z.head(n_in) + + qp.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + } +} +TEST_CASE("ProxQP::dense: check updates work when there are box constraints") +{ + + double sparsity_factor = 1.; + T eps_abs = T(1e-9); + dense::isize dim = 50; + dense::isize n_eq(dim / 4); + dense::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + // ineq and boxes + dense::QP qp(dim, n_eq, n_in, true); + Eigen::Matrix u_box(dim); + u_box.setZero(); + u_box.array() += 1.E2; + Eigen::Matrix l_box(dim); + l_box.setZero(); + l_box.array() -= 1.E2; + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + 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(); + + T pri_res = std::max( + (qp_random.A * qp.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp.results.x - u_box) + + helpers::negative_part(qp.results.x - l_box)) + .lpNorm()); + T dua_res = + (qp_random.H * qp.results.x + qp_random.g + + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z.head(n_in) + qp.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + + u_box.array() += 1.E1; + l_box.array() -= 1.E1; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + qp_random.l, + qp_random.u, + l_box, + u_box); + + qp.solve(); + + pri_res = std::max( + (qp_random.A * qp.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm()); + pri_res = std::max(pri_res, + (helpers::positive_part(qp.results.x - u_box) + + helpers::negative_part(qp.results.x - l_box)) + .lpNorm()); + dua_res = + (qp_random.H * qp.results.x + qp_random.g + + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z.head(n_in) + qp.results.z.tail(dim)) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); } \ No newline at end of file diff --git a/test/src/dense_qp_wrapper.py b/test/src/dense_qp_wrapper.py index 4a80e8331..647bfccbe 100644 --- a/test/src/dense_qp_wrapper.py +++ b/test/src/dense_qp_wrapper.py @@ -44,6 +44,49 @@ def generate_mixed_qp(n, seed=1): return P.toarray(), q, A[:n_eq, :], u[:n_eq], A[n_in:, :], u[n_in:], l[n_in:] +def generate_mixed_qp_with_box(n, seed=1): + """ + Generate sparse problem in dense QP format + """ + np.random.seed(seed) + + m = int(n / 4) + int(n / 4) + # m = n + n_eq = int(n / 4) + n_in = int(n / 4) + + P = spa.random( + n, n, density=0.075, data_rvs=np.random.randn, format="csc" + ).toarray() + P = (P + P.T) / 2.0 + + s = max(np.absolute(np.linalg.eigvals(P))) + P += (abs(s) + 1e-02) * spa.eye(n) + P = spa.coo_matrix(P) + print("sparsity of P : {}".format((P.nnz) / (n**2))) + q = np.random.randn(n) + A = spa.random(m, n, density=0.15, data_rvs=np.random.randn, format="csc").toarray() + v = np.random.randn(n) # Fictitious solution + delta = np.random.rand(m) # To get inequality + u = A @ v + l = -1.0e20 * np.ones(m) + delta_box = np.random.rand(n) + u_box = v + delta_box + l_box = v - delta_box + + return ( + P.toarray(), + q, + A[:n_eq, :], + u[:n_eq], + A[n_in:, :], + u[n_in:], + l[n_in:], + u_box, + l_box, + ) + + class DenseqpWrapper(unittest.TestCase): # TESTS OF GENERAL METHODS OF THE API @@ -4475,6 +4518,181 @@ def test_initializing_with_None(self): ) ) + def test_z_ordering_with_box_constraints_interface(self): + print( + "------------------------test check ordering of z when there are box constraints" + ) + + n = 50 + n_test = 1000 + eps = 1.0e-9 + # inequality and box constraints case + for i in range(n_test): + H, g, A, b, C, u, l, u_box, l_box = generate_mixed_qp_with_box(n, i) + n_eq = A.shape[0] + n_in = C.shape[0] + + qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) + qp.init(H, g, A, b, C, l, u, l_box, u_box) + qp.settings.eps_abs = eps + qp.solve() + + dua_res = normInf( + H @ qp.results.x + + g + + A.transpose() @ qp.results.y + + C.transpose() @ qp.results.z[:n_in] + + qp.results.z[n_in:] + ) + pri_res = max( + normInf(A @ qp.results.x - b), + normInf( + np.maximum(C @ qp.results.x - u, 0) + + np.minimum(C @ qp.results.x - l, 0) + ), + normInf( + np.maximum(qp.results.x - u_box, 0) + + np.minimum(qp.results.x - l_box, 0) + ), + ) + assert dua_res <= eps + assert pri_res <= eps + # no inequality and box constraints case + for i in range(n_test): + H, g, A, b, C, u, l, u_box, l_box = generate_mixed_qp_with_box(n, i) + n_eq = A.shape[0] + n_in = 0 + C = np.zeros((n_in, n)) + u = np.zeros(n_in) + l = np.zeros(n_in) + + qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) + qp.init(H, g, A, b, C, l, u, l_box, u_box) + qp.settings.eps_abs = eps + qp.solve() + + dua_res = normInf( + H @ qp.results.x + + g + + A.transpose() @ qp.results.y + + C.transpose() @ qp.results.z[:n_in] + + qp.results.z[n_in:] + ) + pri_res = max( + normInf(A @ qp.results.x - b), + normInf( + np.maximum(C @ qp.results.x - u, 0) + + np.minimum(C @ qp.results.x - l, 0) + ), + normInf( + np.maximum(qp.results.x - u_box, 0) + + np.minimum(qp.results.x - l_box, 0) + ), + ) + assert dua_res <= eps + assert pri_res <= eps + # # no inequality, no equalities and box constraints case + for i in range(n_test): + H, g, A, b, C, u, l, u_box, l_box = generate_mixed_qp_with_box(n, i) + n_eq = 0 + n_in = 0 + C = np.zeros((n_in, n)) + u = np.zeros(n_in) + l = np.zeros(n_in) + A = np.zeros((n_eq, n)) + b = np.zeros(n_eq) + + qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) + qp.init(H, g, A, b, C, l, u, l_box, u_box) + qp.settings.eps_abs = eps + qp.settings.verbose = True + qp.solve() + + dua_res = normInf( + H @ qp.results.x + + g + + A.transpose() @ qp.results.y + + C.transpose() @ qp.results.z[:n_in] + + qp.results.z[n_in:] + ) + pri_res = max( + normInf(A @ qp.results.x - b), + normInf( + np.maximum(C @ qp.results.x - u, 0) + + np.minimum(C @ qp.results.x - l, 0) + ), + normInf( + np.maximum(qp.results.x - u_box, 0) + + np.minimum(qp.results.x - l_box, 0) + ), + ) + assert dua_res <= eps + assert pri_res <= eps + + def test_updates_with_box_constraints_interface(self): + print( + "------------------------test check updates work when there are box constraints" + ) + n = 50 + H, g, A, b, C, u, l = generate_mixed_qp(n) + eps = 1.0e-9 + n_eq = A.shape[0] + n_in = C.shape[0] + u_box = np.ones(n) * 100 + l_box = -np.ones(n) * 100 + + qp = proxsuite.proxqp.dense.QP(n, n_eq, n_in, True) + qp.init(H, g, A, b, C, l, u, l_box, u_box) + qp.settings.eps_abs = eps + qp.solve() + + dua_res = normInf( + H @ qp.results.x + + g + + A.transpose() @ qp.results.y + + C.transpose() @ qp.results.z[:n_in] + + qp.results.z[n_in:] + ) + pri_res = max( + normInf(A @ qp.results.x - b), + normInf( + np.maximum(C @ qp.results.x - u, 0) + + np.minimum(C @ qp.results.x - l, 0) + ), + normInf( + np.maximum(qp.results.x - u_box, 0) + + np.minimum(qp.results.x - l_box, 0) + ), + ) + assert dua_res <= eps + assert pri_res <= eps + u_box += 10 + l_box -= 10 + + qp.update(l=l, u=u, u_box=u_box, l_box=l_box) + qp.solve() + + dua_res = normInf( + H @ qp.results.x + + g + + A.transpose() @ qp.results.y + + C.transpose() @ qp.results.z[:n_in] + + qp.results.z[n_in:] + ) + pri_res = max( + normInf(A @ qp.results.x - b), + normInf( + np.maximum(C @ qp.results.x - u, 0) + + np.minimum(C @ qp.results.x - l, 0) + ), + normInf( + np.maximum(qp.results.x - u_box, 0) + + np.minimum(qp.results.x - l_box, 0) + ), + ) + assert dua_res <= eps + assert pri_res <= eps + if __name__ == "__main__": unittest.main() diff --git a/test/src/sparse_ruiz_equilibration.cpp b/test/src/sparse_ruiz_equilibration.cpp index 529e53503..9ac1021fa 100644 --- a/test/src/sparse_ruiz_equilibration.cpp +++ b/test/src/sparse_ruiz_equilibration.cpp @@ -43,12 +43,13 @@ TEST_CASE("upper part") Eigen::Matrix C_scaled_dense = CT.transpose(); auto l_scaled_dense = l; auto u_scaled_dense = u; + bool box_constraints(false); proxqp::sparse::preconditioner::RuizEquilibration ruiz{ n, n_eq + n_in, 1e-3, 10, proxqp::sparse::preconditioner::Symmetry::UPPER, }; proxqp::dense::preconditioner::RuizEquilibration ruiz_dense{ - n, n_eq + n_in, 1e-3, 10, Symmetry::upper, + n, n_eq, n_in, box_constraints, 1e-3, 10, Symmetry::upper, }; VEG_MAKE_STACK(stack, ruiz.scale_qp_in_place_req( @@ -56,7 +57,9 @@ TEST_CASE("upper part") bool execute_preconditioner = true; proxsuite::proxqp::Settings settings; - + proxqp::dense::Vec u_scaled_box(0); + proxqp::dense::Vec l_scaled_box(0); + proxqp::dense::Vec eye(0); ruiz.scale_qp_in_place( { { proxsuite::linalg::sparse::from_eigen, H_scaled }, @@ -71,9 +74,8 @@ TEST_CASE("upper part") settings.preconditioner_max_iter, settings.preconditioner_accuracy, stack); - ruiz_dense.scale_qp_in_place( - { + proxqp::dense::QpViewBoxMut{ { proxqp::from_eigen, H_scaled_dense }, { proxqp::from_eigen, g_scaled_dense }, { proxqp::from_eigen, A_scaled_dense }, @@ -81,11 +83,15 @@ TEST_CASE("upper part") { proxqp::from_eigen, C_scaled_dense }, { proxqp::from_eigen, l_scaled_dense }, { proxqp::from_eigen, u_scaled_dense }, + { proxqp::from_eigen, u_scaled_box }, + { proxqp::from_eigen, l_scaled_box }, + { proxqp::from_eigen, eye }, }, execute_preconditioner, settings.preconditioner_max_iter, settings.preconditioner_accuracy, settings.problem_type, + box_constraints, stack); CHECK(H_scaled.toDense().isApprox(H_scaled_dense)); @@ -128,11 +134,13 @@ TEST_CASE("lower part") auto l_scaled_dense = l; auto u_scaled_dense = u; + bool box_constraints(false); + proxqp::sparse::preconditioner::RuizEquilibration ruiz{ n, n_eq + n_in, 1e-3, 10, proxqp::sparse::preconditioner::Symmetry::LOWER, }; proxqp::dense::preconditioner::RuizEquilibration ruiz_dense{ - n, n_eq + n_in, 1e-3, 10, Symmetry::lower, + n, n_eq, n_in, box_constraints, 1e-3, 10, Symmetry::lower, }; VEG_MAKE_STACK(stack, ruiz.scale_qp_in_place_req( @@ -153,9 +161,11 @@ TEST_CASE("lower part") settings.preconditioner_max_iter, settings.preconditioner_accuracy, stack); - + proxqp::dense::Vec u_scaled_box(0); + proxqp::dense::Vec l_scaled_box(0); + proxqp::dense::Vec eye(0); ruiz_dense.scale_qp_in_place( - { + proxqp::dense::QpViewBoxMut{ { proxqp::from_eigen, H_scaled_dense }, { proxqp::from_eigen, g_scaled_dense }, { proxqp::from_eigen, A_scaled_dense }, @@ -163,11 +173,15 @@ TEST_CASE("lower part") { proxqp::from_eigen, C_scaled_dense }, { proxqp::from_eigen, l_scaled_dense }, { proxqp::from_eigen, u_scaled_dense }, + { proxqp::from_eigen, u_scaled_box }, + { proxqp::from_eigen, l_scaled_box }, + { proxqp::from_eigen, eye }, }, execute_preconditioner, settings.preconditioner_max_iter, settings.preconditioner_accuracy, settings.problem_type, + box_constraints, stack); CHECK(H_scaled.toDense().isApprox(H_scaled_dense)); From fb87344b9ba2de994273bb11c9c2262b7a3dd63f Mon Sep 17 00:00:00 2001 From: Bambade Date: Mon, 26 Jun 2023 10:43:09 +0200 Subject: [PATCH 02/17] fix typo --- include/proxsuite/proxqp/dense/utils.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/proxsuite/proxqp/dense/utils.hpp b/include/proxsuite/proxqp/dense/utils.hpp index 4e73739c6..1bd44fc2a 100644 --- a/include/proxsuite/proxqp/dense/utils.hpp +++ b/include/proxsuite/proxqp/dense/utils.hpp @@ -321,7 +321,12 @@ global_dual_residual_infeasibility( // entry are changed in place ruiz.unscale_dual_residual_in_place(Hdx); ruiz.unscale_primal_residual_in_place_eq(Adx); - ruiz.unscale_primal_residual_in_place_in(Cdx); + ruiz.unscale_primal_residual_in_place_in( + VectorViewMut{ from_eigen, Cdx.to_eigen().head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_primal_residual_in_place_in( + VectorViewMut{ from_eigen, Cdx.to_eigen().tail(qpmodel.dim) }); + } T gdx = (dx.to_eigen()).dot(qpwork.g_scaled); ruiz.unscale_primal_in_place(dx); From b74a853975aeb602bad7980c31477e2debe842d6 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 12:26:47 +0200 Subject: [PATCH 03/17] cmake: fix parallel test declaration --- test/CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e20a8c3bd..ebffeaa5d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,11 +42,8 @@ proxsuite_test(sparse_factorization src/sparse_factorization.cpp) proxsuite_test(cvxpy src/cvxpy.cpp) if(BUILD_WITH_OPENMP_SUPPORT) - add_executable(test-cpp-parallel src/parallel_qp_solve.cpp) - target_link_libraries(test-cpp-parallel PRIVATE doctest proxsuite) + proxsuite_test(parallel src/parallel_qp_solve.cpp) target_link_libraries(test-cpp-parallel PRIVATE OpenMP::OpenMP_CXX) - doctest_discover_tests(test-cpp-parallel) - add_dependencies(build_tests test-cpp-parallel) endif() # Test serialization From cdd1a43c55e32c94a40f623272c51be901440d15 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 12:29:24 +0200 Subject: [PATCH 04/17] test/cmake: fix missing keyword --- test/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ebffeaa5d..c6a69dc1c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,8 @@ endif() macro(proxsuite_test name path) add_executable(test-cpp-${name} ${path}) doctest_discover_tests(test-cpp-${name}) - target_link_libraries(test-cpp-${name} proxsuite doctest proxsuite-test-util) + target_link_libraries(test-cpp-${name} PUBLIC proxsuite doctest + proxsuite-test-util) target_compile_definitions(test-cpp-${name} PRIVATE PROBLEM_PATH="${CMAKE_CURRENT_SOURCE_DIR}") add_dependencies(build_tests test-cpp-${name}) From a2f87cae5da8976d35b17ef6d3943af601d7a637 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 12:34:46 +0200 Subject: [PATCH 05/17] cmake: set BUILD_WITH_OPENMP_SUPPORT to OFF by default --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 651cf0f7a..67d096f0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) From 74fd030e24e38de6ed6d972cd8cde5193b0d8aec Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 12:39:34 +0200 Subject: [PATCH 06/17] ci: fix OPENMP options --- .github/workflows/ci-linux-osx-win-conda.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-linux-osx-win-conda.yml b/.github/workflows/ci-linux-osx-win-conda.yml index fe8e1b106..de78d35ef 100644 --- a/.github/workflows/ci-linux-osx-win-conda.yml +++ b/.github/workflows/ci-linux-osx-win-conda.yml @@ -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') @@ -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=ON -DLINK_PYTHON_INTERFACE_TO_OPENMP:BOOL=OFF -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON - name: Configure [Conda/Windows-latest] if: contains(matrix.os, 'windows-latest') @@ -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=OFF -DBUILD_DOCUMENTATION:BOOL=ON -DINSTALL_DOCUMENTATION:BOOL=ON - name: Build [Conda] shell: bash -l {0} From 495b14d047fdad207cee1c50f9ce8d5e49c0df29 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 14:25:31 +0200 Subject: [PATCH 07/17] cmake: fix logic for Python test --- test/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c6a69dc1c..63e9829dc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -73,7 +73,8 @@ endif() if(BUILD_PYTHON_INTERFACE) file(GLOB_RECURSE ${PROJECT_NAME}_PYTHON_UNITTEST *.py) - if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND MSVC) + if((CMAKE_BUILD_TYPE STREQUAL "Debug" AND MSVC) OR NOT + BUILD_WITH_OPENMP_SUPPORT) list_filter("${${PROJECT_NAME}_PYTHON_UNITTEST}" "parallel_qp_solve" ${PROJECT_NAME}_PYTHON_UNITTEST) endif() From c360ab9b446d6b20bc05b28cc3b6c44bd17ba00f Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 15:29:03 +0200 Subject: [PATCH 08/17] example: fix namespace for nullopt --- examples/cpp/solve_without_api.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/cpp/solve_without_api.cpp b/examples/cpp/solve_without_api.cpp index 09d5aa0c3..67bee1a85 100644 --- a/examples/cpp/solve_without_api.cpp +++ b/examples/cpp/solve_without_api.cpp @@ -73,15 +73,15 @@ main() u, l_box, u_box, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt, - std::nullopt); + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt, + proxsuite::nullopt); // print an optimal solution x,y and z std::cout << "optimal x from dense solver: " << results_dense_solver.x << std::endl; From 4bf676ed0f14f2e33595b796f34c57e6c33665e1 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 15:50:24 +0200 Subject: [PATCH 09/17] git: switch to jcarpent JRL cmake module --- .gitmodules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitmodules b/.gitmodules index 69ac5fe4e..ae11a6d6b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,5 @@ [submodule "external/cereal"] path = external/cereal url = https://github.com/USCiLab/cereal.git +[submodule "cmake"] + url = https://github.com/jcarpent/jrl-cmakemodules.git From 9409a8b8d13d18713cde0eef0c5650d39f0e848f Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 15:50:34 +0200 Subject: [PATCH 10/17] cmake: sync submodule --- cmake-module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake-module b/cmake-module index c6cdbceb8..870b8de2b 160000 --- a/cmake-module +++ b/cmake-module @@ -1 +1 @@ -Subproject commit c6cdbceb84e26b090bd1a52c083c7c22f7924467 +Subproject commit 870b8de2b4cbf8bf9385081645ecbc74ffe05ad2 From 0567df2fd7c1175dacad88811aecb8cdef9a2eee Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 15:57:59 +0200 Subject: [PATCH 11/17] cmake: fix search of compile_pyc target --- bindings/python/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 90eacccaa..01dfcc820 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -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) @@ -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}) From d85b1d255df829b7cc9064f00c5e7e3a7295047b Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 16:07:29 +0200 Subject: [PATCH 12/17] bench: fix print --- benchmark/timings-box-constraints.cpp | 4 ++-- benchmark/timings-lp.cpp | 4 ++-- benchmark/timings-parallel.cpp | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/benchmark/timings-box-constraints.cpp b/benchmark/timings-box-constraints.cpp index 29e1b2c00..d364bd1f4 100644 --- a/benchmark/timings-box-constraints.cpp +++ b/benchmark/timings-box-constraints.cpp @@ -92,7 +92,7 @@ main(int /*argc*/, const char** /*argv*/) } } std::cout << "timings QP with box constraints feature : \t" - << elapsed_time * 1e-3 / smooth << "ms" << std::endl; + << elapsed_time * 1e-3 / smooth << " ms" << std::endl; elapsed_time = 0.0; proxqp::dense::QP qp_compare{ dim, n_eq, dim + n_in, false }; @@ -122,6 +122,6 @@ main(int /*argc*/, const char** /*argv*/) } } std::cout << "timings QP without box constraints feature : \t" - << elapsed_time * 1e-3 / smooth << "ms" << std::endl; + << elapsed_time * 1e-3 / smooth << " ms" << std::endl; } } diff --git a/benchmark/timings-lp.cpp b/benchmark/timings-lp.cpp index 8119abb00..97885b070 100644 --- a/benchmark/timings-lp.cpp +++ b/benchmark/timings-lp.cpp @@ -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; @@ -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; } } diff --git a/benchmark/timings-parallel.cpp b/benchmark/timings-parallel.cpp index 1334dc3ac..a0d03e57e 100644 --- a/benchmark/timings-parallel.cpp +++ b/benchmark/timings-parallel.cpp @@ -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++) { @@ -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> qps; @@ -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++) { @@ -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; } { @@ -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(); @@ -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; @@ -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; } } @@ -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(); @@ -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; @@ -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; } } } From 04635635544192fa74565916dbe2109cef308df0 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 26 Jun 2023 17:18:50 +0200 Subject: [PATCH 13/17] ci/windows: fix linkage to OpenMP for python targets --- .github/workflows/ci-linux-osx-win-conda.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-linux-osx-win-conda.yml b/.github/workflows/ci-linux-osx-win-conda.yml index de78d35ef..ab65fcb05 100644 --- a/.github/workflows/ci-linux-osx-win-conda.yml +++ b/.github/workflows/ci-linux-osx-win-conda.yml @@ -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 -DOpenMP_ROOT=$CONDA_PREFIX -DBUILD_WITH_OPENMP_SUPPORT:BOOL=ON -DLINK_PYTHON_INTERFACE_TO_OPENMP:BOOL=OFF -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=ON -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') @@ -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 -DOpenMP_ROOT=$CONDA_PREFIX -DBUILD_WITH_OPENMP_SUPPORT:BOOL=ON -DLINK_PYTHON_INTERFACE_TO_OPENMP:BOOL=OFF -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} From 42df1c71570cab93ab3f4ce9dfd6407a00308463 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 27 Jun 2023 11:57:03 +0200 Subject: [PATCH 14/17] cmake: use local OpenMP for Windows --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67d096f0f..723487df0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 INRIA +# Copyright (c) 2022-2023 INRIA # cmake_minimum_required(VERSION 3.10) @@ -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) From b06cfeb39123a063bcb5195245b299d34c9389e5 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 27 Jun 2023 15:43:41 +0200 Subject: [PATCH 15/17] ci/windows: deactivate OpenMP test on windows-2019 --- .github/workflows/ci-linux-osx-win-conda.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-linux-osx-win-conda.yml b/.github/workflows/ci-linux-osx-win-conda.yml index ab65fcb05..616c49181 100644 --- a/.github/workflows/ci-linux-osx-win-conda.yml +++ b/.github/workflows/ci-linux-osx-win-conda.yml @@ -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 -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 + 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') From 34a0f862da4bb19ef918d44bdec735b2b8d4878a Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 27 Jun 2023 17:18:03 +0200 Subject: [PATCH 16/17] cmake: sync submodule --- cmake-module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake-module b/cmake-module index 870b8de2b..2aa603206 160000 --- a/cmake-module +++ b/cmake-module @@ -1 +1 @@ -Subproject commit 870b8de2b4cbf8bf9385081645ecbc74ffe05ad2 +Subproject commit 2aa603206fdd263bdeb478de4d568ef79d6845ec From dc5b65071844c6cb4704af1b644eb479d0a6f881 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 27 Jun 2023 17:18:30 +0200 Subject: [PATCH 17/17] git: restore submodule cmake --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index ae11a6d6b..ce4fc4593 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,4 +8,4 @@ path = external/cereal url = https://github.com/USCiLab/cereal.git [submodule "cmake"] - url = https://github.com/jcarpent/jrl-cmakemodules.git + url = https://github.com/jrl-umi3218/jrl-cmakemodules.git