From 61b4f2977fe5f7a61d6dcab7e11b681a880a056c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 6 May 2025 11:30:05 +0200 Subject: [PATCH 01/27] wrapper.hpp: Refactorize between proxqp and osqp --- include/proxsuite/osqp/dense/dense.hpp | 13 + include/proxsuite/osqp/dense/solver.hpp | 59 ++++ include/proxsuite/osqp/dense/wrapper.hpp | 308 +++++++++++++++++++++ include/proxsuite/proxqp/dense/wrapper.hpp | 167 +++++------ include/proxsuite/solvers/common/utils.hpp | 274 ++++++++++++++++++ 5 files changed, 718 insertions(+), 103 deletions(-) create mode 100644 include/proxsuite/osqp/dense/dense.hpp create mode 100644 include/proxsuite/osqp/dense/solver.hpp create mode 100644 include/proxsuite/osqp/dense/wrapper.hpp create mode 100644 include/proxsuite/solvers/common/utils.hpp diff --git a/include/proxsuite/osqp/dense/dense.hpp b/include/proxsuite/osqp/dense/dense.hpp new file mode 100644 index 000000000..5f8c5c798 --- /dev/null +++ b/include/proxsuite/osqp/dense/dense.hpp @@ -0,0 +1,13 @@ +// +// Copyright (c) 2025 INRIA +// +/** + * @file dense.hpp + */ + +#ifndef PROXSUITE_OSQP_DENSE_DENSE_HPP +#define PROXSUITE_OSQP_DENSE_DENSE_HPP + +#include "proxsuite/osqp/dense/wrapper.hpp" + +#endif /* end of include guard PROXSUITE_OSQP_DENSE_DENSE_HPP */ \ No newline at end of file diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp new file mode 100644 index 000000000..391805855 --- /dev/null +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2025 INRIA +// +/** + * @file solver.hpp + */ + +#ifndef PROXSUITE_OSQP_DENSE_SOLVER_HPP +#define PROXSUITE_OSQP_DENSE_SOLVER_HPP + +#include +#include +#include "proxsuite/proxqp/dense/solver.hpp" +#include "proxsuite/proxqp/dense/model.hpp" +#include "proxsuite/proxqp/dense/views.hpp" +#include "proxsuite/proxqp/dense/workspace.hpp" +#include "proxsuite/proxqp/dense/utils.hpp" +#include "proxsuite/proxqp/dense/fwd.hpp" +#include "proxsuite/proxqp/dense/preconditioner/ruiz.hpp" +#include "proxsuite/proxqp/settings.hpp" +#include "proxsuite/proxqp/results.hpp" +#include + +namespace proxsuite { +namespace osqp { +namespace dense { + +using namespace proxsuite::proxqp; +using namespace proxsuite::proxqp::dense; + +/*! + * Executes the OSQP algorithm. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + */ +template +void +qp_solve( // + const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const DenseBackend& dense_backend, + const HessianType& hessian_type, + preconditioner::RuizEquilibration& ruiz) +{ +} + +} // namespace dense +} // namespace osqp +} // namespace proxsuite + +#endif /* end of include guard PROXSUITE_OSQP_DENSE_SOLVER_HPP */ \ No newline at end of file diff --git a/include/proxsuite/osqp/dense/wrapper.hpp b/include/proxsuite/osqp/dense/wrapper.hpp new file mode 100644 index 000000000..8978dec76 --- /dev/null +++ b/include/proxsuite/osqp/dense/wrapper.hpp @@ -0,0 +1,308 @@ +// +// Copyright (c) 2025 INRIA +// +/** + * @file wrapper.hpp + */ + +#ifndef PROXSUITE_OSQP_DENSE_WRAPPER_HPP +#define PROXSUITE_OSQP_DENSE_WRAPPER_HPP + +#include +#include +#include + +namespace proxsuite { +namespace osqp { +namespace dense { + +/// +/// @brief This class defines the API of OSQP solver with dense backend. +/// +template +struct QP : public proxsuite::proxqp::dense::QP +{ +public: + /*! + * Solves the QP problem using OSQP algorithm. + */ + void solve() + { + qp_solve( // + this->settings, + this->model, + this->results, + this->work, + this->get_box_constraints(), + this->get_dense_backend(), + this->get_hessian_type(), + this->ruiz); + }; + /*! + * Solves the QP problem using OSQP algorithm using a warm start. + * @param x primal warm start. + * @param y dual equality warm start. + * @param z dual inequality warm start. + */ + void solve(optional> x, + optional> y, + optional> z) + { + warm_start(x, y, z, this->results, this->settings, this->model); + qp_solve( // + this->settings, + this->model, + this->results, + this->work, + this->box_constraints, + this->dense_backend, + this->hessian_type, + this->ruiz); + }; +}; +/*! + * Solves the QP problem using OSQP 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). 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. + * @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 x primal warm start. + * @param y dual equality constraint warm start. + * @param z dual inequality constraint warm start. + * @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> 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, + bool primal_infeasibility_solving = false, + optional manual_minimal_H_eigenvalue = 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, false, DenseBackend::PrimalDualLDLT); + + return proxsuite::common::solve_without_api(Qp, + H, + g, + A, + b, + C, + l, + u, + x, + y, + z, + eps_abs, + eps_rel, + rho, + mu_eq, + mu_in, + verbose, + compute_preconditioner, + compute_timings, + max_iter, + initial_guess, + check_duality_gap, + eps_duality_gap_abs, + eps_duality_gap_rel, + primal_infeasibility_solving, + manual_minimal_H_eigenvalue); +} +/*! + * Solves the QP problem using OSQP 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, + bool primal_infeasibility_solving = false, + optional manual_minimal_H_eigenvalue = 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, DenseBackend::PrimalDualLDLT); + + return proxsuite::common::solve_without_api(Qp, + H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + x, + y, + z, + eps_abs, + eps_rel, + rho, + mu_eq, + mu_in, + verbose, + compute_preconditioner, + compute_timings, + max_iter, + initial_guess, + check_duality_gap, + eps_duality_gap_abs, + eps_duality_gap_rel, + primal_infeasibility_solving, + manual_minimal_H_eigenvalue); +} + +template +bool +operator==(const QP& qp1, const QP& qp2) +{ + return proxsuite::common::is_equal(qp1, qp2); +} + +template +bool +operator!=(const QP& qp1, const QP& qp2) +{ + return !proxsuite::common::is_equal(qp1, qp2); +} + +} // namespace dense +} // namespace osqp +} // namespace proxsuite + +#endif /* end of include guard PROXSUITE_OSQP_DENSE_WRAPPER_HPP */ \ No newline at end of file diff --git a/include/proxsuite/proxqp/dense/wrapper.hpp b/include/proxsuite/proxqp/dense/wrapper.hpp index 6e0c1a066..d27dab0db 100644 --- a/include/proxsuite/proxqp/dense/wrapper.hpp +++ b/include/proxsuite/proxqp/dense/wrapper.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace proxsuite { @@ -128,6 +129,13 @@ struct QP Workspace work; preconditioner::RuizEquilibration ruiz; + /*! + Getters + */ + DenseBackend get_dense_backend() const { return dense_backend; } + bool get_box_constraints() const { return box_constraints; } + HessianType get_hessian_type() const { return hessian_type; } + /*! * Default constructor using QP model dimensions. * @param _dim primal variable dimension. @@ -1041,49 +1049,33 @@ solve( } QP Qp(n, n_eq, n_in, false, DenseBackend::PrimalDualLDLT); - 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; - if (manual_minimal_H_eigenvalue != nullopt) { - Qp.init(H, - g, - A, - b, - C, - l, - u, - compute_preconditioner, - rho, - mu_eq, - mu_in, - manual_minimal_H_eigenvalue.value()); - } else { - Qp.init( - H, g, A, b, C, l, u, compute_preconditioner, rho, mu_eq, mu_in, nullopt); - } - Qp.solve(x, y, z); - return Qp.results; + return proxsuite::common::solve_without_api(Qp, + H, + g, + A, + b, + C, + l, + u, + x, + y, + z, + eps_abs, + eps_rel, + rho, + mu_eq, + mu_in, + verbose, + compute_preconditioner, + compute_timings, + max_iter, + initial_guess, + check_duality_gap, + eps_duality_gap_abs, + eps_duality_gap_rel, + primal_infeasibility_solving, + manual_minimal_H_eigenvalue); } /*! * Solves the QP problem using PROXQP algorithm without the need to define a QP @@ -1173,80 +1165,49 @@ solve( } QP Qp(n, n_eq, n_in, true, DenseBackend::PrimalDualLDLT); - 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; - if (manual_minimal_H_eigenvalue != nullopt) { - Qp.init(H, - g, - A, - b, - C, - l, - u, - l_box, - u_box, - compute_preconditioner, - rho, - mu_eq, - mu_in, - manual_minimal_H_eigenvalue.value()); - } else { - Qp.init(H, - g, - A, - b, - C, - l, - u, - l_box, - u_box, - compute_preconditioner, - rho, - mu_eq, - mu_in, - nullopt); - } - Qp.solve(x, y, z); - return Qp.results; + return proxsuite::common::solve_without_api(Qp, + H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + x, + y, + z, + eps_abs, + eps_rel, + rho, + mu_eq, + mu_in, + verbose, + compute_preconditioner, + compute_timings, + max_iter, + initial_guess, + check_duality_gap, + eps_duality_gap_abs, + eps_duality_gap_rel, + primal_infeasibility_solving, + manual_minimal_H_eigenvalue); } template bool operator==(const QP& qp1, const QP& qp2) { - bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && - qp1.results == qp2.results && - qp1.is_box_constrained() == qp2.is_box_constrained(); - return value; + return proxsuite::common::is_equal(qp1, qp2); } template bool operator!=(const QP& qp1, const QP& qp2) { - return !(qp1 == qp2); + return !proxsuite::common::is_equal(qp1, qp2); } ///// BatchQP object diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp new file mode 100644 index 000000000..68f7149f5 --- /dev/null +++ b/include/proxsuite/solvers/common/utils.hpp @@ -0,0 +1,274 @@ +// +// Copyright (c) 2025 INRIA +// +/** + * @file utils.hpp + */ + +#ifndef PROXSUITE_SOLVERS_COMMON_UTILS_HPP +#define PROXSUITE_SOLVERS_COMMON_UTILS_HPP + +#include "proxsuite/proxqp/results.hpp" +#include "proxsuite/proxqp/dense/fwd.hpp" +#include "proxsuite/linalg/veg/internal/typedefs.hpp" + +namespace proxsuite { +namespace common { + +namespace ppd = proxsuite::proxqp::dense; +namespace plv = proxsuite::linalg::veg; + +/*! + * Generic function to solve the QP. Used in the functions solve() to solve the + * the problem without defining the API. There are no box constraints in the + * model. + * @param Qp QP object on which the problem is solved. + * @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 x primal warm start. + * @param y dual equality constraint warm start. + * @param z dual inequality constraint warm start. + * @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_without_api(QPSolver& Qp, + optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> x, + optional> y, + optional> z, + optional eps_abs, + optional eps_rel, + optional rho, + optional mu_eq, + optional mu_in, + optional verbose, + bool compute_preconditioner, + bool compute_timings, + optional max_iter, + proxsuite::proxqp::InitialGuessStatus initial_guess, + bool check_duality_gap, + optional eps_duality_gap_abs, + optional eps_duality_gap_rel, + bool primal_infeasibility_solving, + optional manual_minimal_H_eigenvalue) +{ + 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; + if (manual_minimal_H_eigenvalue != nullopt) { + Qp.init(H, + g, + A, + b, + C, + l, + u, + compute_preconditioner, + rho, + mu_eq, + mu_in, + manual_minimal_H_eigenvalue.value()); + } else { + Qp.init( + H, g, A, b, C, l, u, compute_preconditioner, rho, mu_eq, mu_in, nullopt); + } + Qp.solve(x, y, z); + + return Qp.results; +} +/*! + * Generic function to solve the QP. Used in the functions solve() to solve the + * the problem without defining the API. There are box constraints in the model. + * @param Qp QP object on which the problem is solved. + * @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. + * @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_without_api(QPSolver& Qp, + optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> l_box, + optional> u_box, + optional> x, + optional> y, + optional> z, + optional eps_abs, + optional eps_rel, + optional rho, + optional mu_eq, + optional mu_in, + optional verbose, + bool compute_preconditioner, + bool compute_timings, + optional max_iter, + proxsuite::proxqp::InitialGuessStatus initial_guess, + bool check_duality_gap, + optional eps_duality_gap_abs, + optional eps_duality_gap_rel, + bool primal_infeasibility_solving, + optional manual_minimal_H_eigenvalue) +{ + 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; + if (manual_minimal_H_eigenvalue != nullopt) { + Qp.init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + compute_preconditioner, + rho, + mu_eq, + mu_in, + manual_minimal_H_eigenvalue.value()); + } else { + Qp.init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + compute_preconditioner, + rho, + mu_eq, + mu_in, + nullopt); + } + Qp.solve(x, y, z); + + return Qp.results; +} +/*! + * Generic function to test wether two QP objects are equal. + * @param qp1 First QP object. + * @param qp2 Second QP object. + */ +template +bool +is_equal(const QPSolver& qp1, const QPSolver& qp2) +{ + bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && + qp1.results == qp2.results && + qp1.is_box_constrained() == qp2.is_box_constrained(); + return value; +} + +} // namespace common +} // namespace proxsuite + +#endif /* end of include guard PROXSUITE_SOLVERS_COMMON_UTILS_HPP */ \ No newline at end of file From 6b070012db54e9b40fb9c11387111f4fe0878ae3 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 6 May 2025 15:03:16 +0200 Subject: [PATCH 02/27] solvers/common/utils.hpp: Renamed QPSolver into QPStruct in function solve_without_api --- include/proxsuite/solvers/common/utils.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 68f7149f5..bee07827a 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -53,9 +53,9 @@ namespace plv = proxsuite::linalg::veg; * @param eps_duality_gap_rel relative accuracy threshold for the duality-gap * criterion. */ -template +template proxqp::Results -solve_without_api(QPSolver& Qp, +solve_without_api(QPStruct& Qp, optional> H, optional> g, optional> A, @@ -164,9 +164,9 @@ solve_without_api(QPSolver& Qp, * @param eps_duality_gap_rel relative accuracy threshold for the duality-gap * criterion. */ -template +template proxqp::Results -solve_without_api(QPSolver& Qp, +solve_without_api(QPStruct& Qp, optional> H, optional> g, optional> A, @@ -258,9 +258,9 @@ solve_without_api(QPSolver& Qp, * @param qp1 First QP object. * @param qp2 Second QP object. */ -template +template bool -is_equal(const QPSolver& qp1, const QPSolver& qp2) +is_equal(const QPStruct& qp1, const QPStruct& qp2) { bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && qp1.results == qp2.results && From fd7a40f7dba857bfe565dff1deb0c19922d49a2b Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 6 May 2025 16:29:47 +0200 Subject: [PATCH 03/27] proxqp/dense/solver.hpp: Factorize the setup in the qp_solve function --- include/proxsuite/osqp/dense/solver.hpp | 3 + include/proxsuite/osqp/dense/wrapper.hpp | 2 +- include/proxsuite/osqp/utils/prints.hpp | 38 ++ include/proxsuite/proxqp/dense/solver.hpp | 279 +------- include/proxsuite/proxqp/dense/utils.hpp | 94 --- include/proxsuite/proxqp/dense/wrapper.hpp | 2 +- include/proxsuite/proxqp/utils/prints.hpp | 7 - include/proxsuite/solvers/common/utils.hpp | 680 ++++++++++++------- include/proxsuite/solvers/common/wrapper.hpp | 274 ++++++++ 9 files changed, 780 insertions(+), 599 deletions(-) create mode 100644 include/proxsuite/osqp/utils/prints.hpp create mode 100644 include/proxsuite/solvers/common/wrapper.hpp diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 391805855..bc39c5013 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -50,6 +50,9 @@ qp_solve( // const HessianType& hessian_type, preconditioner::RuizEquilibration& ruiz) { + PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + + PROXSUITE_EIGEN_MALLOC_ALLOWED(); } } // namespace dense diff --git a/include/proxsuite/osqp/dense/wrapper.hpp b/include/proxsuite/osqp/dense/wrapper.hpp index 8978dec76..e183a4211 100644 --- a/include/proxsuite/osqp/dense/wrapper.hpp +++ b/include/proxsuite/osqp/dense/wrapper.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace proxsuite { namespace osqp { diff --git a/include/proxsuite/osqp/utils/prints.hpp b/include/proxsuite/osqp/utils/prints.hpp new file mode 100644 index 000000000..1b4bc6ddf --- /dev/null +++ b/include/proxsuite/osqp/utils/prints.hpp @@ -0,0 +1,38 @@ +// +// Copyright (c) 2025 INRIA +// +/** \file */ + +#ifndef PROXSUITE_OSQP_UTILS_PRINTS_HPP +#define PROXSUITE_OSQP_UTILS_PRINTS_HPP + +#include + +namespace proxsuite { +namespace osqp { + +inline void +print_line() +{ + std::string the_line = "-----------------------------------------------------" + "--------------------------------------------\0"; + std::cout << the_line << "\n" << std::endl; +} + +inline void +print_preambule() +{ + print_line(); + std::cout << "OSQP - An operator splitting algorithm for QP programs\n" + << "(c) Paper - Bartolomeo Stellato, Goran Banjac, Paul Goulart, " + "Alberto Bemporad and Stephen Boyd\n" + << "(c) Implementation - Lucas Haubert\n" + << "Inria Paris 2025\n" + << std::endl; + print_line(); +} + +} // end namespace osqp +} // end namespace proxsuite + +#endif /* end of include guard PROXSUITE_OSQP_UTILS_PRINTS_HPP */ diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 6d428ae64..53523dc83 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -13,6 +13,7 @@ #include "proxsuite/proxqp/dense/linesearch.hpp" #include "proxsuite/proxqp/dense/helpers.hpp" #include "proxsuite/proxqp/dense/utils.hpp" +#include "proxsuite/solvers/common/utils.hpp" #include #include #include @@ -1105,276 +1106,22 @@ qp_solve( // std::cout << "test " << test << std::endl; */ PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + + proxsuite::common::setup_solver(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + dense_backend, + hessian_type, + ruiz, + common::QPSolver::PROXQP); + isize n_constraints(qpmodel.n_in); if (box_constraints) { n_constraints += qpmodel.dim; } - if (qpsettings.compute_timings) { - qpwork.timer.stop(); - qpwork.timer.start(); - } - if (qpsettings.verbose) { - dense::print_setup_header(qpsettings, - qpresults, - qpmodel, - box_constraints, - dense_backend, - hessian_type); - } - // std::cout << "qpwork.dirty " << qpwork.dirty << std::endl; - if (qpwork.dirty) { // the following is used when a solve has already been - // executed (and without any intermediary model update) - switch (qpsettings.initial_guess) { - case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { - qpwork.cleanup(box_constraints); - qpresults.cleanup(qpsettings); - break; - } - case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { - // keep solutions but restart workspace and results - 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.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(box_constraints); - qpresults.cleanup(qpsettings); - break; - } - case InitialGuessStatus::WARM_START: { - qpwork.cleanup(box_constraints); - qpresults.cold_start( - qpsettings); // because there was already a solve, - // precond was already computed if set so - ruiz.scale_primal_in_place( - { proxsuite::proxqp::from_eigen, - qpresults - .x }); // it contains the value given in entry for warm start - ruiz.scale_dual_in_place_eq( - { proxsuite::proxqp::from_eigen, qpresults.y }); - ruiz.scale_dual_in_place_in( - { 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: { - // keep workspace and results solutions except statistics - // std::cout << "i keep previous solution" << std::endl; - qpresults.cleanup_statistics(); - 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.head(qpmodel.n_in) }); - if (box_constraints) { - ruiz.scale_box_dual_in_place_in( - { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); - } - break; - } - } - if (qpsettings.initial_guess != - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT) { - switch (hessian_type) { - case HessianType::Zero: - break; - case HessianType::Dense: - qpwork.H_scaled = qpmodel.H; - break; - case HessianType::Diagonal: - qpwork.H_scaled = qpmodel.H; - break; - } - qpwork.g_scaled = qpmodel.g; - qpwork.A_scaled = qpmodel.A; - qpwork.b_scaled = qpmodel.b; - qpwork.C_scaled = qpmodel.C; - qpwork.u_scaled = qpmodel.u; - qpwork.l_scaled = qpmodel.l; - proxsuite::proxqp::dense::setup_equilibration( - qpwork, - qpsettings, - box_constraints, - hessian_type, - ruiz, - false); // reuse previous equilibration - proxsuite::proxqp::dense::setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); - } - switch (qpsettings.initial_guess) { - case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { - compute_equality_constrained_initial_guess(qpwork, - qpsettings, - qpmodel, - n_constraints, - dense_backend, - hessian_type, - qpresults); - break; - } - case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { - //!\ TODO in a quicker way - qpwork.n_c = 0; - 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, dense_backend, n_constraints, qpwork); - break; - } - case InitialGuessStatus::NO_INITIAL_GUESS: { - break; - } - case InitialGuessStatus::WARM_START: { - //!\ TODO in a quicker way - qpwork.n_c = 0; - 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, dense_backend, n_constraints, qpwork); - break; - } - case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { - // keep workspace and results solutions except statistics - // std::cout << "i use previous solution" << std::endl; - // meaningful for when one wants to warm start with previous result with - // the same QP model - break; - } - } - } else { // the following is used for a first solve after initializing or - // updating the Qp object - switch (qpsettings.initial_guess) { - case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { - proxsuite::proxqp::dense::setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); - compute_equality_constrained_initial_guess(qpwork, - qpsettings, - qpmodel, - n_constraints, - dense_backend, - hessian_type, - qpresults); - break; - } - case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { - //!\ TODO in a quicker way - ruiz.scale_primal_in_place( - { proxsuite::proxqp::from_eigen, - qpresults - .x }); // meaningful for when there is an upate of the model and - // one wants to warm start with previous result - ruiz.scale_dual_in_place_eq( - { proxsuite::proxqp::from_eigen, qpresults.y }); - ruiz.scale_dual_in_place_in( - { 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, qpresults, dense_backend, hessian_type); - qpwork.n_c = 0; - 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, dense_backend, n_constraints, qpwork); - break; - } - case InitialGuessStatus::NO_INITIAL_GUESS: { - setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); - break; - } - case InitialGuessStatus::WARM_START: { - //!\ TODO in a quicker way - 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.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, qpresults, dense_backend, hessian_type); - qpwork.n_c = 0; - 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, dense_backend, n_constraints, qpwork); - break; - } - case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { - // std::cout << "i refactorize from previous solution" << std::endl; - ruiz.scale_primal_in_place( - { proxsuite::proxqp::from_eigen, - qpresults - .x }); // meaningful for when there is an upate of the model and - // one wants to warm start with previous result - ruiz.scale_dual_in_place_eq( - { proxsuite::proxqp::from_eigen, qpresults.y }); - ruiz.scale_dual_in_place_in( - { 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, qpresults, dense_backend, hessian_type); - qpwork.n_c = 0; - 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, dense_backend, 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); diff --git a/include/proxsuite/proxqp/dense/utils.hpp b/include/proxsuite/proxqp/dense/utils.hpp index 6d9c62857..3eede862a 100644 --- a/include/proxsuite/proxqp/dense/utils.hpp +++ b/include/proxsuite/proxqp/dense/utils.hpp @@ -28,100 +28,6 @@ namespace proxsuite { namespace proxqp { namespace dense { -template -void -print_setup_header(const Settings& settings, - const Results& results, - const Model& model, - const bool box_constraints, - const DenseBackend& dense_backend, - const HessianType& hessian_type) -{ - - proxsuite::proxqp::print_preambule(); - - // Print variables and constraints - std::cout << "problem: " << std::noshowpos << std::endl; - std::cout << " variables n = " << model.dim - << ", equality constraints n_eq = " << model.n_eq << ",\n" - << " inequality constraints n_in = " << model.n_in - << std::endl; - - // Print Settings - std::cout << "settings: " << std::endl; - std::cout << " backend = dense," << std::endl; - std::cout << " eps_abs = " << settings.eps_abs - << " eps_rel = " << settings.eps_rel << std::endl; - std::cout << " eps_prim_inf = " << settings.eps_primal_inf - << ", eps_dual_inf = " << settings.eps_dual_inf << "," << std::endl; - - std::cout << " rho = " << results.info.rho - << ", mu_eq = " << results.info.mu_eq - << ", mu_in = " << results.info.mu_in << "," << std::endl; - std::cout << " max_iter = " << settings.max_iter - << ", max_iter_in = " << settings.max_iter_in << "," << std::endl; - if (box_constraints) { - std::cout << " box constraints: on, " << std::endl; - } else { - std::cout << " box constraints: off, " << std::endl; - } - switch (dense_backend) { - case DenseBackend::PrimalDualLDLT: - std::cout << " dense backend: PrimalDualLDLT, " << std::endl; - break; - case DenseBackend::PrimalLDLT: - std::cout << " dense backend: PrimalLDLT, " << std::endl; - break; - case DenseBackend::Automatic: - break; - } - switch (hessian_type) { - case HessianType::Dense: - std::cout << " problem type: Quadratic Program, " << std::endl; - break; - case HessianType::Zero: - std::cout << " problem type: Linear Program, " << std::endl; - break; - case HessianType::Diagonal: - std::cout - << " problem type: Quadratic Program with diagonal Hessian, " - << std::endl; - break; - } - if (settings.compute_preconditioner) { - std::cout << " scaling: on, " << std::endl; - } else { - std::cout << " scaling: off, " << std::endl; - } - if (settings.compute_timings) { - std::cout << " timings: on, " << std::endl; - } else { - std::cout << " timings: off, " << std::endl; - } - switch (settings.initial_guess) { - case InitialGuessStatus::WARM_START: - std::cout << " initial guess: warm start. \n" << std::endl; - break; - case InitialGuessStatus::NO_INITIAL_GUESS: - std::cout << " initial guess: no initial guess. \n" << std::endl; - break; - case InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: - std::cout - << " initial guess: warm start with previous result. \n" - << std::endl; - break; - case InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: - std::cout - << " initial guess: cold start with previous result. \n" - << std::endl; - break; - case InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: - std::cout - << " initial guess: equality constrained initial guess. \n" - << std::endl; - } -} - /*! * Save a matrix into a CSV format. Used for debug purposes. * diff --git a/include/proxsuite/proxqp/dense/wrapper.hpp b/include/proxsuite/proxqp/dense/wrapper.hpp index d27dab0db..b34384042 100644 --- a/include/proxsuite/proxqp/dense/wrapper.hpp +++ b/include/proxsuite/proxqp/dense/wrapper.hpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include namespace proxsuite { diff --git a/include/proxsuite/proxqp/utils/prints.hpp b/include/proxsuite/proxqp/utils/prints.hpp index 77984e531..6ccc3a8e9 100644 --- a/include/proxsuite/proxqp/utils/prints.hpp +++ b/include/proxsuite/proxqp/utils/prints.hpp @@ -19,13 +19,6 @@ print_line() std::cout << the_line << "\n" << std::endl; } -inline void -print_header() -{ - std::cout << "iter objective pri res dua res mu_in \n" - << std::endl; -} - inline void print_preambule() { diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index bee07827a..9d8755233 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -8,264 +8,484 @@ #ifndef PROXSUITE_SOLVERS_COMMON_UTILS_HPP #define PROXSUITE_SOLVERS_COMMON_UTILS_HPP +#include "proxsuite/proxqp/settings.hpp" #include "proxsuite/proxqp/results.hpp" -#include "proxsuite/proxqp/dense/fwd.hpp" -#include "proxsuite/linalg/veg/internal/typedefs.hpp" +#include "proxsuite/proxqp/dense/model.hpp" +#include "proxsuite/proxqp/dense/workspace.hpp" +#include "proxsuite/proxqp/dense/preconditioner/ruiz.hpp" +#include "proxsuite/proxqp/dense/helpers.hpp" +#include "proxsuite/proxqp/dense/linesearch.hpp" +#include +#include +#include namespace proxsuite { namespace common { +namespace pp = proxsuite::proxqp; namespace ppd = proxsuite::proxqp::dense; +namespace ppdp = proxsuite::proxqp::dense::preconditioner; namespace plv = proxsuite::linalg::veg; +/// +/// @brief This enum defines the different solvers implemented in ProxSuite. +/// +enum class QPSolver +{ + PROXQP, + OSQP +}; /*! - * Generic function to solve the QP. Used in the functions solve() to solve the - * the problem without defining the API. There are no box constraints in the - * model. - * @param Qp QP object on which the problem is solved. - * @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 x primal warm start. - * @param y dual equality constraint warm start. - * @param z dual inequality constraint warm start. - * @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. + * Prints the setup header. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + * @param qp_solver PROXQP or OSQP. */ -template -proxqp::Results -solve_without_api(QPStruct& Qp, - optional> H, - optional> g, - optional> A, - optional> b, - optional> C, - optional> l, - optional> u, - optional> x, - optional> y, - optional> z, - optional eps_abs, - optional eps_rel, - optional rho, - optional mu_eq, - optional mu_in, - optional verbose, - bool compute_preconditioner, - bool compute_timings, - optional max_iter, - proxsuite::proxqp::InitialGuessStatus initial_guess, - bool check_duality_gap, - optional eps_duality_gap_abs, - optional eps_duality_gap_rel, - bool primal_infeasibility_solving, - optional manual_minimal_H_eigenvalue) +template +void +print_setup_header(const pp::Settings& settings, + const pp::Results& results, + const ppd::Model& model, + const bool box_constraints, + const pp::DenseBackend& dense_backend, + const pp::HessianType& hessian_type, + const common::QPSolver qp_solver) { - 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(); + switch (qp_solver) { + case common::QPSolver::PROXQP: + proxsuite::proxqp::print_preambule(); + break; + case common::QPSolver::OSQP: + proxsuite::osqp::print_preambule(); + break; } - if (eps_rel != nullopt) { - Qp.settings.eps_rel = eps_rel.value(); + + // Print variables and constraints + std::cout << "problem: " << std::noshowpos << std::endl; + std::cout << " variables n = " << model.dim + << ", equality constraints n_eq = " << model.n_eq << ",\n" + << " inequality constraints n_in = " << model.n_in + << std::endl; + + // Print Settings + std::cout << "settings: " << std::endl; + std::cout << " backend = dense," << std::endl; + std::cout << " eps_abs = " << settings.eps_abs + << " eps_rel = " << settings.eps_rel << std::endl; + std::cout << " eps_prim_inf = " << settings.eps_primal_inf + << ", eps_dual_inf = " << settings.eps_dual_inf << "," << std::endl; + + std::cout << " rho = " << results.info.rho + << ", mu_eq = " << results.info.mu_eq + << ", mu_in = " << results.info.mu_in << "," << std::endl; + switch (qp_solver) { + case common::QPSolver::PROXQP: + std::cout << " max_iter = " << settings.max_iter + << ", max_iter_in = " << settings.max_iter_in << "," + << std::endl; + break; + case common::QPSolver::OSQP: + std::cout << " max_iter = " << settings.max_iter << std::endl; + break; } - if (verbose != nullopt) { - Qp.settings.verbose = verbose.value(); + if (box_constraints) { + std::cout << " box constraints: on, " << std::endl; + } else { + std::cout << " box constraints: off, " << std::endl; } - if (max_iter != nullopt) { - Qp.settings.max_iter = max_iter.value(); + switch (dense_backend) { + case pp::DenseBackend::PrimalDualLDLT: + std::cout << " dense backend: PrimalDualLDLT, " << std::endl; + break; + case pp::DenseBackend::PrimalLDLT: + std::cout << " dense backend: PrimalLDLT, " << std::endl; + break; + case pp::DenseBackend::Automatic: + break; } - if (eps_duality_gap_abs != nullopt) { - Qp.settings.eps_duality_gap_abs = eps_duality_gap_abs.value(); + switch (hessian_type) { + case pp::HessianType::Dense: + std::cout << " problem type: Quadratic Program, " << std::endl; + break; + case pp::HessianType::Zero: + std::cout << " problem type: Linear Program, " << std::endl; + break; + case pp::HessianType::Diagonal: + std::cout + << " problem type: Quadratic Program with diagonal Hessian, " + << std::endl; + break; } - if (eps_duality_gap_rel != nullopt) { - Qp.settings.eps_duality_gap_rel = eps_duality_gap_rel.value(); + if (settings.compute_preconditioner) { + std::cout << " scaling: on, " << std::endl; + } else { + std::cout << " scaling: off, " << std::endl; } - Qp.settings.compute_timings = compute_timings; - Qp.settings.primal_infeasibility_solving = primal_infeasibility_solving; - if (manual_minimal_H_eigenvalue != nullopt) { - Qp.init(H, - g, - A, - b, - C, - l, - u, - compute_preconditioner, - rho, - mu_eq, - mu_in, - manual_minimal_H_eigenvalue.value()); + if (settings.compute_timings) { + std::cout << " timings: on, " << std::endl; } else { - Qp.init( - H, g, A, b, C, l, u, compute_preconditioner, rho, mu_eq, mu_in, nullopt); + std::cout << " timings: off, " << std::endl; + } + switch (settings.initial_guess) { + case pp::InitialGuessStatus::WARM_START: + std::cout << " initial guess: warm start. \n" << std::endl; + break; + case pp::InitialGuessStatus::NO_INITIAL_GUESS: + std::cout << " initial guess: no initial guess. \n" << std::endl; + break; + case pp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: + std::cout + << " initial guess: warm start with previous result. \n" + << std::endl; + break; + case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: + std::cout + << " initial guess: cold start with previous result. \n" + << std::endl; + break; + case pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: + std::cout + << " initial guess: equality constrained initial guess. \n" + << std::endl; } - Qp.solve(x, y, z); - - return Qp.results; } /*! - * Generic function to solve the QP. Used in the functions solve() to solve the - * the problem without defining the API. There are box constraints in the model. - * @param Qp QP object on which the problem is solved. - * @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. - * @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. + * Setups the solver. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + * @param qp_solver PROXQP or OSQP. */ -template -proxqp::Results -solve_without_api(QPStruct& Qp, - optional> H, - optional> g, - optional> A, - optional> b, - optional> C, - optional> l, - optional> u, - optional> l_box, - optional> u_box, - optional> x, - optional> y, - optional> z, - optional eps_abs, - optional eps_rel, - optional rho, - optional mu_eq, - optional mu_in, - optional verbose, - bool compute_preconditioner, - bool compute_timings, - optional max_iter, - proxsuite::proxqp::InitialGuessStatus initial_guess, - bool check_duality_gap, - optional eps_duality_gap_abs, - optional eps_duality_gap_rel, - bool primal_infeasibility_solving, - optional manual_minimal_H_eigenvalue) +template +void +setup_solver(const pp::Settings& qpsettings, + const ppd::Model& qpmodel, + pp::Results& qpresults, + ppd::Workspace& qpwork, + const bool box_constraints, + const pp::DenseBackend& dense_backend, + const pp::HessianType& hessian_type, + ppdp::RuizEquilibration& ruiz, + QPSolver qp_solver) { - 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(); + plv::isize n_constraints(qpmodel.n_in); + if (box_constraints) { + n_constraints += qpmodel.dim; } - if (eps_rel != nullopt) { - Qp.settings.eps_rel = eps_rel.value(); + if (qpsettings.compute_timings) { + qpwork.timer.stop(); + qpwork.timer.start(); } - if (verbose != nullopt) { - Qp.settings.verbose = verbose.value(); + if (qpsettings.verbose) { + print_setup_header(qpsettings, + qpresults, + qpmodel, + box_constraints, + dense_backend, + hessian_type, + qp_solver); } - if (max_iter != nullopt) { - Qp.settings.max_iter = max_iter.value(); + if (qpwork.dirty) { // the following is used when a solve has already been + // executed (and without any intermediary model update) + switch (qpsettings.initial_guess) { + case pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { + qpwork.cleanup(box_constraints); + qpresults.cleanup(qpsettings); + break; + } + case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { + // keep solutions but restart workspace and results + 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.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 pp::InitialGuessStatus::NO_INITIAL_GUESS: { + qpwork.cleanup(box_constraints); + qpresults.cleanup(qpsettings); + break; + } + case pp::InitialGuessStatus::WARM_START: { + qpwork.cleanup(box_constraints); + qpresults.cold_start( + qpsettings); // because there was already a solve, + // precond was already computed if set so + ruiz.scale_primal_in_place( + { proxsuite::proxqp::from_eigen, + qpresults + .x }); // it contains the value given in entry for warm start + ruiz.scale_dual_in_place_eq( + { proxsuite::proxqp::from_eigen, qpresults.y }); + ruiz.scale_dual_in_place_in( + { 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 pp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { + // keep workspace and results solutions except statistics + // std::cout << "i keep previous solution" << std::endl; + qpresults.cleanup_statistics(); + 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.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } + break; + } + } + if (qpsettings.initial_guess != + pp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT) { + switch (hessian_type) { + case pp::HessianType::Zero: + break; + case pp::HessianType::Dense: + qpwork.H_scaled = qpmodel.H; + break; + case pp::HessianType::Diagonal: + qpwork.H_scaled = qpmodel.H; + break; + } + qpwork.g_scaled = qpmodel.g; + qpwork.A_scaled = qpmodel.A; + qpwork.b_scaled = qpmodel.b; + qpwork.C_scaled = qpmodel.C; + qpwork.u_scaled = qpmodel.u; + qpwork.l_scaled = qpmodel.l; + proxsuite::proxqp::dense::setup_equilibration( + qpwork, + qpsettings, + box_constraints, + hessian_type, + ruiz, + false); // reuse previous equilibration + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + } + switch (qpsettings.initial_guess) { + case pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { + compute_equality_constrained_initial_guess(qpwork, + qpsettings, + qpmodel, + n_constraints, + dense_backend, + hessian_type, + qpresults); + break; + } + case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { + switch (qp_solver) { + case common::QPSolver::PROXQP: { + //!\ TODO in a quicker way + qpwork.n_c = 0; + for (plv::isize i = 0; i < n_constraints; i++) { + if (qpresults.z[i] != 0) { + qpwork.active_inequalities[i] = true; + } else { + qpwork.active_inequalities[i] = false; + } + } + ppd::linesearch::active_set_change( + qpmodel, qpresults, dense_backend, n_constraints, qpwork); + } break; + case common::QPSolver::OSQP: { + // TODO: Call for function to build the full KKT + } break; + } + break; + } + case pp::InitialGuessStatus::NO_INITIAL_GUESS: { + break; + } + case pp::InitialGuessStatus::WARM_START: { + switch (qp_solver) { + case common::QPSolver::PROXQP: { + //!\ TODO in a quicker way + qpwork.n_c = 0; + for (plv::isize i = 0; i < n_constraints; i++) { + if (qpresults.z[i] != 0) { + qpwork.active_inequalities[i] = true; + } else { + qpwork.active_inequalities[i] = false; + } + } + ppd::linesearch::active_set_change( + qpmodel, qpresults, dense_backend, n_constraints, qpwork); + } break; + case common::QPSolver::OSQP: { + // TODO: Call for function to build the full KKT + } break; + } + break; + } + case pp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { + // keep workspace and results solutions except statistics + // std::cout << "i use previous solution" << std::endl; + // meaningful for when one wants to warm start with previous result with + // the same QP model + break; + } + } + } else { // the following is used for a first solve after initializing or + // updating the Qp object + switch (qpsettings.initial_guess) { + case pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + compute_equality_constrained_initial_guess(qpwork, + qpsettings, + qpmodel, + n_constraints, + dense_backend, + hessian_type, + qpresults); + break; + } + case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { + //!\ TODO in a quicker way + ruiz.scale_primal_in_place( + { proxsuite::proxqp::from_eigen, + qpresults + .x }); // meaningful for when there is an upate of the model and + // one wants to warm start with previous result + ruiz.scale_dual_in_place_eq( + { proxsuite::proxqp::from_eigen, qpresults.y }); + ruiz.scale_dual_in_place_in( + { 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, qpresults, dense_backend, hessian_type); + switch (qp_solver) { + case common::QPSolver::PROXQP: { + //!\ TODO in a quicker way + qpwork.n_c = 0; + for (plv::isize i = 0; i < n_constraints; i++) { + if (qpresults.z[i] != 0) { + qpwork.active_inequalities[i] = true; + } else { + qpwork.active_inequalities[i] = false; + } + } + ppd::linesearch::active_set_change( + qpmodel, qpresults, dense_backend, n_constraints, qpwork); + } break; + case common::QPSolver::OSQP: { + // TODO: Call for function to build the full KKT + } break; + } + break; + } + case pp::InitialGuessStatus::NO_INITIAL_GUESS: { + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + break; + } + case pp::InitialGuessStatus::WARM_START: { + //!\ TODO in a quicker way + 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.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, qpresults, dense_backend, hessian_type); + switch (qp_solver) { + case common::QPSolver::PROXQP: { + //!\ TODO in a quicker way + qpwork.n_c = 0; + for (plv::isize i = 0; i < n_constraints; i++) { + if (qpresults.z[i] != 0) { + qpwork.active_inequalities[i] = true; + } else { + qpwork.active_inequalities[i] = false; + } + } + ppd::linesearch::active_set_change( + qpmodel, qpresults, dense_backend, n_constraints, qpwork); + } break; + case common::QPSolver::OSQP: { + // TODO: Call for function to build the full KKT + } break; + } + break; + } + case pp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT: { + // std::cout << "i refactorize from previous solution" << std::endl; + ruiz.scale_primal_in_place( + { proxsuite::proxqp::from_eigen, + qpresults + .x }); // meaningful for when there is an upate of the model and + // one wants to warm start with previous result + ruiz.scale_dual_in_place_eq( + { proxsuite::proxqp::from_eigen, qpresults.y }); + ruiz.scale_dual_in_place_in( + { 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, qpresults, dense_backend, hessian_type); + switch (qp_solver) { + case common::QPSolver::PROXQP: { + //!\ TODO in a quicker way + qpwork.n_c = 0; + for (plv::isize i = 0; i < n_constraints; i++) { + if (qpresults.z[i] != 0) { + qpwork.active_inequalities[i] = true; + } else { + qpwork.active_inequalities[i] = false; + } + } + ppd::linesearch::active_set_change( + qpmodel, qpresults, dense_backend, n_constraints, qpwork); + } break; + case common::QPSolver::OSQP: { + // TODO: Call for function to build the full KKT + } break; + } + break; + } + } + } } - 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; - if (manual_minimal_H_eigenvalue != nullopt) { - Qp.init(H, - g, - A, - b, - C, - l, - u, - l_box, - u_box, - compute_preconditioner, - rho, - mu_eq, - mu_in, - manual_minimal_H_eigenvalue.value()); - } else { - Qp.init(H, - g, - A, - b, - C, - l, - u, - l_box, - u_box, - compute_preconditioner, - rho, - mu_eq, - mu_in, - nullopt); - } - Qp.solve(x, y, z); - - return Qp.results; -} -/*! - * Generic function to test wether two QP objects are equal. - * @param qp1 First QP object. - * @param qp2 Second QP object. - */ -template -bool -is_equal(const QPStruct& qp1, const QPStruct& qp2) -{ - bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && - qp1.results == qp2.results && - qp1.is_box_constrained() == qp2.is_box_constrained(); - return value; } } // namespace common diff --git a/include/proxsuite/solvers/common/wrapper.hpp b/include/proxsuite/solvers/common/wrapper.hpp new file mode 100644 index 000000000..6e74c55e5 --- /dev/null +++ b/include/proxsuite/solvers/common/wrapper.hpp @@ -0,0 +1,274 @@ +// +// Copyright (c) 2025 INRIA +// +/** + * @file utils.hpp + */ + +#ifndef PROXSUITE_SOLVERS_COMMON_WRAPPER_HPP +#define PROXSUITE_SOLVERS_COMMON_WRAPPER_HPP + +#include "proxsuite/proxqp/results.hpp" +#include "proxsuite/proxqp/dense/fwd.hpp" +#include "proxsuite/linalg/veg/internal/typedefs.hpp" + +namespace proxsuite { +namespace common { + +namespace ppd = proxsuite::proxqp::dense; +namespace plv = proxsuite::linalg::veg; + +/*! + * Generic function to solve the QP. Used in the functions solve() to solve the + * the problem without defining the API. There are no box constraints in the + * model. + * @param Qp QP object on which the problem is solved. + * @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 x primal warm start. + * @param y dual equality constraint warm start. + * @param z dual inequality constraint warm start. + * @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_without_api(QPStruct& Qp, + optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> x, + optional> y, + optional> z, + optional eps_abs, + optional eps_rel, + optional rho, + optional mu_eq, + optional mu_in, + optional verbose, + bool compute_preconditioner, + bool compute_timings, + optional max_iter, + proxsuite::proxqp::InitialGuessStatus initial_guess, + bool check_duality_gap, + optional eps_duality_gap_abs, + optional eps_duality_gap_rel, + bool primal_infeasibility_solving, + optional manual_minimal_H_eigenvalue) +{ + 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; + if (manual_minimal_H_eigenvalue != nullopt) { + Qp.init(H, + g, + A, + b, + C, + l, + u, + compute_preconditioner, + rho, + mu_eq, + mu_in, + manual_minimal_H_eigenvalue.value()); + } else { + Qp.init( + H, g, A, b, C, l, u, compute_preconditioner, rho, mu_eq, mu_in, nullopt); + } + Qp.solve(x, y, z); + + return Qp.results; +} +/*! + * Generic function to solve the QP. Used in the functions solve() to solve the + * the problem without defining the API. There are box constraints in the model. + * @param Qp QP object on which the problem is solved. + * @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. + * @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_without_api(QPStruct& Qp, + optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + optional> l_box, + optional> u_box, + optional> x, + optional> y, + optional> z, + optional eps_abs, + optional eps_rel, + optional rho, + optional mu_eq, + optional mu_in, + optional verbose, + bool compute_preconditioner, + bool compute_timings, + optional max_iter, + proxsuite::proxqp::InitialGuessStatus initial_guess, + bool check_duality_gap, + optional eps_duality_gap_abs, + optional eps_duality_gap_rel, + bool primal_infeasibility_solving, + optional manual_minimal_H_eigenvalue) +{ + 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.settings.primal_infeasibility_solving = primal_infeasibility_solving; + if (manual_minimal_H_eigenvalue != nullopt) { + Qp.init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + compute_preconditioner, + rho, + mu_eq, + mu_in, + manual_minimal_H_eigenvalue.value()); + } else { + Qp.init(H, + g, + A, + b, + C, + l, + u, + l_box, + u_box, + compute_preconditioner, + rho, + mu_eq, + mu_in, + nullopt); + } + Qp.solve(x, y, z); + + return Qp.results; +} +/*! + * Generic function to test wether two QP objects are equal. + * @param qp1 First QP object. + * @param qp2 Second QP object. + */ +template +bool +is_equal(const QPStruct& qp1, const QPStruct& qp2) +{ + bool value = qp1.model == qp2.model && qp1.settings == qp2.settings && + qp1.results == qp2.results && + qp1.is_box_constrained() == qp2.is_box_constrained(); + return value; +} + +} // namespace common +} // namespace proxsuite + +#endif /* end of include guard PROXSUITE_SOLVERS_COMMON_WRAPPER_HPP */ \ No newline at end of file From 08a1ab002c406c027e16b6542fd499e7a5bb25a6 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 6 May 2025 18:23:19 +0200 Subject: [PATCH 04/27] proxqp/dense/solver.hpp: Factorize the compute_feasibility function --- include/proxsuite/osqp/dense/solver.hpp | 60 +++++++ include/proxsuite/proxqp/dense/solver.hpp | 138 +++------------- include/proxsuite/solvers/common/utils.hpp | 184 +++++++++++++++++++++ 3 files changed, 271 insertions(+), 111 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index bc39c5013..73f7dea46 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -19,6 +19,7 @@ #include "proxsuite/proxqp/dense/preconditioner/ruiz.hpp" #include "proxsuite/proxqp/settings.hpp" #include "proxsuite/proxqp/results.hpp" +#include "proxsuite/solvers/common/utils.hpp" #include namespace proxsuite { @@ -52,6 +53,65 @@ qp_solve( // { PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + proxsuite::common::setup_solver(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + dense_backend, + hessian_type, + ruiz, + common::QPSolver::OSQP); + + isize n_constraints(qpmodel.n_in); + if (box_constraints) { + n_constraints += qpmodel.dim; + } + + T primal_feasibility_eq_rhs_0(0); + T primal_feasibility_in_rhs_0(0); + T dual_feasibility_rhs_0(0); + T dual_feasibility_rhs_1(0); + T dual_feasibility_rhs_3(0); + T primal_feasibility_lhs(0); + T primal_feasibility_eq_lhs(0); + T primal_feasibility_in_lhs(0); + T dual_feasibility_lhs(0); + + T duality_gap(0); + T rhs_duality_gap(0); + T scaled_eps(qpsettings.eps_abs); + + for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { + + bool stop_loop = false; + proxsuite::common::compute_feasibility(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + common::QPSolver::OSQP, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + iter, + stop_loop); + if (stop_loop) { + break; + } + } + PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 53523dc83..e4442af1c 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1143,122 +1143,38 @@ qp_solve( // for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { - // compute primal residual - - // PERF: fuse matrix product computations in global_{primal, dual}_residual - global_primal_residual(qpmodel, - qpresults, - qpsettings, - qpwork, - ruiz, - box_constraints, - primal_feasibility_lhs, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs); - - global_dual_residual(qpresults, - qpwork, - qpmodel, - box_constraints, - ruiz, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - hessian_type); - - qpresults.info.pri_res = primal_feasibility_lhs; - qpresults.info.dua_res = dual_feasibility_lhs; - qpresults.info.duality_gap = duality_gap; - T new_bcl_mu_in(qpresults.info.mu_in); T new_bcl_mu_eq(qpresults.info.mu_eq); T new_bcl_mu_in_inv(qpresults.info.mu_in_inv); T new_bcl_mu_eq_inv(qpresults.info.mu_eq_inv); - T rhs_pri(scaled_eps); - if (qpsettings.eps_rel != 0) { - rhs_pri += qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0); - } - bool is_primal_feasible = primal_feasibility_lhs <= rhs_pri; - - T rhs_dua(qpsettings.eps_abs); - if (qpsettings.eps_rel != 0) { - rhs_dua += - qpsettings.eps_rel * - std::max( - std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), - std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2)); + bool stop_loop = false; + proxsuite::common::compute_feasibility(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + common::QPSolver::PROXQP, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + iter, + stop_loop); + if (stop_loop) { + break; } - bool is_dual_feasible = dual_feasibility_lhs <= rhs_dua; - - if (qpsettings.verbose) { - - 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.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; - for (Eigen::Index j = 0; j < qpmodel.dim; ++j) { - qpresults.info.objValue += - 0.5 * (qpresults.x(j) * qpresults.x(j)) * qpmodel.H(j, j); - qpresults.info.objValue += - qpresults.x(j) * T(qpmodel.H.col(j) - .tail(qpmodel.dim - j - 1) - .dot(qpresults.x.tail(qpmodel.dim - j - 1))); - } - qpresults.info.objValue += (qpmodel.g).dot(qpresults.x); - } - std::cout << "\033[1;32m[outer iteration " << iter + 1 << "]\033[0m" - << std::endl; - std::cout << std::scientific << std::setw(2) << std::setprecision(2) - << "| primal residual=" << qpresults.info.pri_res - << " | dual residual=" << qpresults.info.dua_res - << " | duality gap=" << qpresults.info.duality_gap - << " | mu_in=" << qpresults.info.mu_in - << " | 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.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) { - if (std::fabs(qpresults.info.duality_gap) <= - qpsettings.eps_duality_gap_abs + - qpsettings.eps_duality_gap_rel * rhs_duality_gap) { - if (qpsettings.primal_infeasibility_solving && - qpresults.info.status == - QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { - qpresults.info.status = - QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; - } else { - qpresults.info.status = QPSolverOutput::PROXQP_SOLVED; - } - break; - } - } else { - qpresults.info.status = QPSolverOutput::PROXQP_SOLVED; - break; - } - } qpresults.info.iter_ext += 1; // We start a new external loop update qpwork.x_prev = qpresults.x; @@ -1354,7 +1270,7 @@ qp_solve( // primal_feasibility_eq_lhs, primal_feasibility_in_lhs); - is_primal_feasible = + bool is_primal_feasible = primal_feasibility_lhs_new <= (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0)); @@ -1377,7 +1293,7 @@ qp_solve( // qpresults.info.dua_res = dual_feasibility_lhs_new; qpresults.info.duality_gap = duality_gap; - is_dual_feasible = + bool is_dual_feasible = dual_feasibility_lhs_new <= (qpsettings.eps_abs + qpsettings.eps_rel * diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 9d8755233..b3b8b4127 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -15,9 +15,11 @@ #include "proxsuite/proxqp/dense/preconditioner/ruiz.hpp" #include "proxsuite/proxqp/dense/helpers.hpp" #include "proxsuite/proxqp/dense/linesearch.hpp" +#include "proxsuite/proxqp/dense/utils.hpp" #include #include #include +#include namespace proxsuite { namespace common { @@ -157,6 +159,10 @@ print_setup_header(const pp::Settings& settings, } /*! * Setups the solver. + * In particular, it scales (Ruiz equilibration) the data, then + * builds the KKT matrix according to the algorihm, eg: + * proxqp: Builds the KKT with equality and activate inequality constraints + * osqp: Builds the KKT with all of the constraints. * * @param qpwork solver workspace. * @param qpmodel QP problem model as defined by the user (without any scaling @@ -487,6 +493,184 @@ setup_solver(const pp::Settings& qpsettings, } } } +/*! + * Computes the objective function. + * + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solver results. + */ +template +void +compute_objective(const ppd::Model& qpmodel, pp::Results& qpresults) +{ + qpresults.info.objValue = 0; + for (Eigen::Index j = 0; j < qpmodel.dim; ++j) { + qpresults.info.objValue += + 0.5 * (qpresults.x(j) * qpresults.x(j)) * qpmodel.H(j, j); + qpresults.info.objValue += + qpresults.x(j) * T(qpmodel.H.col(j) + .tail(qpmodel.dim - j - 1) + .dot(qpresults.x.tail(qpmodel.dim - j - 1))); + } + qpresults.info.objValue += (qpmodel.g).dot(qpresults.x); +} +/*! + * Computes the residuals and the feasibility of the problem, then update it + * and stops the algorithm if needed. + * + * @param qpsettings solver settings. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solver results. + * @param qpwork solver workspace. + * @param ruiz ruiz preconditioner. + * @param qp_solver PROXQP or OSQP. + */ +template +void +compute_feasibility( // + const pp::Settings& qpsettings, + const ppd::Model& qpmodel, + pp::Results& qpresults, + ppd::Workspace& qpwork, + const bool box_constraints, + const pp::HessianType& hessian_type, + ppd::preconditioner::RuizEquilibration& ruiz, + QPSolver qp_solver, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& primal_feasibility_lhs, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap, + T& scaled_eps, + plv::i64 iter, + bool& stop_loop) +{ + + ppd::global_primal_residual(qpmodel, + qpresults, + qpsettings, + qpwork, + ruiz, + box_constraints, + primal_feasibility_lhs, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs); + + ppd::global_dual_residual(qpresults, + qpwork, + qpmodel, + box_constraints, + ruiz, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + hessian_type); + + qpresults.info.pri_res = primal_feasibility_lhs; + qpresults.info.dua_res = dual_feasibility_lhs; + qpresults.info.duality_gap = duality_gap; + + T rhs_pri(scaled_eps); + if (qpsettings.eps_rel != 0) { + rhs_pri += qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0); + } + bool is_primal_feasible = primal_feasibility_lhs <= rhs_pri; + + T rhs_dua(qpsettings.eps_abs); + if (qpsettings.eps_rel != 0) { + rhs_dua += + qpsettings.eps_rel * + std::max(std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), + std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2)); + } + bool is_dual_feasible = dual_feasibility_lhs <= rhs_dua; + + if (qpsettings.verbose) { + ruiz.unscale_primal_in_place( + pp::VectorViewMut{ pp::from_eigen, qpresults.x }); + ruiz.unscale_dual_in_place_eq( + pp::VectorViewMut{ pp::from_eigen, qpresults.y }); + ruiz.unscale_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } + + compute_objective(qpmodel, qpresults); + + std::cout << "\033[1;32m[outer iteration " << iter + 1 << "]\033[0m" + << std::endl; + + switch (qp_solver) { + case common::QPSolver::PROXQP: { + std::cout << std::scientific << std::setw(2) << std::setprecision(2) + << " | primal residual=" << qpresults.info.pri_res + << " | dual residual=" << qpresults.info.dua_res + << " | duality gap=" << qpresults.info.duality_gap + << " | mu_in=" << qpresults.info.mu_in + << " | rho=" << qpresults.info.rho << std::endl; + break; + case common::QPSolver::OSQP: { + std::cout << std::scientific << std::setw(2) << std::setprecision(2) + << " | primal residual=" << qpresults.info.pri_res + << " | dual residual=" << qpresults.info.dua_res + << " | duality gap=" << qpresults.info.duality_gap + << " | rho=" << qpresults.info.rho + << " | mu_in=" << qpresults.info.mu_in + << " | mu_eq=" << qpresults.info.mu_eq << std::endl; + break; + } + } + } + + ruiz.scale_primal_in_place( + pp::VectorViewMut{ pp::from_eigen, qpresults.x }); + ruiz.scale_dual_in_place_eq( + pp::VectorViewMut{ pp::from_eigen, qpresults.y }); + ruiz.scale_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.scale_box_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } + } + + if (is_primal_feasible && is_dual_feasible) { + if (qpsettings.check_duality_gap) { + if (std::fabs(qpresults.info.duality_gap) <= + qpsettings.eps_duality_gap_abs + + qpsettings.eps_duality_gap_rel * rhs_duality_gap) { + if (qpsettings.primal_infeasibility_solving && + qpresults.info.status == + pp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { + qpresults.info.status = + pp::QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; + } else { + qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; + } + stop_loop = true; + } + } else { + qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; + stop_loop = true; + } + } +} } // namespace common } // namespace proxsuite From 39240eefd1d099596937abf3ac37784a77e4975c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 6 May 2025 18:51:15 +0200 Subject: [PATCH 05/27] proxqp/dense/solver.hpp: Factorize the update_solver_status function --- include/proxsuite/osqp/dense/solver.hpp | 6 ++ include/proxsuite/proxqp/dense/solver.hpp | 96 ++++++------------- include/proxsuite/solvers/common/utils.hpp | 105 ++++++++++++++++++++- 3 files changed, 137 insertions(+), 70 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 73f7dea46..c67ff1a42 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -112,6 +112,12 @@ qp_solve( // } } + qpresults.info.iter_ext += 1; + + qpwork.x_prev = qpresults.x; + qpwork.y_prev = qpresults.y; + qpwork.z_prev = qpresults.z; + PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index e4442af1c..461a2b305 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1181,6 +1181,8 @@ qp_solve( // qpwork.y_prev = qpresults.y; qpwork.z_prev = qpresults.z; + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // primal dual version from gill and robinson ruiz.scale_primal_residual_in_place_in( @@ -1221,6 +1223,8 @@ qp_solve( // qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + primal_dual_newton_semi_smooth(qpsettings, qpmodel, qpresults, @@ -1256,77 +1260,31 @@ qp_solve( // scaled_eps = infty_norm(qpwork.rhs.head(qpmodel.dim)) * qpsettings.eps_abs; } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + T primal_feasibility_lhs_new(primal_feasibility_lhs); + proxsuite::common::update_solver_status(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - global_primal_residual(qpmodel, - qpresults, - qpsettings, - qpwork, - ruiz, - box_constraints, - primal_feasibility_lhs_new, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs); - - bool is_primal_feasible = - primal_feasibility_lhs_new <= - (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0)); - qpresults.info.pri_res = primal_feasibility_lhs_new; - if (is_primal_feasible) { - T dual_feasibility_lhs_new(dual_feasibility_lhs); - - global_dual_residual(qpresults, - qpwork, - qpmodel, - box_constraints, - ruiz, - dual_feasibility_lhs_new, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - hessian_type); - qpresults.info.dua_res = dual_feasibility_lhs_new; - qpresults.info.duality_gap = duality_gap; - - bool is_dual_feasible = - dual_feasibility_lhs_new <= - (qpsettings.eps_abs + - qpsettings.eps_rel * - std::max( - std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), - std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2))); - - if (is_dual_feasible) { - if (qpsettings.check_duality_gap) { - if (std::fabs(qpresults.info.duality_gap) <= - qpsettings.eps_duality_gap_abs + - qpsettings.eps_duality_gap_rel * rhs_duality_gap) { - if (qpsettings.primal_infeasibility_solving && - qpresults.info.status == - QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { - qpresults.info.status = - QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; - } else { - qpresults.info.status = QPSolverOutput::PROXQP_SOLVED; - } - } - } else { - if (qpsettings.primal_infeasibility_solving && - qpresults.info.status == - QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { - qpresults.info.status = - QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; - } else { - qpresults.info.status = QPSolverOutput::PROXQP_SOLVED; - } - } - } - } if (qpsettings.bcl_update) { bcl_update(qpsettings, qpresults, diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index b3b8b4127..c91d4c491 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -536,7 +536,7 @@ compute_feasibility( // ppd::Workspace& qpwork, const bool box_constraints, const pp::HessianType& hessian_type, - ppd::preconditioner::RuizEquilibration& ruiz, + ppdp::RuizEquilibration& ruiz, QPSolver qp_solver, T& primal_feasibility_eq_rhs_0, T& primal_feasibility_in_rhs_0, @@ -671,6 +671,109 @@ compute_feasibility( // } } } +/*! + * Computes residuals, the infeasibility and updates the solver's status. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + */ +template +void +update_solver_status( // + const pp::Settings& qpsettings, + const ppd::Model& qpmodel, + pp::Results& qpresults, + ppd::Workspace& qpwork, + const bool box_constraints, + const pp::HessianType& hessian_type, + ppdp::RuizEquilibration& ruiz, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& primal_feasibility_lhs_new, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap, + T& scaled_eps) +{ + ppd::global_primal_residual(qpmodel, + qpresults, + qpsettings, + qpwork, + ruiz, + box_constraints, + primal_feasibility_lhs_new, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs); + + bool is_primal_feasible = + primal_feasibility_lhs_new <= + (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0)); + qpresults.info.pri_res = primal_feasibility_lhs_new; + if (is_primal_feasible) { + T dual_feasibility_lhs_new(dual_feasibility_lhs); + + ppd::global_dual_residual(qpresults, + qpwork, + qpmodel, + box_constraints, + ruiz, + dual_feasibility_lhs_new, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + hessian_type); + qpresults.info.dua_res = dual_feasibility_lhs_new; + qpresults.info.duality_gap = duality_gap; + + bool is_dual_feasible = + dual_feasibility_lhs_new <= + (qpsettings.eps_abs + + qpsettings.eps_rel * + std::max( + std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), + std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2))); + + if (is_dual_feasible) { + if (qpsettings.check_duality_gap) { + if (std::fabs(qpresults.info.duality_gap) <= + qpsettings.eps_duality_gap_abs + + qpsettings.eps_duality_gap_rel * rhs_duality_gap) { + if (qpsettings.primal_infeasibility_solving && + qpresults.info.status == + pp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { + qpresults.info.status = + pp::QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; + } else { + qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; + } + } + } else { + if (qpsettings.primal_infeasibility_solving && + qpresults.info.status == + pp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { + qpresults.info.status = + pp::QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE; + } else { + qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; + } + } + } + } +} } // namespace common } // namespace proxsuite From 8d1b0d83eddab76bac276bd226baacfac2fdddc2 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 10:03:11 +0200 Subject: [PATCH 06/27] proxqp/dense/solver: Factorize the print_solver_statistics function --- include/proxsuite/osqp/dense/solver.hpp | 21 ++++ include/proxsuite/proxqp/dense/solver.hpp | 60 +++-------- include/proxsuite/solvers/common/utils.hpp | 113 +++++++++++++++++---- 3 files changed, 130 insertions(+), 64 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index c67ff1a42..72a3592f5 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -118,6 +118,27 @@ qp_solve( // qpwork.y_prev = qpresults.y; qpwork.z_prev = qpresults.z; + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 1 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 2 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 3 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 4 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 5 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 6 + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 7 + PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 461a2b305..f8e732549 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1182,6 +1182,7 @@ qp_solve( // qpwork.z_prev = qpresults.z; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 1 // primal dual version from gill and robinson @@ -1224,6 +1225,7 @@ qp_solve( // } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 2 primal_dual_newton_semi_smooth(qpsettings, qpmodel, @@ -1261,6 +1263,7 @@ qp_solve( // infty_norm(qpwork.rhs.head(qpmodel.dim)) * qpsettings.eps_abs; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 3 T primal_feasibility_lhs_new(primal_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, @@ -1284,6 +1287,7 @@ qp_solve( // scaled_eps); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 4 if (qpsettings.bcl_update) { bcl_update(qpsettings, @@ -1367,6 +1371,9 @@ qp_solve( // qpresults.info.mu_in_inv = new_bcl_mu_in_inv; } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 5 + 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( @@ -1407,52 +1414,17 @@ qp_solve( // qpresults.info.solve_time + qpresults.info.setup_time; } - if (qpsettings.verbose) { - std::cout << "-------------------SOLVER STATISTICS-------------------" - << std::endl; - std::cout << "outer iter: " << qpresults.info.iter_ext << std::endl; - std::cout << "total iter: " << qpresults.info.iter << std::endl; - std::cout << "mu updates: " << qpresults.info.mu_updates << std::endl; - std::cout << "rho updates: " << qpresults.info.rho_updates << std::endl; - std::cout << "objective: " << qpresults.info.objValue << std::endl; - switch (qpresults.info.status) { - case QPSolverOutput::PROXQP_SOLVED: { - std::cout << "status: " - << "Solved" << std::endl; - break; - } - case QPSolverOutput::PROXQP_MAX_ITER_REACHED: { - std::cout << "status: " - << "Maximum number of iterations reached" << std::endl; - break; - } - case QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE: { - std::cout << "status: " - << "Primal infeasible" << std::endl; - break; - } - case QPSolverOutput::PROXQP_DUAL_INFEASIBLE: { - std::cout << "status: " - << "Dual infeasible" << std::endl; - break; - } - case QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE: { - std::cout << "status: " - << "Solved closest primal feasible" << std::endl; - break; - } - case QPSolverOutput::PROXQP_NOT_RUN: { - std::cout << "status: " - << "Solver not run" << std::endl; - break; - } - } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 6 - if (qpsettings.compute_timings) - std::cout << "run time [μs]: " << qpresults.info.solve_time << std::endl; - std::cout << "--------------------------------------------------------" - << std::endl; + if (qpsettings.verbose) { + proxsuite::common::print_solver_statistics( + qpsettings, qpresults, common::QPSolver::PROXQP); } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 7 + qpwork.dirty = true; qpwork.is_initialized = true; // necessary because we call workspace cleanup diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index c91d4c491..bc31b5a23 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -40,7 +40,6 @@ enum class QPSolver /*! * Prints the setup header. * - * @param qpwork solver workspace. * @param qpmodel QP problem model as defined by the user (without any scaling * performed). * @param qpsettings solver settings. @@ -50,9 +49,9 @@ enum class QPSolver */ template void -print_setup_header(const pp::Settings& settings, - const pp::Results& results, - const ppd::Model& model, +print_setup_header(const pp::Settings& qpsettings, + const pp::Results& qpresults, + const ppd::Model& qpmodel, const bool box_constraints, const pp::DenseBackend& dense_backend, const pp::HessianType& hessian_type, @@ -70,30 +69,31 @@ print_setup_header(const pp::Settings& settings, // Print variables and constraints std::cout << "problem: " << std::noshowpos << std::endl; - std::cout << " variables n = " << model.dim - << ", equality constraints n_eq = " << model.n_eq << ",\n" - << " inequality constraints n_in = " << model.n_in + std::cout << " variables n = " << qpmodel.dim + << ", equality constraints n_eq = " << qpmodel.n_eq << ",\n" + << " inequality constraints n_in = " << qpmodel.n_in << std::endl; // Print Settings std::cout << "settings: " << std::endl; std::cout << " backend = dense," << std::endl; - std::cout << " eps_abs = " << settings.eps_abs - << " eps_rel = " << settings.eps_rel << std::endl; - std::cout << " eps_prim_inf = " << settings.eps_primal_inf - << ", eps_dual_inf = " << settings.eps_dual_inf << "," << std::endl; + std::cout << " eps_abs = " << qpsettings.eps_abs + << " eps_rel = " << qpsettings.eps_rel << std::endl; + std::cout << " eps_prim_inf = " << qpsettings.eps_primal_inf + << ", eps_dual_inf = " << qpsettings.eps_dual_inf << "," + << std::endl; - std::cout << " rho = " << results.info.rho - << ", mu_eq = " << results.info.mu_eq - << ", mu_in = " << results.info.mu_in << "," << std::endl; + std::cout << " rho = " << qpresults.info.rho + << ", mu_eq = " << qpresults.info.mu_eq + << ", mu_in = " << qpresults.info.mu_in << "," << std::endl; switch (qp_solver) { case common::QPSolver::PROXQP: - std::cout << " max_iter = " << settings.max_iter - << ", max_iter_in = " << settings.max_iter_in << "," + std::cout << " max_iter = " << qpsettings.max_iter + << ", max_iter_in = " << qpsettings.max_iter_in << "," << std::endl; break; case common::QPSolver::OSQP: - std::cout << " max_iter = " << settings.max_iter << std::endl; + std::cout << " max_iter = " << qpsettings.max_iter << std::endl; break; } if (box_constraints) { @@ -124,17 +124,17 @@ print_setup_header(const pp::Settings& settings, << std::endl; break; } - if (settings.compute_preconditioner) { + if (qpsettings.compute_preconditioner) { std::cout << " scaling: on, " << std::endl; } else { std::cout << " scaling: off, " << std::endl; } - if (settings.compute_timings) { + if (qpsettings.compute_timings) { std::cout << " timings: on, " << std::endl; } else { std::cout << " timings: off, " << std::endl; } - switch (settings.initial_guess) { + switch (qpsettings.initial_guess) { case pp::InitialGuessStatus::WARM_START: std::cout << " initial guess: warm start. \n" << std::endl; break; @@ -157,6 +157,79 @@ print_setup_header(const pp::Settings& settings, << std::endl; } } +/*! + * Prints the solver's statistics. + * + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param qp_solver PROXQP or OSQP. + */ +template +void +print_solver_statistics(const pp::Settings& qpsettings, + const pp::Results& qpresults, + const common::QPSolver qp_solver) +{ + std::cout << "-------------------SOLVER STATISTICS-------------------" + << std::endl; + + switch (qp_solver) { + case common::QPSolver::PROXQP: { + std::cout << "outer iter: " << qpresults.info.iter_ext << std::endl; + std::cout << "total iter: " << qpresults.info.iter << std::endl; + std::cout << "mu updates: " << qpresults.info.mu_updates << std::endl; + std::cout << "rho updates: " << qpresults.info.rho_updates + << std::endl; + std::cout << "objective: " << qpresults.info.objValue << std::endl; + break; + } + case common::QPSolver::OSQP: { + std::cout << "outer iter: " << qpresults.info.iter_ext << std::endl; + std::cout << "total iter: " << qpresults.info.iter_ext << std::endl; + std::cout << "mu updates: " << qpresults.info.mu_updates << std::endl; + std::cout << "objective: " << qpresults.info.objValue << std::endl; + break; + } + } + + switch (qpresults.info.status) { + case pp::QPSolverOutput::PROXQP_SOLVED: { + std::cout << "status: " + << "Solved" << std::endl; + break; + } + case pp::QPSolverOutput::PROXQP_MAX_ITER_REACHED: { + std::cout << "status: " + << "Maximum number of iterations reached" << std::endl; + break; + } + case pp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE: { + std::cout << "status: " + << "Primal infeasible" << std::endl; + break; + } + case pp::QPSolverOutput::PROXQP_DUAL_INFEASIBLE: { + std::cout << "status: " + << "Dual infeasible" << std::endl; + break; + } + case pp::QPSolverOutput::PROXQP_SOLVED_CLOSEST_PRIMAL_FEASIBLE: { + std::cout << "status: " + << "Solved closest primal feasible" << std::endl; + break; + } + case pp::QPSolverOutput::PROXQP_NOT_RUN: { + std::cout << "status: " + << "Solver not run" << std::endl; + break; + } + } + + if (qpsettings.compute_timings) + std::cout << "run time [μs]: " << qpresults.info.solve_time << std::endl; + std::cout << "--------------------------------------------------------" + << std::endl; +} /*! * Setups the solver. * In particular, it scales (Ruiz equilibration) the data, then From b2e3825edcd30cd67b484f97c7c7acf0b63d3b00 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 10:14:07 +0200 Subject: [PATCH 07/27] proxqp/dense/solver.hpp: Factorize the prepare_next_solve function --- include/proxsuite/osqp/dense/solver.hpp | 3 +++ include/proxsuite/proxqp/dense/solver.hpp | 8 +++----- include/proxsuite/solvers/common/utils.hpp | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 72a3592f5..85091c408 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -139,6 +139,9 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 7 + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 8 + PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index f8e732549..f6ca056a1 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1425,12 +1425,10 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 7 - qpwork.dirty = true; - qpwork.is_initialized = true; // necessary because we call workspace cleanup + proxsuite::common::prepare_next_solve(qpresults, qpwork); - assert(!std::isnan(qpresults.info.pri_res)); - assert(!std::isnan(qpresults.info.dua_res)); - assert(!std::isnan(qpresults.info.duality_gap)); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 8 PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index bc31b5a23..fdf82bbb2 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -230,6 +230,24 @@ print_solver_statistics(const pp::Settings& qpsettings, std::cout << "--------------------------------------------------------" << std::endl; } +/*! + * Prepares the next solve. Sets workspace to initialized and + * cleanups the information results. + * + * @param qpwork solver workspace. + * @param qpresults solver results. + */ +template +void +prepare_next_solve(pp::Results& qpresults, ppd::Workspace& qpwork) +{ + qpwork.dirty = true; + qpwork.is_initialized = true; // necessary because we call workspace cleanup + + assert(!std::isnan(qpresults.info.pri_res)); + assert(!std::isnan(qpresults.info.dua_res)); + assert(!std::isnan(qpresults.info.duality_gap)); +} /*! * Setups the solver. * In particular, it scales (Ruiz equilibration) the data, then From edb2904f168c8758400784ecf74e37fdbfbe10f9 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 10:21:22 +0200 Subject: [PATCH 08/27] proxqp/dense/solver.hpp: Factorize tje compute_objective function --- include/proxsuite/proxqp/dense/solver.hpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index f6ca056a1..64db7be54 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1394,19 +1394,7 @@ qp_solve( // } } - { - // EigenAllowAlloc _{}; - qpresults.info.objValue = 0; - for (Eigen::Index j = 0; j < qpmodel.dim; ++j) { - qpresults.info.objValue += - 0.5 * (qpresults.x(j) * qpresults.x(j)) * qpmodel.H(j, j); - qpresults.info.objValue += - qpresults.x(j) * T(qpmodel.H.col(j) - .tail(qpmodel.dim - j - 1) - .dot(qpresults.x.tail(qpmodel.dim - j - 1))); - } - qpresults.info.objValue += (qpmodel.g).dot(qpresults.x); - } + proxsuite::common::compute_objective(qpmodel, qpresults); if (qpsettings.compute_timings) { qpresults.info.solve_time = qpwork.timer.elapsed().user; // in microseconds From 89f0f5d3cb3f24334c05457ac4ad80d0e1fd49c8 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 10:26:11 +0200 Subject: [PATCH 09/27] proxqp/dense/solver/hpp: Factorize the compute_timings function --- include/proxsuite/proxqp/dense/solver.hpp | 4 +--- include/proxsuite/solvers/common/utils.hpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 64db7be54..66fe66f95 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1397,9 +1397,7 @@ qp_solve( // proxsuite::common::compute_objective(qpmodel, qpresults); if (qpsettings.compute_timings) { - qpresults.info.solve_time = qpwork.timer.elapsed().user; // in microseconds - qpresults.info.run_time = - qpresults.info.solve_time + qpresults.info.setup_time; + proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index fdf82bbb2..7efad3e9e 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -606,6 +606,23 @@ compute_objective(const ppd::Model& qpmodel, pp::Results& qpresults) } qpresults.info.objValue += (qpmodel.g).dot(qpresults.x); } +/*! + * Computes the objective function. + * + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param qpwork solver workspace. + */ +template +void +compute_timings(const pp::Settings& qpsettings, + pp::Results& qpresults, + ppd::Workspace& qpwork) +{ + qpresults.info.solve_time = qpwork.timer.elapsed().user; // in microseconds + qpresults.info.run_time = + qpresults.info.solve_time + qpresults.info.setup_time; +} /*! * Computes the residuals and the feasibility of the problem, then update it * and stops the algorithm if needed. From 29aa9bd758d7205073671cc550cd2cbe01770d3c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 10:35:23 +0200 Subject: [PATCH 10/27] proxqp/dense/solver.hpp: Factorize the unscale_solver function --- include/proxsuite/proxqp/dense/solver.hpp | 23 ++----------- include/proxsuite/solvers/common/utils.hpp | 38 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 66fe66f95..3ec3b8b51 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1374,28 +1374,9 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 5 - 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.head(qpmodel.n_in) }); - if (box_constraints) { - ruiz.unscale_box_dual_in_place_in( - VectorViewMut{ from_eigen, qpresults.z.tail(qpmodel.dim) }); - } - if (qpsettings.primal_infeasibility_solving && - qpresults.info.status == QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { - ruiz.unscale_primal_residual_in_place_eq( - VectorViewMut{ from_eigen, qpresults.se }); - ruiz.unscale_primal_residual_in_place_in( - VectorViewMut{ from_eigen, qpresults.si.head(qpmodel.n_in) }); - if (box_constraints) { - ruiz.unscale_box_primal_residual_in_place_in( - VectorViewMut{ from_eigen, qpresults.si.tail(qpmodel.dim) }); - } - } - + proxsuite::common::unscale_solver( + qpsettings, qpmodel, qpresults, box_constraints, ruiz); proxsuite::common::compute_objective(qpmodel, qpresults); - if (qpsettings.compute_timings) { proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); } diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 7efad3e9e..3aab4f9ee 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -623,6 +623,44 @@ compute_timings(const pp::Settings& qpsettings, qpresults.info.run_time = qpresults.info.solve_time + qpresults.info.setup_time; } +/*! + * Unscales the solver once the algorithm is finished. + * + * @param qpsettings solver settings. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solver results. + */ +template +void +unscale_solver(const pp::Settings& qpsettings, + const ppd::Model& qpmodel, + pp::Results& qpresults, + const bool box_constraints, + ppdp::RuizEquilibration& ruiz) +{ + ruiz.unscale_primal_in_place( + pp::VectorViewMut{ pp::from_eigen, qpresults.x }); + ruiz.unscale_dual_in_place_eq( + pp::VectorViewMut{ pp::from_eigen, qpresults.y }); + ruiz.unscale_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_dual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.z.tail(qpmodel.dim) }); + } + if (qpsettings.primal_infeasibility_solving && + qpresults.info.status == pp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE) { + ruiz.unscale_primal_residual_in_place_eq( + pp::VectorViewMut{ pp::from_eigen, qpresults.se }); + ruiz.unscale_primal_residual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.si.head(qpmodel.n_in) }); + if (box_constraints) { + ruiz.unscale_box_primal_residual_in_place_in( + pp::VectorViewMut{ pp::from_eigen, qpresults.si.tail(qpmodel.dim) }); + } + } +} /*! * Computes the residuals and the feasibility of the problem, then update it * and stops the algorithm if needed. From 98fe6625d85eaa9917f10e29d2ca710c6a370372 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 11:14:26 +0200 Subject: [PATCH 11/27] osqp/dense/wrapper.hpp: Fix bug with class constructors and call to qp_solve --- examples/cpp/osqp_overview-simple.cpp | 37 +++++++ include/proxsuite/osqp/dense/solver.hpp | 40 +++++-- include/proxsuite/osqp/dense/wrapper.hpp | 126 +++++++++++----------- include/proxsuite/proxqp/dense/solver.hpp | 9 -- 4 files changed, 137 insertions(+), 75 deletions(-) create mode 100644 examples/cpp/osqp_overview-simple.cpp diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp new file mode 100644 index 000000000..5d3245f24 --- /dev/null +++ b/examples/cpp/osqp_overview-simple.cpp @@ -0,0 +1,37 @@ +#include +#include +#include // used for generating a random convex qp + +using namespace proxsuite::osqp; +using T = double; + +namespace pp = proxsuite::proxqp; +namespace ppd = proxsuite::proxqp::dense; +namespace pod = proxsuite::osqp::dense; + +int +main() +{ + T sparsity_factor = 0.15; + ppd::isize dim = 10; + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + + ppd::Model qp_random = pp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + pod::QP qp(dim, n_eq, n_in); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); + + 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; +} diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 85091c408..ccd46b930 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -127,20 +127,48 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 3 + T primal_feasibility_lhs_new(primal_feasibility_lhs); + proxsuite::common::update_solver_status(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 4 + // Update of mu + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 5 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 6 + proxsuite::common::unscale_solver( + qpsettings, qpmodel, qpresults, box_constraints, ruiz); + proxsuite::common::compute_objective(qpmodel, qpresults); + if (qpsettings.compute_timings) { + proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); + } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 7 + if (qpsettings.verbose) { + proxsuite::common::print_solver_statistics( + qpsettings, qpresults, common::QPSolver::PROXQP); + } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 8 + proxsuite::common::prepare_next_solve(qpresults, qpwork); PROXSUITE_EIGEN_MALLOC_ALLOWED(); } diff --git a/include/proxsuite/osqp/dense/wrapper.hpp b/include/proxsuite/osqp/dense/wrapper.hpp index e183a4211..0b8bb671d 100644 --- a/include/proxsuite/osqp/dense/wrapper.hpp +++ b/include/proxsuite/osqp/dense/wrapper.hpp @@ -16,19 +16,27 @@ namespace proxsuite { namespace osqp { namespace dense { +namespace pp = proxsuite::proxqp; +namespace ppd = proxsuite::proxqp::dense; +namespace pod = proxsuite::osqp::dense; + /// /// @brief This class defines the API of OSQP solver with dense backend. /// template -struct QP : public proxsuite::proxqp::dense::QP +struct QP : public ppd::QP { public: + /*! + * Class constructors. + */ + using ppd::QP::QP; /*! * Solves the QP problem using OSQP algorithm. */ void solve() { - qp_solve( // + pod::qp_solve( // this->settings, this->model, this->results, @@ -48,8 +56,8 @@ struct QP : public proxsuite::proxqp::dense::QP optional> y, optional> z) { - warm_start(x, y, z, this->results, this->settings, this->model); - qp_solve( // + ppd::warm_start(x, y, z, this->results, this->settings, this->model); + pod::qp_solve( // this->settings, this->model, this->results, @@ -98,33 +106,32 @@ struct QP : public proxsuite::proxqp::dense::QP */ template proxqp::Results -solve( - optional> H, - optional> g, - optional> A, - optional> b, - optional> C, - optional> l, - optional> u, - 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, - bool primal_infeasibility_solving = false, - optional manual_minimal_H_eigenvalue = nullopt) +solve(optional> H, + optional> g, + optional> A, + optional> b, + optional> C, + optional> l, + optional> u, + 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, + pp::InitialGuessStatus initial_guess = + pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS, + bool check_duality_gap = false, + optional eps_duality_gap_abs = nullopt, + optional eps_duality_gap_rel = nullopt, + bool primal_infeasibility_solving = false, + optional manual_minimal_H_eigenvalue = nullopt) { isize n(0); isize n_eq(0); @@ -212,35 +219,34 @@ solve( */ 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, - bool primal_infeasibility_solving = false, - optional manual_minimal_H_eigenvalue = nullopt) +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, + pp::InitialGuessStatus initial_guess = + pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS, + bool check_duality_gap = false, + optional eps_duality_gap_abs = nullopt, + optional eps_duality_gap_rel = nullopt, + bool primal_infeasibility_solving = false, + optional manual_minimal_H_eigenvalue = nullopt) { isize n(0); isize n_eq(0); diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 3ec3b8b51..964db36c2 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1381,22 +1381,13 @@ qp_solve( // proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 6 - if (qpsettings.verbose) { proxsuite::common::print_solver_statistics( qpsettings, qpresults, common::QPSolver::PROXQP); } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 7 - proxsuite::common::prepare_next_solve(qpresults, qpwork); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 8 - PROXSUITE_EIGEN_MALLOC_ALLOWED(); } From 8b27e7e8d7d061151fcba382c230a9048bc66452 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Wed, 7 May 2025 15:34:57 +0200 Subject: [PATCH 12/27] osqp::dense: Equality --- examples/cpp/osqp_initializing_with_none.cpp | 38 +++ ...sqp_initializing_with_none_without_api.cpp | 37 +++ include/proxsuite/osqp/dense/solver.hpp | 150 +++++++--- include/proxsuite/osqp/dense/wrapper.hpp | 6 +- include/proxsuite/proxqp/settings.hpp | 9 + include/proxsuite/solvers/common/utils.hpp | 10 +- test/CMakeLists.txt | 3 + test/src/osqp_dense_qp_eq.cpp | 258 ++++++++++++++++++ test/src/osqp_dense_ruiz_equilibration.cpp | 73 +++++ test/src/osqp_dense_unconstrained_qp.cpp | 210 ++++++++++++++ 10 files changed, 751 insertions(+), 43 deletions(-) create mode 100644 examples/cpp/osqp_initializing_with_none.cpp create mode 100644 examples/cpp/osqp_initializing_with_none_without_api.cpp create mode 100644 test/src/osqp_dense_qp_eq.cpp create mode 100644 test/src/osqp_dense_ruiz_equilibration.cpp create mode 100644 test/src/osqp_dense_unconstrained_qp.cpp diff --git a/examples/cpp/osqp_initializing_with_none.cpp b/examples/cpp/osqp_initializing_with_none.cpp new file mode 100644 index 000000000..974da2b4e --- /dev/null +++ b/examples/cpp/osqp_initializing_with_none.cpp @@ -0,0 +1,38 @@ +#include +#include "proxsuite/osqp/dense/dense.hpp" +#include // used for generating a random convex qp + +using namespace proxsuite::proxqp; +using T = double; +namespace pod = proxsuite::osqp::dense; + +int +main() +{ + dense::isize dim = 10; + dense::isize n_eq(0); + dense::isize n_in(0); + pod::QP qp(dim, n_eq, n_in); + T strong_convexity_factor(0.1); + T sparsity_factor(0.15); + // we generate a qp, so the function used from helpers.hpp is + // in proxqp namespace. The qp is in dense eigen format and + // you can control its sparsity ratio and strong convexity factor. + dense::Model qp_random = utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); // initialization with zero shape matrices + // it is equivalent to do qp.init(qp_random.H, qp_random.g, + // nullopt,nullopt,nullopt,nullopt,nullopt); + qp.solve(); + // print an optimal solution x,y and z + 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; +} \ No newline at end of file diff --git a/examples/cpp/osqp_initializing_with_none_without_api.cpp b/examples/cpp/osqp_initializing_with_none_without_api.cpp new file mode 100644 index 000000000..3babe0d37 --- /dev/null +++ b/examples/cpp/osqp_initializing_with_none_without_api.cpp @@ -0,0 +1,37 @@ +#include +#include "proxsuite/osqp/dense/dense.hpp" +#include // used for generating a random convex qp + +using namespace proxsuite::proxqp; +using T = double; +namespace pod = proxsuite::osqp::dense; + +int +main() +{ + dense::isize dim = 10; + dense::isize n_eq(0); + dense::isize n_in(0); + T strong_convexity_factor(0.1); + T sparsity_factor(0.15); + // we generate a qp, so the function used from helpers.hpp is + // in proxqp namespace. The qp is in dense eigen format and + // you can control its sparsity ratio and strong convexity factor. + dense::Model qp_random = utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + Results results = + pod::solve(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); // initialization with zero shape matrices + // it is equivalent to do dense::solve(qp_random.H, qp_random.g, + // nullopt,nullopt,nullopt,nullopt,nullopt); + // print an optimal solution x,y and z + std::cout << "optimal x: " << results.x << std::endl; + std::cout << "optimal y: " << results.y << std::endl; + std::cout << "optimal z: " << results.z << std::endl; +} \ No newline at end of file diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index ccd46b930..968bc9d03 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -29,6 +29,73 @@ namespace dense { using namespace proxsuite::proxqp; using namespace proxsuite::proxqp::dense; +/*! + * One iteration of the ADMM algorithm adapted in OSQP. + * + * Solves the linear system (KKT), then update the primal and dual variables. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + */ +template +void +admm_iter(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + preconditioner::RuizEquilibration& ruiz, + const DenseBackend dense_backend, + const HessianType hessian_type) +{ + // Note: + // In the context of a library (proxsuite) to implement different solvers, we + // use the same inetrmediate functions (infeasibility, residuals, etc) and API + // than in the code of ProxQP Yet, the OSQP paper (see + // https://inria.hal.science/hal-03683733/file/Yet_another_QP_solver_for_robotics_and_beyond.pdf/) + // manages both the equality and inequality constraints in one matrix A. Thus, + // the following adapts the content of the paper to our library (eg matrices + // A, C, I for constraints). + + // Solve the linear system + Vec x_tilde; + Vec nu_eq; + Vec zeta_tilde_eq; + + qpwork.rhs.setZero(); + qpwork.rhs.head(qpmodel.dim) = + qpresults.info.rho * qpresults.x - qpwork.g_scaled; + qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq) = + qpwork.b_scaled - qpresults.info.mu_eq * qpresults.y; + + isize inner_pb_dim = qpmodel.dim + qpmodel.n_eq; + proxsuite::linalg::veg::dynstack::DynStackMut stack{ + proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() + }; + solve_linear_system(qpwork.rhs, + qpmodel, + qpresults, + qpwork, + n_constraints, + dense_backend, + inner_pb_dim, + stack); + x_tilde = qpwork.rhs.head(qpmodel.dim); + nu_eq = qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq); + + // Update the variables + zeta_tilde_eq = + qpwork.b_scaled + qpresults.info.mu_eq * (nu_eq - qpresults.y); + qpresults.x = + qpsettings.alpha_osqp * x_tilde + (1 - qpsettings.alpha_osqp) * qpresults.x; + qpresults.y = qpresults.y + qpresults.info.mu_eq_inv * qpsettings.alpha_osqp * + (zeta_tilde_eq - qpwork.b_scaled); +} /*! * Executes the OSQP algorithm. * @@ -110,48 +177,61 @@ qp_solve( // if (stop_loop) { break; } - } - qpresults.info.iter_ext += 1; + qpresults.info.iter_ext += 1; - qpwork.x_prev = qpresults.x; - qpwork.y_prev = qpresults.y; - qpwork.z_prev = qpresults.z; + qpwork.x_prev = qpresults.x; + qpwork.y_prev = qpresults.y; + qpwork.z_prev = qpresults.z; - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 1 + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 1 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 2 + // Activate sets, specific to inequality - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 3 - - T primal_feasibility_lhs_new(primal_feasibility_lhs); - proxsuite::common::update_solver_status(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - hessian_type, - ruiz, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs_new, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 2 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 4 + admm_iter(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + ruiz, + dense_backend, + hessian_type); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 3 + + T primal_feasibility_lhs_new(primal_feasibility_lhs); + proxsuite::common::update_solver_status(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// 4 + + // Update of mu - // Update of mu + } // End of loop of ADMM iterations ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 5 diff --git a/include/proxsuite/osqp/dense/wrapper.hpp b/include/proxsuite/osqp/dense/wrapper.hpp index 0b8bb671d..59f61bdc6 100644 --- a/include/proxsuite/osqp/dense/wrapper.hpp +++ b/include/proxsuite/osqp/dense/wrapper.hpp @@ -62,9 +62,9 @@ struct QP : public ppd::QP this->model, this->results, this->work, - this->box_constraints, - this->dense_backend, - this->hessian_type, + this->get_box_constraints(), + this->get_dense_backend(), + this->get_hessian_type(), this->ruiz); }; }; diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index f455238b4..630a94b29 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -95,6 +95,8 @@ struct Settings T alpha_bcl; T beta_bcl; + T alpha_osqp; + T refactor_dual_feasibility_threshold; T refactor_rho_threshold; @@ -148,6 +150,7 @@ struct Settings * @param default_mu_in default mu_in parameter of result class * @param alpha_bcl alpha parameter of the BCL algorithm. * @param beta_bcl beta parameter of the BCL algorithm. + * @param alpha_osqp alpha parameter in the ADMM step in OSQP. * @param refactor_dual_feasibility_threshold threshold above which * refactorization is performed to change rho parameter. * @param refactor_rho_threshold new rho parameter used if the @@ -216,6 +219,7 @@ struct Settings T default_mu_in = 1.E-1, T alpha_bcl = 0.1, T beta_bcl = 0.9, + T alpha_osqp = 1.6, T refactor_dual_feasibility_threshold = 1e-2, T refactor_rho_threshold = 1e-7, T mu_min_eq = 1e-9, @@ -262,6 +266,7 @@ struct Settings , default_mu_in(default_mu_in) , alpha_bcl(alpha_bcl) , beta_bcl(beta_bcl) + , alpha_osqp(alpha_osqp) , refactor_dual_feasibility_threshold(refactor_dual_feasibility_threshold) , refactor_rho_threshold(refactor_rho_threshold) , mu_min_eq(mu_min_eq) @@ -325,6 +330,10 @@ operator==(const Settings& settings1, const Settings& settings2) settings1.default_mu_in == settings2.default_mu_in && settings1.alpha_bcl == settings2.alpha_bcl && settings1.alpha_bcl == settings2.alpha_bcl && + settings1.beta_bcl == settings2.beta_bcl && + settings1.beta_bcl == settings2.beta_bcl && + settings1.alpha_osqp == settings2.alpha_osqp && + settings1.alpha_osqp == settings2.alpha_osqp && settings1.refactor_dual_feasibility_threshold == settings2.refactor_dual_feasibility_threshold && settings1.refactor_rho_threshold == settings2.refactor_rho_threshold && diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 3aab4f9ee..8d2b3b47e 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -412,7 +412,7 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Call for function to build the full KKT + // TODO: Build full KKT with inequality constraints } break; } break; @@ -436,7 +436,7 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Call for function to build the full KKT + // TODO: Build full KKT with inequality constraints } break; } break; @@ -496,7 +496,7 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Call for function to build the full KKT + // TODO: Build full KKT with inequality constraints } break; } break; @@ -535,7 +535,7 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Call for function to build the full KKT + // TODO: Build full KKT with inequality constraints } break; } break; @@ -575,7 +575,7 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Call for function to build the full KKT + // TODO: Build full KKT with enaqulity constraints } break; } break; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dbea3694d..bf68ccde0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,9 @@ macro(proxsuite_test name path) add_dependencies(build_tests ${target_name}) endmacro() +proxsuite_test(osqp_dense_ruiz_equilibration src/osqp_dense_ruiz_equilibration.cpp) +proxsuite_test(osqp_dense_qp_eq src/osqp_dense_qp_eq.cpp) +proxsuite_test(osqp_dense_qp_unconstrained src/osqp_dense_unconstrained_qp.cpp) proxsuite_test(dense_ruiz_equilibration src/dense_ruiz_equilibration.cpp) proxsuite_test(dense_qp_eq src/dense_qp_eq.cpp) proxsuite_test(dense_qp_with_eq_and_in src/dense_qp_with_eq_and_in.cpp) diff --git a/test/src/osqp_dense_qp_eq.cpp b/test/src/osqp_dense_qp_eq.cpp new file mode 100644 index 000000000..b481da453 --- /dev/null +++ b/test/src/osqp_dense_qp_eq.cpp @@ -0,0 +1,258 @@ +// +// Copyright (c) 2022 - 2024 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE("qp: start from solution using the wrapper framework") +{ + proxqp::isize dim = 30; + proxqp::isize n_eq = 6; + proxqp::isize n_in = 0; + T sparsity_factor = 0.15; + T strong_convexity_factor(1.e-2); + std::cout << "---testing sparse random strongly convex qp with equality " + "constraints and starting at the solution using the wrapper " + "framework---" + << std::endl; + proxqp::utils::rand::set_seed(1); + auto H = ::proxsuite::proxqp::utils::rand:: + sparse_positive_definite_rand_not_compressed( + dim, strong_convexity_factor, sparsity_factor); + auto A = + ::proxsuite::proxqp::utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + auto solution = ::proxsuite::proxqp::utils::rand::vector_rand(dim + n_eq); + auto primal_solution = solution.topRows(dim); + auto dual_solution = solution.bottomRows(n_eq); + auto b = A * primal_solution; + auto g = -H * primal_solution - A.transpose() * dual_solution; + auto C = + ::proxsuite::proxqp::utils::rand::sparse_matrix_rand_not_compressed( + 0, dim, sparsity_factor); + Eigen::Matrix dual_init_in(n_in); + Eigen::Matrix u(0); + Eigen::Matrix l(0); + dual_init_in.setZero(); + T eps_abs = T(1e-9); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.initial_guess = proxsuite::proxqp::InitialGuessStatus::WARM_START; + qp.init(H, g, A, b, C, l, u); + qp.solve(primal_solution, dual_solution, dual_init_in); + + DOCTEST_CHECK((A * qp.results.x - b).lpNorm() <= eps_abs); + 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") +{ + + std::cout << "---testing sparse random strongly convex qp with equality " + "constraints and increasing dimension with the wrapper API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + + proxqp::isize n_eq(dim / 2); + proxqp::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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.u, + qp_random.l); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using wrapper API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} +DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " + "linar cost and increasing dimension using wrapper API") +{ + + std::cout << "---testing linear problem with equality constraints and " + "increasing dimension using wrapper API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + + proxqp::isize n_eq(dim / 2); + proxqp::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); + qp_random.H.setZero(); + auto y_sol = proxqp::utils::rand::vector_rand( + n_eq); // make sure the LP is bounded within the feasible set + qp_random.g = -qp_random.A.transpose() * y_sol; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using wrapper API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " + "linear cost and increasing dimension using wrapper API and " + "the dedicated LP interface") +{ + + std::cout + << "---testing LP interface for solving linear problem with " + "equality constraints and increasing dimension using wrapper API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + + proxqp::isize n_eq(dim / 2); + proxqp::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); + qp_random.H.setZero(); + auto y_sol = proxqp::utils::rand::vector_rand( + n_eq); // make sure the LP is bounded within the feasible set + qp_random.g = -qp_random.A.transpose() * y_sol; + + pod::QP qp{ + dim, n_eq, n_in, proxqp::HessianType::Zero + }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using wrapper API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +// DOCTEST_TEST_CASE("infeasible qp") +// { +// // (x1- 9)^2 + (x2-6)^2 +// // s.t. +// // x1 <= 10 +// // x2 <= 10 +// // x1 >= 20 +// Eigen::Matrix H; +// H << 1.0, 0.0, 0.0, 1.0; +// H = 2 * H; + +// Eigen::Matrix g; +// g << -18.0, -12.0; + +// Eigen::Matrix C; +// C << 1, 0, // x1 <= 10 +// 0, 1, // x2 <= 10 +// -1, 0; // x1 >= 20 + +// Eigen::Matrix u; +// u << 10, 10, -20; + +// int n = H.rows(); +// int n_in = C.rows(); +// int n_eq = 0; + +// Eigen::Matrix l = +// Eigen::Matrix::Constant( +// n_in, -std::numeric_limits::infinity()); + +// proxsuite::pod::QP qp(n, n_eq, n_in); +// qp.init(H, g, nullopt, nullopt, C, l, u); +// qp.settings.eps_rel = 0.; +// qp.settings.eps_abs = 1e-9; + +// qp.solve(); + +// DOCTEST_CHECK(qp.results.info.status == +// proxsuite::proxqp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE); +// } \ No newline at end of file diff --git a/test/src/osqp_dense_ruiz_equilibration.cpp b/test/src/osqp_dense_ruiz_equilibration.cpp new file mode 100644 index 000000000..56d355c23 --- /dev/null +++ b/test/src/osqp_dense_ruiz_equilibration.cpp @@ -0,0 +1,73 @@ +// +// Copyright (c) 2022-2023 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using namespace proxsuite; +using Scalar = double; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE("ruiz preconditioner") +{ + int dim = 5; + int n_eq = 6; + int n_in = 0; + auto sym = proxqp::Symmetry::general; // 0 : upper triangular (by default), + // 1: + // auto sym = proxqp::Symmetry::lower; // 0 : upper triangular (by default), + // 1: lower triangular ; else full matrix + + Scalar sparsity_factor(0.75); + Scalar strong_convexity_factor(0.01); + proxqp::dense::Model qp_random = + proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + switch (sym) { + case proxqp::Symmetry::upper: { + qp_random.H = qp_random.H.triangularView(); + break; + } + case proxqp::Symmetry::lower: { + qp_random.H = qp_random.H.triangularView(); + break; + } + default: { + } + } + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + + auto head = Eigen::Matrix( + qp.ruiz.delta.head(dim).asDiagonal()); + auto tail = Eigen::Matrix( + qp.ruiz.delta.tail(n_eq).asDiagonal()); + auto c = qp.ruiz.c; + + auto const& H = qp_random.H; + auto const& g = qp_random.g; + auto const& A = qp_random.A; + auto const& b = qp_random.b; + + auto H_new = (c * head * H * head).eval(); + auto g_new = (c * head * g).eval(); + auto A_new = (tail * A * head).eval(); + auto b_new = (tail * b).eval(); + + DOCTEST_CHECK((H_new - qp.work.H_scaled).norm() <= Scalar(1e-10)); + DOCTEST_CHECK((g_new - qp.work.g_scaled).norm() <= Scalar(1e-10)); + DOCTEST_CHECK((A_new - qp.work.A_scaled).norm() <= Scalar(1e-10)); + DOCTEST_CHECK((b_new - qp.work.b_scaled).norm() <= Scalar(1e-10)); +} diff --git a/test/src/osqp_dense_unconstrained_qp.cpp b/test/src/osqp_dense_unconstrained_qp.cpp new file mode 100644 index 000000000..3eadd236d --- /dev/null +++ b/test/src/osqp_dense_unconstrained_qp.cpp @@ -0,0 +1,210 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using namespace proxsuite; +using T = double; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE( + "sparse random strongly convex unconstrained qp and increasing dimension") +{ + + std::cout << "---testing sparse random strongly convex qp with increasing " + "dimension---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + for (int dim = 10; dim < 1000; dim += 100) { + + int n_eq(0); + int n_in(0); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_unconstrained_qp( + dim, sparsity_factor, strong_convexity_factor); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +DOCTEST_TEST_CASE("sparse random not strongly convex unconstrained qp and " + "increasing dimension") +{ + + std::cout << "---testing sparse random not strongly convex unconstrained qp " + "with increasing dimension---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + for (int dim = 10; dim < 1000; dim += 100) { + + int n_eq(0); + int n_in(0); + T strong_convexity_factor(0); + proxqp::dense::Model qp_random = proxqp::utils::dense_unconstrained_qp( + dim, sparsity_factor, strong_convexity_factor); + auto x_sol = proxqp::utils::rand::vector_rand(dim); + qp_random.g = + -qp_random.H * + x_sol; // to be dually feasible g must be in the image space of H + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +DOCTEST_TEST_CASE("unconstrained qp with H = Id and g random") +{ + + std::cout << "---unconstrained qp with H = Id and g random---" << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + + int dim(100); + int n_eq(0); + int n_in(0); + T strong_convexity_factor(1.E-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_unconstrained_qp( + dim, sparsity_factor, strong_convexity_factor); + qp_random.H.setZero(); + qp_random.H.diagonal().array() += 1; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; +} + +DOCTEST_TEST_CASE("unconstrained qp with H = Id and g = 0") +{ + + std::cout << "---unconstrained qp with H = Id and g = 0---" << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + + int dim(100); + int n_eq(0); + int n_in(0); + T strong_convexity_factor(1.E-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_unconstrained_qp( + dim, sparsity_factor, strong_convexity_factor); + qp_random.H.setZero(); + qp_random.H.diagonal().array() += 1; + qp_random.g.setZero(); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; +} From 76f5945b97d77306fbad819317d75e51a9a9b2ec Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Fri, 9 May 2025 16:53:00 +0200 Subject: [PATCH 13/27] osqp::dense: Inequality without polishing and mu update --- include/proxsuite/osqp/dense/solver.hpp | 206 ++++++++++++++++--- include/proxsuite/proxqp/dense/solver.hpp | 133 ++++++------ include/proxsuite/proxqp/dense/workspace.hpp | 20 ++ include/proxsuite/solvers/common/utils.hpp | 156 +++++++++++++- test/src/dense_qp_eq.cpp | 33 +++ test/src/osqp_dense_qp_eq.cpp | 114 ++++++---- 6 files changed, 520 insertions(+), 142 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 968bc9d03..8883a5923 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -29,6 +29,111 @@ namespace dense { using namespace proxsuite::proxqp; using namespace proxsuite::proxqp::dense; +/*! + * Checks the feasibility of the problem at the current step of the ADMM (OSQP) + * solver. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + */ +template +bool +is_infeasible(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + preconditioner::RuizEquilibration& ruiz, + const HessianType hessian_type) +{ + // Get the intermediate data from the workspace + // Computation here as it was done implicitely in the corresponding part + // of the proxqp version in the function primal_dual_newton_semi_smooth + Vec dx = qpresults.x - qpwork.x_prev; + Vec dy = qpresults.y - qpwork.y_prev; + Vec dz = qpresults.z - qpwork.z_prev; + + auto& Hdx = qpwork.Hdx; + auto& Adx = qpwork.Adx; + auto& Cdx = qpwork.Cdx; + auto& ATdy = qpwork.CTz; + + switch (hessian_type) { + case HessianType::Zero: + break; + case HessianType::Dense: + Hdx.noalias() = + qpwork.H_scaled.template selfadjointView() * dx; + break; + case HessianType::Diagonal: +#ifndef NDEBUG + PROXSUITE_THROW_PRETTY(!qpwork.H_scaled.isDiagonal(), + std::invalid_argument, + "H is not diagonal."); +#endif + Hdx.array() = qpwork.H_scaled.diagonal().array() * dx.array(); + break; + } + Adx.noalias() = qpwork.A_scaled * dx; + ATdy.noalias() = qpwork.A_scaled.transpose() * dy; + + proxsuite::linalg::veg::dynstack::DynStackMut stack{ + proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() + }; + 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) { + 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(); + } + + // Call to intermadiate functions to check the feasibility + if (qpresults.info.iter_ext % qpsettings.frequence_infeasibility_check == 0 || + qpsettings.primal_infeasibility_solving) { + + bool is_primal_infeasible = + global_primal_residual_infeasibility(VectorViewMut{ from_eigen, ATdy }, + VectorViewMut{ from_eigen, CTdz }, + VectorViewMut{ from_eigen, dy }, + VectorViewMut{ from_eigen, dz }, + qpwork, + qpmodel, + qpsettings, + box_constraints, + ruiz); + + bool is_dual_infeasible = + global_dual_residual_infeasibility(VectorViewMut{ from_eigen, Adx }, + VectorViewMut{ from_eigen, Cdx }, + VectorViewMut{ from_eigen, Hdx }, + VectorViewMut{ from_eigen, dx }, + qpwork, + qpsettings, + qpmodel, + box_constraints, + ruiz); + + if (is_primal_infeasible) { + qpresults.info.status = QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE; + return true; + } else if (is_dual_infeasible) { + qpresults.info.status = QPSolverOutput::PROXQP_DUAL_INFEASIBLE; + return true; + } + } + return false; +} /*! * One iteration of the ADMM algorithm adapted in OSQP. * @@ -43,7 +148,7 @@ using namespace proxsuite::proxqp::dense; */ template void -admm_iter(const Settings& qpsettings, +admm_step(const Settings& qpsettings, const Model& qpmodel, Results& qpresults, Workspace& qpwork, @@ -65,15 +170,19 @@ admm_iter(const Settings& qpsettings, // Solve the linear system Vec x_tilde; Vec nu_eq; + Vec nu_in; Vec zeta_tilde_eq; + Vec zeta_tilde_in; qpwork.rhs.setZero(); qpwork.rhs.head(qpmodel.dim) = qpresults.info.rho * qpresults.x - qpwork.g_scaled; qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq) = qpwork.b_scaled - qpresults.info.mu_eq * qpresults.y; + qpwork.rhs.tail(n_constraints) = + qpwork.zeta_in - qpresults.info.mu_in * qpresults.z; - isize inner_pb_dim = qpmodel.dim + qpmodel.n_eq; + isize inner_pb_dim = qpmodel.dim + qpmodel.n_eq + n_constraints; proxsuite::linalg::veg::dynstack::DynStackMut stack{ proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() }; @@ -87,14 +196,38 @@ admm_iter(const Settings& qpsettings, stack); x_tilde = qpwork.rhs.head(qpmodel.dim); nu_eq = qpwork.rhs.segment(qpmodel.dim, qpmodel.n_eq); + nu_in = qpwork.rhs.tail(n_constraints); // Update the variables zeta_tilde_eq = qpwork.b_scaled + qpresults.info.mu_eq * (nu_eq - qpresults.y); + zeta_tilde_in = qpwork.zeta_in + qpresults.info.mu_in * (nu_in - qpresults.z); + qpresults.x = qpsettings.alpha_osqp * x_tilde + (1 - qpsettings.alpha_osqp) * qpresults.x; + + qpwork.zeta_eq = qpwork.b_scaled; + Vec zeta_in_next = qpsettings.alpha_osqp * zeta_tilde_in + + (1 - qpsettings.alpha_osqp) * qpwork.zeta_in + + qpresults.info.mu_in * qpresults.z; + if (box_constraints) { + zeta_in_next.head(qpmodel.n_in) = qpwork.l_scaled.cwiseMax( + zeta_in_next.head(qpmodel.n_in).cwiseMin(qpwork.u_scaled)); + zeta_in_next.tail(qpmodel.dim) = qpwork.l_box_scaled.cwiseMax( + zeta_in_next.tail(qpmodel.dim).cwiseMin(qpwork.u_box_scaled)); + } else { + zeta_in_next = + qpwork.l_scaled.cwiseMax(zeta_in_next.cwiseMin(qpwork.u_scaled)); + } + qpresults.y = qpresults.y + qpresults.info.mu_eq_inv * qpsettings.alpha_osqp * (zeta_tilde_eq - qpwork.b_scaled); + qpresults.z = qpresults.z + + qpresults.info.mu_in_inv * + (qpsettings.alpha_osqp * zeta_tilde_in + + (1 - qpsettings.alpha_osqp) * qpwork.zeta_in - zeta_in_next); + + qpwork.zeta_in = zeta_in_next; } /*! * Executes the OSQP algorithm. @@ -151,30 +284,29 @@ qp_solve( // for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { - bool stop_loop = false; - proxsuite::common::compute_feasibility(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - hessian_type, - ruiz, - common::QPSolver::OSQP, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps, - iter, - stop_loop); - if (stop_loop) { + bool is_solved_qp = + proxsuite::common::is_solved(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + common::QPSolver::OSQP, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + iter); + if (is_solved_qp) { break; } @@ -187,12 +319,19 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 1 - // Activate sets, specific to inequality + proxsuite::common::compute_scaled_primal_residual_ineq( + qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + common::QPSolver::OSQP); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 2 - admm_iter(qpsettings, + admm_step(qpsettings, qpmodel, qpresults, qpwork, @@ -202,6 +341,17 @@ qp_solve( // dense_backend, hessian_type); + bool is_infeasible_qp = is_infeasible(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + hessian_type); + if (is_infeasible_qp) { + break; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 3 diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 964db36c2..7b6749b87 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1148,30 +1148,28 @@ qp_solve( // T new_bcl_mu_in_inv(qpresults.info.mu_in_inv); T new_bcl_mu_eq_inv(qpresults.info.mu_eq_inv); - bool stop_loop = false; - proxsuite::common::compute_feasibility(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - hessian_type, - ruiz, - common::QPSolver::PROXQP, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps, - iter, - stop_loop); - if (stop_loop) { + bool is_solved = proxsuite::common::is_solved(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + common::QPSolver::PROXQP, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + iter); + if (is_solved) { break; } @@ -1184,45 +1182,54 @@ qp_solve( // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 1 - // primal dual version from gill and robinson - - 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) - switch (qpsettings.merit_function_type) { - case MeritFunctionType::GPDAL: - qpwork.primal_residual_in_scaled_up += - (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; - break; - case MeritFunctionType::PDAL: - break; - } - qpresults.si = 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) - qpresults.si.head(qpmodel.n_in) -= - qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_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) - qpresults.si.tail(qpmodel.dim) -= - qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) - } + // // primal dual version from gill and robinson + + // 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) + // switch (qpsettings.merit_function_type) { + // case MeritFunctionType::GPDAL: + // qpwork.primal_residual_in_scaled_up += + // (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; + // break; + // case MeritFunctionType::PDAL: + // break; + // } + // qpresults.si = 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) + // qpresults.si.head(qpmodel.n_in) -= + // qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_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) + // qpresults.si.tail(qpmodel.dim) -= + // qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + // } + + proxsuite::common::compute_scaled_primal_residual_ineq( + qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + common::QPSolver::PROXQP); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// 2 diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index 2a4d77b61..7685309af 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -60,6 +60,12 @@ struct Workspace VecBool active_set_low; VecBool active_inequalities; + //// OSQP variables + Vec zeta_eq; + Vec zeta_in; + Vec nu_eq; + Vec nu_in; + //// First order residuals for line search Vec Hdx; @@ -116,6 +122,8 @@ struct Workspace , l_scaled(n_in) , x_prev(dim) , y_prev(n_eq) + , zeta_eq(n_eq) + , nu_eq(n_eq) , Hdx(dim) , Adx(n_eq) , dual_residual_scaled(dim) @@ -207,6 +215,8 @@ struct Workspace active_set_low.resize(n_in + dim); active_inequalities.resize(n_in + dim); active_part_z.resize(n_in + dim); + zeta_in.resize(n_in + dim); + nu_in.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); @@ -282,6 +292,8 @@ struct Workspace active_set_low.resize(n_in); active_inequalities.resize(n_in); active_part_z.resize(n_in); + zeta_in.resize(n_in); + nu_in.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); @@ -302,6 +314,10 @@ struct Workspace x_prev.setZero(); y_prev.setZero(); z_prev.setZero(); + zeta_eq.setZero(); + zeta_in.setZero(); + nu_eq.setZero(); + nu_in.setZero(); kkt.setZero(); Hdx.setZero(); Cdx.setZero(); @@ -338,6 +354,10 @@ struct Workspace b_scaled.setZero(); u_scaled.setZero(); l_scaled.setZero(); + zeta_eq.setZero(); + zeta_in.setZero(); + nu_eq.setZero(); + nu_in.setZero(); Hdx.setZero(); Cdx.setZero(); Adx.setZero(); diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 8d2b3b47e..81a3a2832 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace proxsuite { namespace common { @@ -248,6 +249,50 @@ prepare_next_solve(pp::Results& qpresults, ppd::Workspace& qpwork) assert(!std::isnan(qpresults.info.dua_res)); assert(!std::isnan(qpresults.info.duality_gap)); } +/*! + * Setups and performs the factorization of the complete regularized KKT matrix + * of the problem (containing all of the equality and inequality constraints). + * It adds n_constraints (n_in + dim if box_constraints) rows/columns to the + * equality case KKT matrix. + * + * @param qpwork workspace of the solver. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solution results. + */ +template +void +setup_factorization_complete_kkt(ppd::Workspace& qpwork, + const ppd::Model& qpmodel, + pp::Results& qpresults, + const pp::DenseBackend dense_backend, + const plv::isize n_constraints) +{ + proxsuite::linalg::veg::dynstack::DynStackMut stack{ + proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut() + }; + + T mu_in_neg(-qpresults.info.mu_in); + plv::isize n = qpmodel.dim; + plv::isize n_eq = qpmodel.n_eq; + LDLT_TEMP_MAT_UNINIT( + T, new_cols, n + n_eq + n_constraints, n_constraints, stack); + + for (plv::isize k = 0; k < n_constraints; ++k) { + auto col = new_cols.col(k); + if (k >= qpmodel.n_in) { + col.head(n).setZero(); + col[k - qpmodel.n_in] = qpwork.i_scaled[k - qpmodel.n_in]; + } else { + col.head(n) = (qpwork.C_scaled.row(k)); + } + col.tail(n_eq + n_constraints).setZero(); + col[n + n_eq + k] = mu_in_neg; + } + qpwork.ldl.insert_block_at(n + n_eq, new_cols, stack); + + qpwork.n_c = n_constraints; +} /*! * Setups the solver. * In particular, it scales (Ruiz equilibration) the data, then @@ -394,6 +439,12 @@ setup_solver(const pp::Settings& qpsettings, dense_backend, hessian_type, qpresults); + switch (qp_solver) { + case common::QPSolver::OSQP: { + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } break; + } break; } case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { @@ -412,12 +463,19 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Build full KKT with inequality constraints + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } break; } break; } case pp::InitialGuessStatus::NO_INITIAL_GUESS: { + switch (qp_solver) { + case common::QPSolver::OSQP: { + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } break; + } break; } case pp::InitialGuessStatus::WARM_START: { @@ -436,7 +494,8 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Build full KKT with inequality constraints + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } break; } break; @@ -446,6 +505,12 @@ setup_solver(const pp::Settings& qpsettings, // std::cout << "i use previous solution" << std::endl; // meaningful for when one wants to warm start with previous result with // the same QP model + switch (qp_solver) { + case common::QPSolver::OSQP: { + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } break; + } break; } } @@ -462,6 +527,12 @@ setup_solver(const pp::Settings& qpsettings, dense_backend, hessian_type, qpresults); + switch (qp_solver) { + case common::QPSolver::OSQP: { + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } break; + } break; } case pp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT: { @@ -496,7 +567,8 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Build full KKT with inequality constraints + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } break; } break; @@ -504,6 +576,12 @@ setup_solver(const pp::Settings& qpsettings, case pp::InitialGuessStatus::NO_INITIAL_GUESS: { setup_factorization( qpwork, qpmodel, qpresults, dense_backend, hessian_type); + switch (qp_solver) { + case common::QPSolver::OSQP: { + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } break; + } break; } case pp::InitialGuessStatus::WARM_START: { @@ -535,7 +613,8 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Build full KKT with inequality constraints + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } break; } break; @@ -575,7 +654,8 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - // TODO: Build full KKT with enaqulity constraints + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } break; } break; @@ -584,6 +664,60 @@ setup_solver(const pp::Settings& qpsettings, } } } +/*! + * Computes the scaled primal residual for inequality. + * Used to define the sets of upper and lower inequality active constraints. + * + * @param qpsettings solver settings. + * @param qpmodel QP problem model. + * @param qpresults solver results. + * @param qpwork solver workspace. + */ +template +void +compute_scaled_primal_residual_ineq(const pp::Settings& qpsettings, + const ppd::Model& qpmodel, + pp::Results& qpresults, + ppd::Workspace& qpwork, + const bool box_constraints, + ppdp::RuizEquilibration& ruiz, + QPSolver qp_solver) +{ + ruiz.scale_primal_residual_in_place_in( + pp::VectorViewMut{ pp::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( + pp::VectorViewMut{ pp::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) + if (qp_solver == QPSolver::PROXQP) { + switch (qpsettings.merit_function_type) { + case pp::MeritFunctionType::GPDAL: + qpwork.primal_residual_in_scaled_up += + (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; + break; + case pp::MeritFunctionType::PDAL: + break; + } + } + qpresults.si = 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) + qpresults.si.head(qpmodel.n_in) -= + qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + if (box_constraints) { + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= + qpwork.u_box_scaled; // contains now scaled(Cx-u+z_prev*mu_in) + qpresults.si.tail(qpmodel.dim) -= + qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + } +} /*! * Computes the objective function. * @@ -674,8 +808,8 @@ unscale_solver(const pp::Settings& qpsettings, * @param qp_solver PROXQP or OSQP. */ template -void -compute_feasibility( // +bool +is_solved( // const pp::Settings& qpsettings, const ppd::Model& qpmodel, pp::Results& qpresults, @@ -696,8 +830,7 @@ compute_feasibility( // T& rhs_duality_gap, T& duality_gap, T& scaled_eps, - plv::i64 iter, - bool& stop_loop) + plv::i64 iter) { ppd::global_primal_residual(qpmodel, @@ -809,13 +942,14 @@ compute_feasibility( // } else { qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; } - stop_loop = true; + return true; } } else { qpresults.info.status = pp::QPSolverOutput::PROXQP_SOLVED; - stop_loop = true; + return true; } } + return false; } /*! * Computes residuals, the infeasibility and updates the solver's status. diff --git a/test/src/dense_qp_eq.cpp b/test/src/dense_qp_eq.cpp index 2a6aa2541..d79cc02c2 100644 --- a/test/src/dense_qp_eq.cpp +++ b/test/src/dense_qp_eq.cpp @@ -253,4 +253,37 @@ DOCTEST_TEST_CASE("infeasible qp") DOCTEST_CHECK(qp.results.info.status == proxsuite::proxqp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE); +} + +DOCTEST_TEST_CASE("dual infeasible qp equality constraints") +{ + Eigen::Matrix H; + H << 0.0, 0.0, 0.0, 0.0; + H = 2 * H; + + Eigen::Matrix g; + g << 1.0, -1.0; + + Eigen::Matrix A; + A << 1, 1, 1, 1; + + Eigen::Matrix b; + b << 1, 1; + + int n = H.rows(); + int n_in = 0; + int n_eq = A.rows(); + + bool box_constraints = false; + proxqp::dense::QP qp{ + n, n_eq, n_in, box_constraints, proxqp::DenseBackend::PrimalDualLDLT + }; // creating QP object + qp.init(H, g, A, b, nullopt, nullopt, nullopt); + qp.settings.eps_rel = 0.; + qp.settings.eps_abs = 1e-9; + + qp.solve(); + + DOCTEST_CHECK(qp.results.info.status == + proxsuite::proxqp::QPSolverOutput::PROXQP_DUAL_INFEASIBLE); } \ No newline at end of file diff --git a/test/src/osqp_dense_qp_eq.cpp b/test/src/osqp_dense_qp_eq.cpp index b481da453..afd847db6 100644 --- a/test/src/osqp_dense_qp_eq.cpp +++ b/test/src/osqp_dense_qp_eq.cpp @@ -216,43 +216,77 @@ DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " } } -// DOCTEST_TEST_CASE("infeasible qp") -// { -// // (x1- 9)^2 + (x2-6)^2 -// // s.t. -// // x1 <= 10 -// // x2 <= 10 -// // x1 >= 20 -// Eigen::Matrix H; -// H << 1.0, 0.0, 0.0, 1.0; -// H = 2 * H; - -// Eigen::Matrix g; -// g << -18.0, -12.0; - -// Eigen::Matrix C; -// C << 1, 0, // x1 <= 10 -// 0, 1, // x2 <= 10 -// -1, 0; // x1 >= 20 - -// Eigen::Matrix u; -// u << 10, 10, -20; - -// int n = H.rows(); -// int n_in = C.rows(); -// int n_eq = 0; - -// Eigen::Matrix l = -// Eigen::Matrix::Constant( -// n_in, -std::numeric_limits::infinity()); - -// proxsuite::pod::QP qp(n, n_eq, n_in); -// qp.init(H, g, nullopt, nullopt, C, l, u); -// qp.settings.eps_rel = 0.; -// qp.settings.eps_abs = 1e-9; - -// qp.solve(); - -// DOCTEST_CHECK(qp.results.info.status == -// proxsuite::proxqp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE); -// } \ No newline at end of file +DOCTEST_TEST_CASE("infeasible qp") +{ + // (x1- 9)^2 + (x2-6)^2 + // s.t. + // x1 <= 10 + // x2 <= 10 + // x1 >= 20 + Eigen::Matrix H; + H << 1.0, 0.0, 0.0, 1.0; + H = 2 * H; + + Eigen::Matrix g; + g << -18.0, -12.0; + + Eigen::Matrix C; + C << 1, 0, // x1 <= 10 + 0, 1, // x2 <= 10 + -1, 0; // x1 >= 20 + + Eigen::Matrix u; + u << 10, 10, -20; + + int n = H.rows(); + int n_in = C.rows(); + int n_eq = 0; + + Eigen::Matrix l = + Eigen::Matrix::Constant( + n_in, -std::numeric_limits::infinity()); + + pod::QP qp(n, n_eq, n_in); + qp.init(H, g, nullopt, nullopt, C, l, u); + qp.settings.eps_rel = 0.; + qp.settings.eps_abs = 1e-9; + + qp.solve(); + + DOCTEST_CHECK(qp.results.info.status == + proxsuite::proxqp::QPSolverOutput::PROXQP_PRIMAL_INFEASIBLE); +} + +DOCTEST_TEST_CASE("dual infeasible qp equality constraints") +{ + Eigen::Matrix H; + H << 0.0, 0.0, 0.0, 0.0; + H = 2 * H; + + Eigen::Matrix g; + g << 1.0, -1.0; + + Eigen::Matrix A; + A << 1, 1, 1, 1; + + Eigen::Matrix b; + b << 1, 1; + + int n = H.rows(); + int n_in = 0; + int n_eq = A.rows(); + + // osqp::dense::QP qp{ n, n_eq, n_in }; // creating QP object + bool box_constraints = false; + pod::QP qp{ + n, n_eq, n_in, box_constraints, proxqp::DenseBackend::PrimalDualLDLT + }; // creating QP object + qp.init(H, g, A, b, nullopt, nullopt, nullopt); + qp.settings.eps_rel = 0.; + qp.settings.eps_abs = 1e-9; + + qp.solve(); + + DOCTEST_CHECK(qp.results.info.status == + proxsuite::proxqp::QPSolverOutput::PROXQP_DUAL_INFEASIBLE); +} \ No newline at end of file From ed42ea29e21dfa9b5e9d0deff7dbe5ecd1d259fa Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Fri, 9 May 2025 17:46:09 +0200 Subject: [PATCH 14/27] format: Removed useless commentaries --- include/proxsuite/osqp/dense/solver.hpp | 22 ++------- include/proxsuite/proxqp/dense/solver.hpp | 58 ++--------------------- 2 files changed, 10 insertions(+), 70 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 8883a5923..c95bb1db6 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -316,9 +316,6 @@ qp_solve( // qpwork.y_prev = qpresults.y; qpwork.z_prev = qpresults.z; - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 1 - proxsuite::common::compute_scaled_primal_residual_ineq( qpsettings, qpmodel, @@ -328,9 +325,6 @@ qp_solve( // ruiz, common::QPSolver::OSQP); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 2 - admm_step(qpsettings, qpmodel, qpresults, @@ -352,9 +346,6 @@ qp_solve( // break; } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 3 - T primal_feasibility_lhs_new(primal_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, qpmodel, @@ -376,15 +367,12 @@ qp_solve( // duality_gap, scaled_eps); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 4 + ////////////////////////////////////////////////////////////////////////////////////////////// + /// mu update - // Update of mu - - } // End of loop of ADMM iterations - - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 5 + ////////////////////////////////////////////////////////////////////////////////////////////// + /// end of mu update + } proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 7b6749b87..139408afd 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1179,49 +1179,6 @@ qp_solve( // qpwork.y_prev = qpresults.y; qpwork.z_prev = qpresults.z; - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 1 - - // // primal dual version from gill and robinson - - // 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) - // switch (qpsettings.merit_function_type) { - // case MeritFunctionType::GPDAL: - // qpwork.primal_residual_in_scaled_up += - // (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; - // break; - // case MeritFunctionType::PDAL: - // break; - // } - // qpresults.si = 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) - // qpresults.si.head(qpmodel.n_in) -= - // qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_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) - // qpresults.si.tail(qpmodel.dim) -= - // qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) - // } - proxsuite::common::compute_scaled_primal_residual_ineq( qpsettings, qpmodel, @@ -1231,9 +1188,6 @@ qp_solve( // ruiz, common::QPSolver::PROXQP); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 2 - primal_dual_newton_semi_smooth(qpsettings, qpmodel, qpresults, @@ -1269,8 +1223,6 @@ qp_solve( // scaled_eps = infty_norm(qpwork.rhs.head(qpmodel.dim)) * qpsettings.eps_abs; } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 3 T primal_feasibility_lhs_new(primal_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, @@ -1293,8 +1245,8 @@ qp_solve( // duality_gap, scaled_eps); - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 4 + ////////////////////////////////////////////////////////////////////////////////////////////// + /// mu update if (qpsettings.bcl_update) { bcl_update(qpsettings, @@ -1376,10 +1328,10 @@ qp_solve( // qpresults.info.mu_in = new_bcl_mu_in; qpresults.info.mu_eq_inv = new_bcl_mu_eq_inv; qpresults.info.mu_in_inv = new_bcl_mu_in_inv; - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// 5 + ////////////////////////////////////////////////////////////////////////////////////////////// + /// end of mu update + } proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); From 1f7a6fa6ea3b74ca1a09575f20aca9154c498d5d Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Fri, 9 May 2025 18:32:01 +0200 Subject: [PATCH 15/27] Add test osqp_dense_qp_solve: Test on different values of mu_eq and mu_in fails because the solver is ADMM only (no polishing yet) --- test/CMakeLists.txt | 1 + test/src/osqp_dense_qp_solve.cpp | 390 +++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 test/src/osqp_dense_qp_solve.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bf68ccde0..96f9e3c92 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,6 +42,7 @@ endmacro() proxsuite_test(osqp_dense_ruiz_equilibration src/osqp_dense_ruiz_equilibration.cpp) proxsuite_test(osqp_dense_qp_eq src/osqp_dense_qp_eq.cpp) proxsuite_test(osqp_dense_qp_unconstrained src/osqp_dense_unconstrained_qp.cpp) +proxsuite_test(osqp_dense_qp_solve src/osqp_dense_qp_solve.cpp) proxsuite_test(dense_ruiz_equilibration src/dense_ruiz_equilibration.cpp) proxsuite_test(dense_qp_eq src/dense_qp_eq.cpp) proxsuite_test(dense_qp_with_eq_and_in src/dense_qp_with_eq_and_in.cpp) diff --git a/test/src/osqp_dense_qp_solve.cpp b/test/src/osqp_dense_qp_solve.cpp new file mode 100644 index 000000000..eeab61adf --- /dev/null +++ b/test/src/osqp_dense_qp_solve.cpp @@ -0,0 +1,390 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; + +namespace pp = proxsuite::proxqp; +namespace ppd = proxsuite::proxqp::dense; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") +{ + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(5), n_in(2); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + Eigen::Matrix H = qp.H; + Eigen::Matrix g = qp.g; + Eigen::Matrix A = qp.A; + Eigen::Matrix b = qp.b; + Eigen::Matrix C = qp.C; + Eigen::Matrix l = qp.l; + Eigen::Matrix u = qp.u; + + { + pp::Results results = + pod::solve(H, g, A, b, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); + + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; + } + + { + pod::QP qp_problem(dim, n_eq, 0); + qp_problem.init(H, g, A, b, nullopt, nullopt, nullopt); + qp_problem.settings.eps_abs = eps_abs; + qp_problem.solve(); + + const pp::Results& results = qp_problem.results; + + T pri_res = (qp.A * results.x - qp.b).lpNorm(); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; + } +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test solve function") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve function---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0); + + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different rho value") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different rho value---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + T(1.E-7)); + DOCTEST_CHECK(results.info.rho == T(1.E-7)); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different mu_eq and mu_in values") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different mu_eq and " + "mu_in values---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1.E-2), + T(1.E-2)); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + auto x_wm = pp::utils::rand::vector_rand(dim); + auto y_wm = pp::utils::rand::vector_rand(n_eq); + auto z_wm = pp::utils::rand::vector_rand(n_in); + pp::Results results = pod::solve( + qp.H, qp.g, qp.A, qp.b, qp.C, qp.l, qp.u, x_wm, y_wm, z_wm, eps_abs, 0); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test verbose = true") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test verbose = true ---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + bool verbose = true; + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + nullopt, + nullopt, + verbose); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test no initial guess") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test no initial guess ---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::InitialGuessStatus initial_guess = + pp::InitialGuessStatus::NO_INITIAL_GUESS; + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + nullopt, + nullopt, + nullopt, + true, + true, + nullopt, + initial_guess); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} From 9b5314740272bf94bfc5047bc285e806fc33afe3 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Fri, 9 May 2025 21:00:07 +0200 Subject: [PATCH 16/27] /test: Inequality without mu update and polishing. Remaining failed tests to fix with mu update, polishing or debugging --- examples/cpp/osqp_initializing_with_none.cpp | 3 + ...sqp_initializing_with_none_without_api.cpp | 41 ++- examples/cpp/osqp_overview-simple.cpp | 4 + include/proxsuite/proxqp/settings.hpp | 2 +- test/CMakeLists.txt | 2 + test/src/osqp_cvxpy.cpp | 199 ++++++++++++ test/src/osqp_dense_qp_eq.cpp | 12 + test/src/osqp_dense_qp_solve.cpp | 83 +++-- test/src/osqp_dense_qp_with_eq_and_in.cpp | 291 ++++++++++++++++++ test/src/osqp_dense_ruiz_equilibration.cpp | 2 + test/src/osqp_dense_unconstrained_qp.cpp | 8 + 11 files changed, 613 insertions(+), 34 deletions(-) create mode 100644 test/src/osqp_cvxpy.cpp create mode 100644 test/src/osqp_dense_qp_with_eq_and_in.cpp diff --git a/examples/cpp/osqp_initializing_with_none.cpp b/examples/cpp/osqp_initializing_with_none.cpp index 974da2b4e..fa761d485 100644 --- a/examples/cpp/osqp_initializing_with_none.cpp +++ b/examples/cpp/osqp_initializing_with_none.cpp @@ -21,6 +21,9 @@ main() dense::Model qp_random = utils::dense_strongly_convex_qp( dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, qp_random.g, qp_random.A, diff --git a/examples/cpp/osqp_initializing_with_none_without_api.cpp b/examples/cpp/osqp_initializing_with_none_without_api.cpp index 3babe0d37..b40576dda 100644 --- a/examples/cpp/osqp_initializing_with_none_without_api.cpp +++ b/examples/cpp/osqp_initializing_with_none_without_api.cpp @@ -1,33 +1,48 @@ #include +#include "proxsuite/helpers/optional.hpp" #include "proxsuite/osqp/dense/dense.hpp" #include // used for generating a random convex qp -using namespace proxsuite::proxqp; +using namespace proxsuite; using T = double; + +namespace pp = proxsuite::proxqp; +namespace ppd = proxsuite::proxqp::dense; namespace pod = proxsuite::osqp::dense; int main() { - dense::isize dim = 10; - dense::isize n_eq(0); - dense::isize n_in(0); + ppd::isize dim = 10; + ppd::isize n_eq(0); + ppd::isize n_in(0); T strong_convexity_factor(0.1); T sparsity_factor(0.15); // we generate a qp, so the function used from helpers.hpp is // in proxqp namespace. The qp is in dense eigen format and // you can control its sparsity ratio and strong convexity factor. - dense::Model qp_random = utils::dense_strongly_convex_qp( + ppd::Model qp_random = pp::utils::dense_strongly_convex_qp( dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - Results results = - pod::solve(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); // initialization with zero shape matrices + T eps_abs(1.E-5); + T eps_rel(0); + pp::Results results = pod::solve(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + nullopt, + nullopt, + nullopt, + eps_abs, + eps_rel, + nullopt, + T(1.E-2), + T(1.E-1)); + + // initialization with zero shape matrices // it is equivalent to do dense::solve(qp_random.H, qp_random.g, // nullopt,nullopt,nullopt,nullopt,nullopt); // print an optimal solution x,y and z diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp index 5d3245f24..b043e8d65 100644 --- a/examples/cpp/osqp_overview-simple.cpp +++ b/examples/cpp/osqp_overview-simple.cpp @@ -22,6 +22,10 @@ main() dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); pod::QP qp(dim, n_eq, n_in); + + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, qp_random.g, qp_random.A, diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index 630a94b29..1a2533a20 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -239,7 +239,7 @@ struct Settings isize safe_guard = 1.E4, isize nb_iterative_refinement = 10, T eps_refact = 1.e-6, // before eps_refact_=1.e-6 - bool verbose = false, + bool verbose = true, InitialGuessStatus initial_guess = InitialGuessStatus:: EQUALITY_CONSTRAINED_INITIAL_GUESS, // default to // EQUALITY_CONSTRAINED_INITIAL_GUESS, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 96f9e3c92..bdf78a543 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,6 +43,8 @@ proxsuite_test(osqp_dense_ruiz_equilibration src/osqp_dense_ruiz_equilibration.c proxsuite_test(osqp_dense_qp_eq src/osqp_dense_qp_eq.cpp) proxsuite_test(osqp_dense_qp_unconstrained src/osqp_dense_unconstrained_qp.cpp) proxsuite_test(osqp_dense_qp_solve src/osqp_dense_qp_solve.cpp) +proxsuite_test(osqp_dense_qp_with_eq_and_in src/osqp_dense_qp_with_eq_and_in.cpp) +proxsuite_test(osqp_cvxpy src/osqp_cvxpy.cpp) proxsuite_test(dense_ruiz_equilibration src/dense_ruiz_equilibration.cpp) proxsuite_test(dense_qp_eq src/dense_qp_eq.cpp) proxsuite_test(dense_qp_with_eq_and_in src/dense_qp_with_eq_and_in.cpp) diff --git a/test/src/osqp_cvxpy.cpp b/test/src/osqp_cvxpy.cpp new file mode 100644 index 000000000..7bdada706 --- /dev/null +++ b/test/src/osqp_cvxpy.cpp @@ -0,0 +1,199 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; + +namespace pp = proxsuite::proxqp; +namespace ppd = proxsuite::proxqp::dense; +namespace pod = proxsuite::osqp::dense; + +template +using Mat = + Eigen::Matrix; +template +using Vec = Eigen::Matrix; + +DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility") +{ + + std::cout << "---3 dim test case from cvxpy, check feasibility " << std::endl; + T eps_abs = T(1e-9); + ppd::isize dim = 3; + + Mat H = Mat(dim, dim); + H << 13.0, 12.0, -2.0, 12.0, 17.0, 6.0, -2.0, 6.0, 12.0; + + Vec g = Vec(dim); + g << -22.0, -14.5, 13.0; + + Mat C = Mat(dim, dim); + C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0; + + Vec l = Vec(dim); + l << -1.0, -1.0, -1.0; + + Vec u = Vec(dim); + u << 1.0, 1.0, 1.0; + // pp::Results results = pod::solve( + // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); + pp::Results results = pod::solve(H, + g, + nullopt, + nullopt, + C, + l, + u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + T(1.E-6), + T(1.E-2), + T(1.E1)); + + T pri_res = (helpers::positive_part(C * results.x - u) + + helpers::negative_part(C * results.x - l)) + .lpNorm(); + T dua_res = + (H * results.x + g + C.transpose() * results.z).lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") +{ + + std::cout << "---simple test case from cvxpy, check feasibility " + << std::endl; + T eps_abs = T(1e-8); + ppd::isize dim = 1; + + Mat H = Mat(dim, dim); + H << 20.0; + + Vec g = Vec(dim); + g << -10.0; + + Mat C = Mat(dim, dim); + C << 1.0; + + Vec l = Vec(dim); + l << 0.0; + + Vec u = Vec(dim); + u << 1.0; + // pp::Results results = pod::solve( + // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); + pp::Results results = pod::solve(H, + g, + nullopt, + nullopt, + C, + l, + u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + T(1.E-6), + T(1.E-2), + T(1.E1)); + + T pri_res = (helpers::positive_part(C * results.x - u) + + helpers::negative_part(C * results.x - l)) + .lpNorm(); + T dua_res = + (H * results.x + g + C.transpose() * results.z).lpNorm(); + T x_sol = 0.5; + + DOCTEST_CHECK((x_sol - results.x.coeff(0, 0)) <= eps_abs); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check that " + "solver stays there") +{ + + std::cout << "---simple test case from cvxpy, init with solution, check that " + "solver stays there" + << std::endl; + T eps_abs = T(1e-4); + ppd::isize dim = 1; + + Mat H = Mat(dim, dim); + H << 20.0; + + Vec g = Vec(dim); + g << -10.0; + + Mat C = Mat(dim, dim); + C << 1.0; + + Vec l = Vec(dim); + l << 0.0; + + Vec u = Vec(dim); + u << 1.0; + + T x_sol = 0.5; + + proxqp::isize n_in(1); + proxqp::isize n_eq(0); + pod::QP qp{ dim, n_eq, n_in }; + qp.settings.eps_abs = eps_abs; + + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + + qp.init(H, g, nullopt, nullopt, C, u, l); + + ppd::Vec x = ppd::Vec(dim); + ppd::Vec z = ppd::Vec(n_in); + x << 0.5; + z << 0.0; + qp.solve(x, nullopt, z); + + T pri_res = (helpers::positive_part(C * qp.results.x - u) + + helpers::negative_part(C * qp.results.x - l)) + .lpNorm(); + T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) + .lpNorm(); + + DOCTEST_CHECK(qp.results.info.iter <= 0); + DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} diff --git a/test/src/osqp_dense_qp_eq.cpp b/test/src/osqp_dense_qp_eq.cpp index afd847db6..872aeb61c 100644 --- a/test/src/osqp_dense_qp_eq.cpp +++ b/test/src/osqp_dense_qp_eq.cpp @@ -48,6 +48,8 @@ DOCTEST_TEST_CASE("qp: start from solution using the wrapper framework") pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; qp.settings.initial_guess = proxsuite::proxqp::InitialGuessStatus::WARM_START; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(H, g, A, b, C, l, u); qp.solve(primal_solution, dual_solution, dual_init_in); @@ -74,6 +76,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality constraints " dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -127,6 +131,8 @@ DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -186,6 +192,8 @@ DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " }; // creating QP object qp.settings.eps_abs = eps_abs; qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -247,6 +255,8 @@ DOCTEST_TEST_CASE("infeasible qp") n_in, -std::numeric_limits::infinity()); pod::QP qp(n, n_eq, n_in); + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(H, g, nullopt, nullopt, C, l, u); qp.settings.eps_rel = 0.; qp.settings.eps_abs = 1e-9; @@ -281,6 +291,8 @@ DOCTEST_TEST_CASE("dual infeasible qp equality constraints") pod::QP qp{ n, n_eq, n_in, box_constraints, proxqp::DenseBackend::PrimalDualLDLT }; // creating QP object + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(H, g, A, b, nullopt, nullopt, nullopt); qp.settings.eps_rel = 0.; qp.settings.eps_abs = 1e-9; diff --git a/test/src/osqp_dense_qp_solve.cpp b/test/src/osqp_dense_qp_solve.cpp index eeab61adf..1223c39b9 100644 --- a/test/src/osqp_dense_qp_solve.cpp +++ b/test/src/osqp_dense_qp_solve.cpp @@ -37,8 +37,21 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") Eigen::Matrix u = qp.u; { - pp::Results results = - pod::solve(H, g, A, b, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); + pp::Results results = pod::solve(H, + g, + A, + b, + C, + l, + u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1)); T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + @@ -54,7 +67,7 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter + std::cout << "total number of iteration: " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -62,6 +75,8 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") { pod::QP qp_problem(dim, n_eq, 0); + qp_problem.settings.default_mu_eq = T(1.E-2); + qp_problem.settings.default_mu_in = T(1.E1); qp_problem.init(H, g, A, b, nullopt, nullopt, nullopt); qp_problem.settings.eps_abs = eps_abs; qp_problem.solve(); @@ -78,7 +93,7 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter + std::cout << "total number of iteration: " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -113,7 +128,10 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " nullopt, nullopt, eps_abs, - 0); + 0, + nullopt, + T(1E-2), + T(1E1)); T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + @@ -129,7 +147,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } @@ -163,7 +182,9 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " nullopt, eps_abs, 0, - T(1.E-7)); + T(1.E-7), + T(1E-2), + T(1E1)); DOCTEST_CHECK(results.info.rho == T(1.E-7)); T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + @@ -179,7 +200,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } @@ -216,8 +238,12 @@ DOCTEST_TEST_CASE( eps_abs, 0, nullopt, - T(1.E-2), - T(1.E-2)); + T(1.E-1), + T(1.E0)); + // Note: + // Different alternative values than for proxqp, due to different + // suggestions for default_mu_eq and default_mu_in between proxqp paper + // and osqp paper - but same update (*10 for mu_eq and /10 for mu_in) T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + helpers::negative_part(qp.C * results.x - qp.l)) @@ -232,7 +258,8 @@ DOCTEST_TEST_CASE( << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } @@ -257,8 +284,21 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " auto x_wm = pp::utils::rand::vector_rand(dim); auto y_wm = pp::utils::rand::vector_rand(n_eq); auto z_wm = pp::utils::rand::vector_rand(n_in); - pp::Results results = pod::solve( - qp.H, qp.g, qp.A, qp.b, qp.C, qp.l, qp.u, x_wm, y_wm, z_wm, eps_abs, 0); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + x_wm, + y_wm, + z_wm, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1)); T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + helpers::negative_part(qp.C * results.x - qp.l)) @@ -273,7 +313,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } @@ -309,8 +350,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " eps_abs, 0, nullopt, - nullopt, - nullopt, + T(1E-2), + T(1E1), verbose); T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), (helpers::positive_part(qp.C * results.x - qp.u) + @@ -326,7 +367,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } @@ -363,8 +405,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " eps_abs, 0, nullopt, - nullopt, - nullopt, + T(1E-2), + T(1E1), nullopt, true, true, @@ -384,7 +426,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter << std::endl; + std::cout << "total number of iteration: " << results.info.iter_ext + << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp new file mode 100644 index 000000000..a3d095bc2 --- /dev/null +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -0,0 +1,291 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and inequality constraints " + "and increasing dimension using wrapper API") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints and increasing dimension using wrapper API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + + proxqp::isize n_eq(dim / 4); + proxqp::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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " + "constraints and increasing dimension using the API") +{ + + std::cout + << "---testing sparse random strongly convex qp with box inequality " + "constraints and increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + + proxqp::isize n_eq(0); + proxqp::isize n_in(dim); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_box_constrained_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " + "constraints and increasing dimension using the API") +{ + + std::cout + << "---testing sparse random not strongly convex qp with inequality " + "constraints and increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + proxqp::isize n_in(dim / 2); + proxqp::isize n_eq(0); + proxqp::dense::Model qp_random = + proxqp::utils::dense_not_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter_ext + << std::endl; + } +} + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate +// inequality " +// "constraints and increasing dimension using the API") +// { + +// std::cout +// << "---testing sparse random strongly convex qp with degenerate " +// "inequality constraints and increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.45; +// T eps_abs = T(1e-9); +// T strong_convexity_factor(1e-2); +// proxqp::utils::rand::set_seed(1); +// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// proxqp::isize m(dim / 4); +// proxqp::isize n_in(2 * m); +// proxqp::isize n_eq(0); +// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( +// dim, +// n_eq, +// m, // it n_in = 2 * m, it doubles the inequality constraints +// sparsity_factor, +// strong_convexity_factor); +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// qp.solve(); +// DOCTEST_CHECK(qp.results.info.status == +// proxqp::QPSolverOutput::PROXQP_SOLVED); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration: " << qp.results.info.iter_ext +// << std::endl; +// } +// } + +// DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " +// "increasing dimension using the API") +// { +// srand(1); +// std::cout << "---testing linear problem with inequality constraints and " +// "increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// proxqp::utils::rand::set_seed(1); +// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// proxqp::isize n_in(dim / 2); +// proxqp::isize n_eq(0); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_not_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor); +// qp_random.H.setZero(); +// auto z_sol = proxqp::utils::rand::vector_rand(n_in); +// qp_random.g = -qp_random.C.transpose() * +// z_sol; // make sure the LP is bounded within the feasible +// set +// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l +// " +// // << qp.l << std::endl; +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// // qp.settings.verbose = false; +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration: " << qp.results.info.iter_ext +// << std::endl; +// } +// } diff --git a/test/src/osqp_dense_ruiz_equilibration.cpp b/test/src/osqp_dense_ruiz_equilibration.cpp index 56d355c23..01634b459 100644 --- a/test/src/osqp_dense_ruiz_equilibration.cpp +++ b/test/src/osqp_dense_ruiz_equilibration.cpp @@ -42,6 +42,8 @@ DOCTEST_TEST_CASE("ruiz preconditioner") } } pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.default_mu_eq = Scalar(1.E-2); + qp.settings.default_mu_in = Scalar(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, diff --git a/test/src/osqp_dense_unconstrained_qp.cpp b/test/src/osqp_dense_unconstrained_qp.cpp index 3eadd236d..8a7b5be74 100644 --- a/test/src/osqp_dense_unconstrained_qp.cpp +++ b/test/src/osqp_dense_unconstrained_qp.cpp @@ -31,6 +31,8 @@ DOCTEST_TEST_CASE( dim, sparsity_factor, strong_convexity_factor); pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -84,6 +86,8 @@ DOCTEST_TEST_CASE("sparse random not strongly convex unconstrained qp and " pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -132,6 +136,8 @@ DOCTEST_TEST_CASE("unconstrained qp with H = Id and g random") pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -180,6 +186,8 @@ DOCTEST_TEST_CASE("unconstrained qp with H = Id and g = 0") pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); qp.init(qp_random.H, qp_random.g, qp_random.A, From 32dd134e688529123890a9157b96913cfbca7695 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Thu, 15 May 2025 09:11:26 +0200 Subject: [PATCH 17/27] osqp/: mu_update: Code done with time condition transformed into iteration condition. Also remains to pass tests and benchmarks. --- examples/cpp/osqp_overview-simple.cpp | 59 +++-- include/proxsuite/linalg/dense/ldlt.hpp | 28 +++ include/proxsuite/osqp/dense/solver.hpp | 247 ++++++++++++++++++- include/proxsuite/proxqp/dense/solver.hpp | 28 +-- include/proxsuite/proxqp/dense/workspace.hpp | 24 ++ include/proxsuite/proxqp/settings.hpp | 116 ++++++++- include/proxsuite/solvers/common/utils.hpp | 213 ++++++++++++++-- test/src/osqp_dense_qp_with_eq_and_in.cpp | 3 +- 8 files changed, 640 insertions(+), 78 deletions(-) diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp index b043e8d65..0791f12cc 100644 --- a/examples/cpp/osqp_overview-simple.cpp +++ b/examples/cpp/osqp_overview-simple.cpp @@ -1,7 +1,10 @@ #include #include +#include #include // used for generating a random convex qp +#include + using namespace proxsuite::osqp; using T = double; @@ -13,7 +16,7 @@ int main() { T sparsity_factor = 0.15; - ppd::isize dim = 10; + ppd::isize dim = 110; ppd::isize n_eq(dim / 4); ppd::isize n_in(dim / 4); T strong_convexity_factor(1.e-2); @@ -21,21 +24,47 @@ main() ppd::Model qp_random = pp::utils::dense_strongly_convex_qp( dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pod::QP qp(dim, n_eq, n_in); + // // Proxqp + // ppd::QP qp_proxqp(dim, n_eq, n_in); + + // qp_proxqp.init(qp_random.H, + // qp_random.g, + // qp_random.A, + // qp_random.b, + // qp_random.C, + // qp_random.l, + // qp_random.u); + // qp_proxqp.solve(); + + // // Osqp without update + // pod::QP qp_osqp(dim, n_eq, n_in); + + // qp_osqp.settings.default_mu_eq = T(1.E-2); + // qp_osqp.settings.default_mu_in = T(1.E1); + + // qp_osqp.init(qp_random.H, + // qp_random.g, + // qp_random.A, + // qp_random.b, + // qp_random.C, + // qp_random.l, + // qp_random.u); + // qp_osqp.solve(); + + // qp_osqp_1 with mu_update + // pod::QP qp_osqp_1(dim, n_eq, n_in); - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); + // qp_osqp_1.settings.default_mu_eq = T(1.E-2); + // qp_osqp_1.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp.solve(); + // qp_osqp_1.settings.update_mu = true; - 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; + // qp_osqp_1.init(qp_random.H, + // qp_random.g, + // qp_random.A, + // qp_random.b, + // qp_random.C, + // qp_random.l, + // qp_random.u); + // qp_osqp_1.solve(); } diff --git a/include/proxsuite/linalg/dense/ldlt.hpp b/include/proxsuite/linalg/dense/ldlt.hpp index b74bde51a..dec06338d 100644 --- a/include/proxsuite/linalg/dense/ldlt.hpp +++ b/include/proxsuite/linalg/dense/ldlt.hpp @@ -11,6 +11,9 @@ #include "proxsuite/linalg/dense/solve.hpp" #include +#include "proxsuite/proxqp/timings.hpp" +#include + namespace proxsuite { namespace linalg { namespace dense { @@ -718,29 +721,54 @@ struct Ldlt void factorize(Eigen::Ref mat /* NOLINT */, proxsuite::linalg::veg::dynstack::DynStackMut stack) { + // proxsuite::proxqp::Timer timer; + VEG_ASSERT(mat.rows() == mat.cols()); isize n = mat.rows(); + // timer.stop(); + // timer.start(); reserve_uninit(n); + // timer.stop(); + // std::cout << "Time of reserve_unit: " << timer.elapsed().user << + // std::endl; // Diff + // timer.start(); perm.resize_for_overwrite(n); perm_inv.resize_for_overwrite(n); maybe_sorted_diag.resize_for_overwrite(n); + // timer.stop(); + // std::cout << "Time of resize_for_overwrite: " << timer.elapsed().user << + // std::endl; // Diff + // timer.start(); proxsuite::linalg::dense::_detail::compute_permutation( // perm.ptr_mut(), perm_inv.ptr_mut(), util::diagonal(mat)); + // timer.stop(); + // std::cout << "Time of compute_permutation: " << timer.elapsed().user << + // std::endl; // Diff + // timer.start(); { LDLT_TEMP_MAT_UNINIT(T, work, n, n, stack); ld_col_mut() = mat; proxsuite::linalg::dense::_detail::apply_permutation_tri_lower( ld_col_mut(), work, perm.ptr()); } + // timer.stop(); + // std::cout << "Time of apply_permutation_tri_lower: " << + // timer.elapsed().user << std::endl; // Same timer.start(); for (isize i = 0; i < n; ++i) { maybe_sorted_diag[i] = ld_col()(i, i); } + // timer.stop(); + // std::cout << "Time of ld_col: " << timer.elapsed().user << std::endl; + // timer.start(); proxsuite::linalg::dense::factorize(ld_col_mut(), stack); + // timer.stop(); + // std::cout << "Time of proxsuite::lialg::dense::factorize: " << + // timer.elapsed().user << std::endl; // Diff } /*! diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index c95bb1db6..2fe545650 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -29,6 +29,207 @@ namespace dense { using namespace proxsuite::proxqp; using namespace proxsuite::proxqp::dense; +/*! + * Computes the scaled primal - dual residual ratio to update mu in OSQP. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +T +compute_update_ratio_primal_dual(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + const DenseBackend dense_backend, + const HessianType hessian_type) +{ + proxsuite::common::global_primal_residual_scaled( + qpmodel, qpresults, qpwork, box_constraints); + T norm_primal_residual_scaled = infty_norm(qpwork.primal_residual_scaled); + + proxsuite::common::global_dual_residual_scaled( + qpresults, qpwork, qpmodel, box_constraints, hessian_type); + T norm_dual_residual_scaled = infty_norm(qpwork.dual_residual_scaled); + + T epsilon = 1e-10; + + T norm_Ax = infty_norm(qpwork.A_scaled * qpresults.x); + T norm_Cx = infty_norm(qpwork.C_scaled * qpresults.x); + if (box_constraints) { + norm_Cx = std::max( + norm_Cx, + infty_norm((qpwork.i_scaled.array() * qpresults.x.array()).matrix())); + } + T norm_zeta = + std::max(infty_norm(qpwork.zeta_eq), infty_norm(qpwork.zeta_in)); + T max_Ax_Cx = std::max(norm_Ax, norm_Cx); + T max_scale_primal = std::max(max_Ax_Cx, norm_zeta); + T primal_term = norm_primal_residual_scaled / (max_scale_primal + epsilon); + + T norm_Hx; + switch (hessian_type) { + case HessianType::Zero: + norm_Hx = 0; + break; + case HessianType::Dense: + norm_Hx = infty_norm( + qpwork.H_scaled.template selfadjointView() * qpresults.x); + break; + case HessianType::Diagonal: + norm_Hx = infty_norm( + (qpwork.H_scaled.diagonal().array() * qpresults.x.array()).matrix()); + break; + } + T norm_ATy = infty_norm(qpwork.A_scaled.transpose() * qpresults.y); + T norm_CTz = + infty_norm(qpwork.C_scaled.transpose() * qpresults.z.head(qpmodel.n_in)); + if (box_constraints) { + norm_CTz = std::max(norm_Cx, + infty_norm((qpwork.i_scaled.array() * + qpresults.z.tail(qpmodel.dim).array()) + .matrix())); + } + T norm_g = infty_norm(qpwork.g_scaled); + T max_Hx_g = std::max(norm_Hx, norm_g); + T max_ATy_CTz = std::max(norm_ATy, norm_CTz); + T max_scale_dual = std::max(max_Hx_g, max_ATy_CTz); + T dual_term = norm_dual_residual_scaled / (max_scale_dual + epsilon); + + T update_ratio_primal_dual = std::sqrt(primal_term / (dual_term + epsilon)); + return update_ratio_primal_dual; +} +/*! + * Updates the proximal parameters mu_eq and mu_in in the OSQP algorithm. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +update_mu(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + const DenseBackend dense_backend, + const HessianType hessian_type, + T& primal_feasibility_lhs, + T& primal_feasibility_lhs_new, + T& dual_feasibility_lhs, + T& dual_feasibility_lhs_new, + T& new_mu_eq, + T& new_mu_in, + T& new_mu_eq_inv, + T& new_mu_in_inv, + i64 iter) +{ + bool iteration_condition; + switch (qpsettings.update_mu_iteration_criteria) { + case UpdateMuIterationCriteria::FactorizationTime: { + if (iter == 0) { + qpwork.timer_between_updates.stop(); + qpwork.timer_between_updates.start(); + qpwork.time_since_last_update_mu = + qpwork.timer_between_updates.elapsed().user; + } else { + qpwork.time_since_last_update_mu = + qpwork.timer_between_updates.elapsed().user; + } + iteration_condition = qpwork.time_since_last_update_mu > + qpsettings.percentage_factorization_time_update_mu * + qpwork.factorization_time_complete_kkt; + break; + } + case UpdateMuIterationCriteria::FixedNumberIterations: { + iteration_condition = + iter - qpwork.last_iteration_update_mu > qpsettings.interval_update_mu; + } + } + + if (iteration_condition) { + T update_ratio_primal_dual = + compute_update_ratio_primal_dual(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type); + // std::cout << "update_ratio_update_mu :" << update_ratio_primal_dual << + // std::endl; + + bool value_condition = + update_ratio_primal_dual > qpsettings.threshold_ratio_update_mu || + update_ratio_primal_dual < qpsettings.threshold_ratio_update_mu_inv; + + if (value_condition) { + new_mu_eq = qpresults.info.mu_eq / update_ratio_primal_dual; + new_mu_in = qpresults.info.mu_in / update_ratio_primal_dual; + new_mu_eq_inv = qpresults.info.mu_eq_inv * update_ratio_primal_dual; + new_mu_in_inv = qpresults.info.mu_in_inv * update_ratio_primal_dual; + + new_mu_eq = std::min(std::max(new_mu_eq, qpsettings.mu_min_eq), + qpsettings.mu_max_eq); + new_mu_in = std::min(std::max(new_mu_in, qpsettings.mu_min_in_osqp), + qpsettings.mu_max_in); + new_mu_eq_inv = + std::min(std::max(new_mu_eq_inv, qpsettings.mu_min_eq_inv), + qpsettings.mu_max_eq_inv); + new_mu_in_inv = + std::min(std::max(new_mu_in_inv, qpsettings.mu_min_in_inv), + qpsettings.mu_max_in_inv_osqp); + } + + if (primal_feasibility_lhs_new >= primal_feasibility_lhs - 1e-6 && + dual_feasibility_lhs_new >= dual_feasibility_lhs - 1e-6 && + qpresults.info.mu_in <= T(1e-3)) { + new_mu_in = qpsettings.cold_reset_mu_in_osqp; + new_mu_eq = qpsettings.cold_reset_mu_eq_osqp; + new_mu_in_inv = qpsettings.cold_reset_mu_in_inv_osqp; + new_mu_eq_inv = qpsettings.cold_reset_mu_eq_inv_osqp; + } + + if (qpresults.info.mu_in != new_mu_in || + qpresults.info.mu_eq != new_mu_eq) { + { + ++qpresults.info.mu_updates; + } + mu_update(qpmodel, + qpresults, + qpwork, + n_constraints, + dense_backend, + new_mu_eq, + new_mu_in); + switch (qpsettings.update_mu_iteration_criteria) { + case UpdateMuIterationCriteria::FactorizationTime: { + qpwork.timer_between_updates.stop(); + qpwork.timer_between_updates.start(); + break; + } + case UpdateMuIterationCriteria::FixedNumberIterations: { + qpwork.last_iteration_update_mu = iter; + break; + } + } + qpresults.info.mu_eq = new_mu_eq; + qpresults.info.mu_in = new_mu_in; + qpresults.info.mu_eq_inv = new_mu_eq_inv; + qpresults.info.mu_in_inv = new_mu_in_inv; + } + } +} /*! * Checks the feasibility of the problem at the current step of the ADMM (OSQP) * solver. @@ -284,6 +485,16 @@ qp_solve( // for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { + T new_mu_in(qpresults.info.mu_in); + T new_mu_eq(qpresults.info.mu_eq); + T new_mu_in_inv(qpresults.info.mu_in_inv); + T new_mu_eq_inv(qpresults.info.mu_eq_inv); + + // proxsuite::proxqp::Timer timer_admm_iter; + // timer_admm_iter.stop(); + // std::cout << "Time iter: " << timer_admm_iter.elapsed().user << + // std::endl; timer_admm_iter.start(); + bool is_solved_qp = proxsuite::common::is_solved(qpsettings, qpmodel, @@ -346,7 +557,12 @@ qp_solve( // break; } + qpwork.active_set_up.array() = + (qpwork.primal_residual_in_scaled_up.array() >= 0); + qpwork.active_set_low.array() = (qpresults.si.array() <= 0); + T primal_feasibility_lhs_new(primal_feasibility_lhs); + T dual_feasibility_lhs_new(dual_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, qpmodel, qpresults, @@ -359,7 +575,7 @@ qp_solve( // primal_feasibility_eq_lhs, primal_feasibility_in_lhs, primal_feasibility_lhs_new, - dual_feasibility_lhs, + dual_feasibility_lhs_new, dual_feasibility_rhs_0, dual_feasibility_rhs_1, dual_feasibility_rhs_3, @@ -367,23 +583,38 @@ qp_solve( // duality_gap, scaled_eps); - ////////////////////////////////////////////////////////////////////////////////////////////// - /// mu update + if (qpsettings.update_mu) { + update_mu(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + primal_feasibility_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs, + dual_feasibility_lhs_new, + new_mu_eq, + new_mu_in, + new_mu_eq_inv, + new_mu_in_inv, + iter); + } - ////////////////////////////////////////////////////////////////////////////////////////////// - /// end of mu update - } + } // outer iterations loop proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); proxsuite::common::compute_objective(qpmodel, qpresults); if (qpsettings.compute_timings) { - proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); + proxsuite::common::compute_timings(qpresults, qpwork); } if (qpsettings.verbose) { proxsuite::common::print_solver_statistics( - qpsettings, qpresults, common::QPSolver::PROXQP); + qpsettings, qpresults, common::QPSolver::OSQP); } proxsuite::common::prepare_next_solve(qpresults, qpwork); diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 139408afd..3a9eee0c9 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1225,6 +1225,7 @@ qp_solve( // } T primal_feasibility_lhs_new(primal_feasibility_lhs); + T dual_feasibility_lhs_new(dual_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, qpmodel, qpresults, @@ -1237,7 +1238,7 @@ qp_solve( // primal_feasibility_eq_lhs, primal_feasibility_in_lhs, primal_feasibility_lhs_new, - dual_feasibility_lhs, + dual_feasibility_lhs_new, dual_feasibility_rhs_0, dual_feasibility_rhs_1, dual_feasibility_rhs_3, @@ -1245,9 +1246,6 @@ qp_solve( // duality_gap, scaled_eps); - ////////////////////////////////////////////////////////////////////////////////////////////// - /// mu update - if (qpsettings.bcl_update) { bcl_update(qpsettings, qpresults, @@ -1276,23 +1274,6 @@ qp_solve( // } // COLD RESTART - T dual_feasibility_lhs_new(dual_feasibility_lhs); - - global_dual_residual(qpresults, - qpwork, - qpmodel, - box_constraints, - ruiz, - dual_feasibility_lhs_new, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - hessian_type); - qpresults.info.dua_res = dual_feasibility_lhs_new; - qpresults.info.duality_gap = duality_gap; - if (primal_feasibility_lhs_new >= primal_feasibility_lhs && dual_feasibility_lhs_new >= dual_feasibility_lhs && qpresults.info.mu_in <= T(1e-5)) { @@ -1328,16 +1309,13 @@ qp_solve( // qpresults.info.mu_in = new_bcl_mu_in; qpresults.info.mu_eq_inv = new_bcl_mu_eq_inv; qpresults.info.mu_in_inv = new_bcl_mu_in_inv; - - ////////////////////////////////////////////////////////////////////////////////////////////// - /// end of mu update } proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); proxsuite::common::compute_objective(qpmodel, qpresults); if (qpsettings.compute_timings) { - proxsuite::common::compute_timings(qpsettings, qpresults, qpwork); + proxsuite::common::compute_timings(qpresults, qpwork); } if (qpsettings.verbose) { diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index 7685309af..e2d569f3c 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -88,6 +88,7 @@ struct Workspace T alpha; Vec dual_residual_scaled; + Vec primal_residual_scaled; Vec primal_residual_in_scaled_up; Vec primal_residual_in_scaled_up_plus_alphaCdx; @@ -99,6 +100,16 @@ struct Workspace bool refactorize; bool proximal_parameter_update; bool is_initialized; + bool is_first_solve; + + ///// Timers for update_mu in OSQP + Timer timer_factorization_complete_kkt; + Timer timer_between_updates; + T factorization_time_complete_kkt; + T time_since_last_update_mu; + + ///// Fixed number iterations approach in mu update in OSQP + isize last_iteration_update_mu; sparse::isize n_c; // final number of active inequalities /*! @@ -133,6 +144,7 @@ struct Workspace , refactorize(false) , proximal_parameter_update(false) , is_initialized(false) + , is_first_solve(true) { if (box_constraints) { @@ -220,6 +232,7 @@ struct Workspace 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_scaled.resize(n_eq + n_in + dim); primal_residual_in_scaled_up.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); @@ -297,6 +310,7 @@ struct Workspace dw_aug.resize(dim + n_eq + n_in); rhs.resize(dim + n_eq + n_in); err.resize(dim + n_eq + n_in); + primal_residual_scaled.resize(n_eq + n_in); primal_residual_in_scaled_up.resize(n_in); primal_residual_in_scaled_up_plus_alphaCdx.resize(n_in); primal_residual_in_scaled_low_plus_alphaCdx.resize(n_in); @@ -333,12 +347,18 @@ struct Workspace alpha = 1.; dual_residual_scaled.setZero(); + primal_residual_scaled.setZero(); primal_residual_in_scaled_up.setZero(); primal_residual_in_scaled_up_plus_alphaCdx.setZero(); primal_residual_in_scaled_low_plus_alphaCdx.setZero(); CTz.setZero(); n_c = 0; + + factorization_time_complete_kkt = 0.; + time_since_last_update_mu = 0.; + + last_iteration_update_mu = 0; } /*! * Clean-ups solver's workspace. @@ -369,6 +389,7 @@ struct Workspace alpha = 1.; dual_residual_scaled.setZero(); + primal_residual_scaled.setZero(); primal_residual_in_scaled_up.setZero(); primal_residual_in_scaled_up_plus_alphaCdx.setZero(); @@ -394,6 +415,9 @@ struct Workspace proximal_parameter_update = false; is_initialized = false; n_c = 0; + + time_since_last_update_mu = 0.; + last_iteration_update_mu = 0; } }; } // namespace dense diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index 1a2533a20..6baa91933 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -51,6 +51,12 @@ enum struct EigenValueEstimateMethodOption // watch out, the last option is only available for dense // matrices! }; +// Choice of approach for the iteraion condition to update mu in OSQP. +enum struct UpdateMuIterationCriteria +{ + FactorizationTime, // Time spent since last update wrt factorization time. + FixedNumberIterations // Fixed number of iteration to outpass. +}; inline std::ostream& operator<<(std::ostream& os, const SparseBackend& sparse_backend) { @@ -101,9 +107,17 @@ struct Settings T refactor_rho_threshold; T mu_min_eq; - T mu_min_in; + T mu_max_eq; // for osqp + T mu_min_in; // osqp 1e-6 + T mu_max_in; // for osqp + T mu_max_eq_inv; - T mu_max_in_inv; + T mu_min_eq_inv; // for osqp + T mu_max_in_inv; // osqp 1e6 + T mu_min_in_inv; // for osqp + + T mu_min_in_osqp; + T mu_max_in_inv_osqp; T mu_update_factor; T mu_update_inv_factor; @@ -112,6 +126,12 @@ struct Settings T cold_reset_mu_in; T cold_reset_mu_eq_inv; T cold_reset_mu_in_inv; + + T cold_reset_mu_eq_osqp; + T cold_reset_mu_in_osqp; + T cold_reset_mu_eq_inv_osqp; + T cold_reset_mu_in_inv_osqp; + T eps_abs; T eps_rel; @@ -131,6 +151,13 @@ struct Settings T eps_duality_gap_abs; T eps_duality_gap_rel; + bool update_mu; + T threshold_ratio_update_mu; + T threshold_ratio_update_mu_inv; + T percentage_factorization_time_update_mu; + UpdateMuIterationCriteria update_mu_iteration_criteria; + isize interval_update_mu; + isize preconditioner_max_iter; T preconditioner_accuracy; T eps_primal_inf; @@ -156,11 +183,20 @@ struct Settings * @param refactor_rho_threshold new rho parameter used if the * refactor_dual_feasibility_threshold_ condition has been satisfied. * @param mu_min_eq minimal authorized value for mu_eq. + * @param mu_max_eq maximal authorized value for mu_eq. * @param mu_min_in minimal authorized value for mu_in. + * @param mu_max_in maximal authorized value for mu_in. * @param mu_max_eq_inv maximal authorized value for the inverse of * mu_eq_inv. + * @param mu_min_eq_inv minimal authorized value for the inverse of + * mu_eq_inv. * @param mu_max_in_inv maximal authorized value for the inverse of * mu_in_inv. + * @param mu_min_in_inv minimal authorized value for the inverse of + * mu_in_inv. + * @param mu_min_in_osqp minimal authorized value for mu_in in osqp. + * @param mu_max_in_inv_osqp maximal authorized value for the inverse of mu_in + * in osqp. * @param mu_update_factor update factor used for updating mu_eq and mu_in. * @param mu_update_inv_factor update factor used for updating mu_eq_inv and * mu_in_inv. @@ -168,6 +204,12 @@ struct Settings * @param cold_reset_mu_in value used for cold restarting mu_in. * @param cold_reset_mu_eq_inv value used for cold restarting mu_eq_inv. * @param cold_reset_mu_in_inv value used for cold restarting mu_in_inv. + * @param cold_reset_mu_eq_osqp value used for cold restarting mu_eq in osqp. + * @param cold_reset_mu_in_osqp value used for cold restarting mu_in in osqp. + * @param cold_reset_mu_eq_inv_osqp value used for cold restarting mu_eq_inv + * in osqp. + * @param cold_reset_mu_in_inv_osqp value used for cold restarting mu_in_inv + * in osqp. * @param eps_abs asbolute stopping criterion of the solver. * @param eps_rel relative stopping criterion of the solver. * @param max_iter maximal number of authorized iteration. @@ -192,6 +234,20 @@ struct Settings * included in the stopping criterion. * @param eps_duality_gap_abs absolute duality-gap stopping criterion. * @param eps_duality_gap_rel relative duality-gap stopping criterion. + * @param update_mu If set to true, the proximal parameters in OSQP are + * updated during the solve. + * @param threshold_ratio_update_mu update mu if below the scaled ratio + * between primal and dual residuals. + * @param threshold_ratio_update_mu_inv update mu if above the scaled ratio + * between primal and dual residuals. + * @param percentage_factorization_time_update_mu percentage of the + * factorization time of the complete KKT matrix in OSQP involved in the + * update of mu. + * @param update_mu_iteration_criteria choose wether we potnetially update mu + * after a fixed amount of iterations or some percentage of factorization + * time. + * @param interval_update_mu minimum number of ADMM iterations between two mu + * updates in OSQP if iteration based criteria * @param preconditioner_max_iter maximal number of authorized iterations for * the preconditioner. * @param preconditioner_accuracy accuracy level of the preconditioner. @@ -223,15 +279,25 @@ struct Settings T refactor_dual_feasibility_threshold = 1e-2, T refactor_rho_threshold = 1e-7, T mu_min_eq = 1e-9, + T mu_max_eq = 1e3, // for osqp T mu_min_in = 1e-8, + T mu_max_in = 1e6, // for osqp T mu_max_eq_inv = 1e9, + T mu_min_eq_inv = 1e-3, // for osqp T mu_max_in_inv = 1e8, + T mu_min_in_inv = 1e-6, // for osqp + T mu_min_in_osqp = 1e-6, + T mu_max_in_inv_osqp = 1e6, T mu_update_factor = 0.1, T mu_update_inv_factor = 10, T cold_reset_mu_eq = 1. / 1.1, T cold_reset_mu_in = 1. / 1.1, T cold_reset_mu_eq_inv = 1.1, T cold_reset_mu_in_inv = 1.1, + T cold_reset_mu_eq_osqp = 1. / 1.1, + T cold_reset_mu_in_osqp = 1. / 1.1, + T cold_reset_mu_eq_inv_osqp = 1.1, + T cold_reset_mu_in_inv_osqp = 1.1, T eps_abs = 1.e-5, T eps_rel = 0, isize max_iter = 10000, @@ -251,6 +317,13 @@ struct Settings bool check_duality_gap = false, T eps_duality_gap_abs = 1.e-4, T eps_duality_gap_rel = 0, + bool update_mu = false, + T threshold_ratio_update_mu = 5.0, + T threshold_ratio_update_mu_inv = 0.2, + T percentage_factorization_time_update_mu = 0.4, + UpdateMuIterationCriteria update_mu_iteration_criteria = + UpdateMuIterationCriteria::FixedNumberIterations, + isize interval_update_mu = 10, isize preconditioner_max_iter = 10, T preconditioner_accuracy = 1.e-3, T eps_primal_inf = 1.E-4, @@ -270,15 +343,25 @@ struct Settings , refactor_dual_feasibility_threshold(refactor_dual_feasibility_threshold) , refactor_rho_threshold(refactor_rho_threshold) , mu_min_eq(mu_min_eq) + , mu_max_eq(mu_max_eq) , mu_min_in(mu_min_in) + , mu_max_in(mu_max_in) , mu_max_eq_inv(mu_max_eq_inv) + , mu_min_eq_inv(mu_min_eq_inv) , mu_max_in_inv(mu_max_in_inv) + , mu_min_in_inv(mu_min_in_inv) + , mu_min_in_osqp(mu_min_in_osqp) + , mu_max_in_inv_osqp(mu_max_in_inv_osqp) , mu_update_factor(mu_update_factor) , mu_update_inv_factor(mu_update_inv_factor) , cold_reset_mu_eq(cold_reset_mu_eq) , cold_reset_mu_in(cold_reset_mu_in) , cold_reset_mu_eq_inv(cold_reset_mu_eq_inv) , cold_reset_mu_in_inv(cold_reset_mu_in_inv) + , cold_reset_mu_eq_osqp(cold_reset_mu_eq_osqp) + , cold_reset_mu_in_osqp(cold_reset_mu_in_osqp) + , cold_reset_mu_eq_inv_osqp(cold_reset_mu_eq_inv_osqp) + , cold_reset_mu_in_inv_osqp(cold_reset_mu_in_inv_osqp) , eps_abs(eps_abs) , eps_rel(eps_rel) , max_iter(max_iter) @@ -294,6 +377,13 @@ struct Settings , check_duality_gap(check_duality_gap) , eps_duality_gap_abs(eps_duality_gap_abs) , eps_duality_gap_rel(eps_duality_gap_rel) + , update_mu(update_mu) + , threshold_ratio_update_mu(threshold_ratio_update_mu) + , threshold_ratio_update_mu_inv(threshold_ratio_update_mu_inv) + , percentage_factorization_time_update_mu( + percentage_factorization_time_update_mu) + , update_mu_iteration_criteria(update_mu_iteration_criteria) + , interval_update_mu(interval_update_mu) , preconditioner_max_iter(preconditioner_max_iter) , preconditioner_accuracy(preconditioner_accuracy) , eps_primal_inf(eps_primal_inf) @@ -338,15 +428,27 @@ operator==(const Settings& settings1, const Settings& settings2) settings2.refactor_dual_feasibility_threshold && settings1.refactor_rho_threshold == settings2.refactor_rho_threshold && settings1.mu_min_eq == settings2.mu_min_eq && + settings1.mu_max_eq == settings2.mu_max_eq && settings1.mu_min_in == settings2.mu_min_in && + settings1.mu_max_in == settings2.mu_max_in && settings1.mu_max_eq_inv == settings2.mu_max_eq_inv && + settings1.mu_min_eq_inv == settings2.mu_min_eq_inv && settings1.mu_max_in_inv == settings2.mu_max_in_inv && + settings1.mu_min_in_inv == settings2.mu_min_in_inv && + settings1.mu_min_in_osqp == settings2.mu_min_in_osqp && + settings1.mu_max_in_inv_osqp == settings2.mu_max_in_inv_osqp && settings1.mu_update_factor == settings2.mu_update_factor && settings1.mu_update_factor == settings2.mu_update_factor && settings1.cold_reset_mu_eq == settings2.cold_reset_mu_eq && settings1.cold_reset_mu_in == settings2.cold_reset_mu_in && settings1.cold_reset_mu_eq_inv == settings2.cold_reset_mu_eq_inv && settings1.cold_reset_mu_in_inv == settings2.cold_reset_mu_in_inv && + settings1.cold_reset_mu_eq_osqp == settings2.cold_reset_mu_eq_osqp && + settings1.cold_reset_mu_in_osqp == settings2.cold_reset_mu_in_osqp && + settings1.cold_reset_mu_eq_inv_osqp == + settings2.cold_reset_mu_eq_inv_osqp && + settings1.cold_reset_mu_in_inv_osqp == + settings2.cold_reset_mu_in_inv_osqp && settings1.eps_abs == settings2.eps_abs && settings1.eps_rel == settings2.eps_rel && settings1.max_iter == settings2.max_iter && @@ -362,6 +464,16 @@ operator==(const Settings& settings1, const Settings& settings2) settings1.check_duality_gap == settings2.check_duality_gap && settings1.eps_duality_gap_abs == settings2.eps_duality_gap_abs && settings1.eps_duality_gap_rel == settings2.eps_duality_gap_rel && + settings1.update_mu == settings2.update_mu && + settings1.threshold_ratio_update_mu == + settings2.threshold_ratio_update_mu && + settings1.threshold_ratio_update_mu_inv == + settings2.threshold_ratio_update_mu_inv && + settings1.percentage_factorization_time_update_mu == + settings2.percentage_factorization_time_update_mu && + settings1.update_mu_iteration_criteria == + settings2.update_mu_iteration_criteria && + settings1.interval_update_mu == settings2.interval_update_mu && settings1.preconditioner_max_iter == settings2.preconditioner_max_iter && settings1.preconditioner_accuracy == settings2.preconditioner_accuracy && settings1.eps_primal_inf == settings2.eps_primal_inf && diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 81a3a2832..f7672dc02 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -244,6 +244,7 @@ prepare_next_solve(pp::Results& qpresults, ppd::Workspace& qpwork) { qpwork.dirty = true; qpwork.is_initialized = true; // necessary because we call workspace cleanup + qpwork.is_first_solve = false; assert(!std::isnan(qpresults.info.pri_res)); assert(!std::isnan(qpresults.info.dua_res)); @@ -518,19 +519,61 @@ setup_solver(const pp::Settings& qpsettings, // updating the Qp object switch (qpsettings.initial_guess) { case pp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS: { - proxsuite::proxqp::dense::setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); - compute_equality_constrained_initial_guess(qpwork, - qpsettings, - qpmodel, - n_constraints, - dense_backend, - hessian_type, - qpresults); switch (qp_solver) { + case common::QPSolver::PROXQP: { + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + compute_equality_constrained_initial_guess(qpwork, + qpsettings, + qpmodel, + n_constraints, + dense_backend, + hessian_type, + qpresults); + } break; case common::QPSolver::OSQP: { - setup_factorization_complete_kkt( - qpwork, qpmodel, qpresults, dense_backend, n_constraints); + if (qpwork.is_first_solve) { + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.timer_factorization_complete_kkt.start(); + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.factorization_time_complete_kkt = + qpwork.timer_factorization_complete_kkt.elapsed().user; + // std::cout << "Time setup_factorization: " + // << + // qpwork.timer_factorization_complete_kkt.elapsed().user + // << std::endl; + compute_equality_constrained_initial_guess(qpwork, + qpsettings, + qpmodel, + n_constraints, + dense_backend, + hessian_type, + qpresults); + qpwork.timer_factorization_complete_kkt.start(); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.factorization_time_complete_kkt += + qpwork.timer_factorization_complete_kkt.elapsed().user; + // std::cout << "Time setup_factorization_compute_kkt: " + // << + // qpwork.timer_factorization_complete_kkt.elapsed().user + // << std::endl; + } else { // Keep the same first factorization time for mu update + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + compute_equality_constrained_initial_guess(qpwork, + qpsettings, + qpmodel, + n_constraints, + dense_backend, + hessian_type, + qpresults); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } } break; } break; @@ -574,12 +617,28 @@ setup_solver(const pp::Settings& qpsettings, break; } case pp::InitialGuessStatus::NO_INITIAL_GUESS: { - setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); switch (qp_solver) { + case common::QPSolver::PROXQP: { + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + } break; case common::QPSolver::OSQP: { - setup_factorization_complete_kkt( - qpwork, qpmodel, qpresults, dense_backend, n_constraints); + if (qpwork.is_first_solve) { + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.timer_factorization_complete_kkt.start(); + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.factorization_time_complete_kkt = + qpwork.timer_factorization_complete_kkt.elapsed().user; + } else { // Keep the same first factorization time for mu update + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } } break; } break; @@ -596,10 +655,10 @@ setup_solver(const pp::Settings& qpsettings, ruiz.scale_box_dual_in_place_in( { proxsuite::proxqp::from_eigen, qpresults.z.tail(qpmodel.dim) }); } - setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); switch (qp_solver) { case common::QPSolver::PROXQP: { + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); //!\ TODO in a quicker way qpwork.n_c = 0; for (plv::isize i = 0; i < n_constraints; i++) { @@ -613,8 +672,22 @@ setup_solver(const pp::Settings& qpsettings, qpmodel, qpresults, dense_backend, n_constraints, qpwork); } break; case common::QPSolver::OSQP: { - setup_factorization_complete_kkt( - qpwork, qpmodel, qpresults, dense_backend, n_constraints); + if (qpwork.is_first_solve) { + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.timer_factorization_complete_kkt.start(); + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + qpwork.timer_factorization_complete_kkt.stop(); + qpwork.factorization_time_complete_kkt = + qpwork.timer_factorization_complete_kkt.elapsed().user; + } else { // Keep the same first factorization time for mu update + setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); + } } break; } break; @@ -718,6 +791,98 @@ compute_scaled_primal_residual_ineq(const pp::Settings& qpsettings, qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) } } +/*! + * Computes the scaled primal residual. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +global_primal_residual_scaled(const ppd::Model& qpmodel, + pp::Results& qpresults, + ppd::Workspace& qpwork, + const bool box_constraints) +{ + qpresults.se.noalias() = qpwork.A_scaled * qpresults.x - qpwork.b_scaled; + + 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) = + qpwork.i_scaled.array() * qpresults.x.array(); + } + + qpresults.si.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) { + qpresults.si.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_scaled.head(qpmodel.n_eq) = qpresults.se; + qpwork.primal_residual_scaled.segment(qpmodel.n_eq, qpmodel.n_in) = + qpresults.si.head(qpmodel.n_in); + if (box_constraints) { + qpwork.primal_residual_scaled.tail(qpmodel.dim) = + qpresults.si.tail(qpmodel.dim); + } +} +/*! + * Computes the scaled dual residual. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +global_dual_residual_scaled(pp::Results& qpresults, + ppd::Workspace& qpwork, + const ppd::Model& qpmodel, + const bool box_constraints, + const pp::HessianType& hessian_type) +{ + qpwork.dual_residual_scaled = qpwork.g_scaled; + + switch (hessian_type) { + case pp::HessianType::Zero: + break; + case pp::HessianType::Dense: + qpwork.CTz.noalias() = + qpwork.H_scaled.template selfadjointView() * qpresults.x; + qpwork.dual_residual_scaled += qpwork.CTz; + break; + case pp::HessianType::Diagonal: + qpwork.CTz.array() = + qpwork.H_scaled.diagonal().array() * qpresults.x.array(); + qpwork.dual_residual_scaled += qpwork.CTz; + break; + } + + qpwork.CTz.noalias() = qpwork.A_scaled.transpose() * qpresults.y; + qpwork.dual_residual_scaled += qpwork.CTz; + + qpwork.CTz.noalias() = + qpwork.C_scaled.transpose() * qpresults.z.head(qpmodel.n_in); + qpwork.dual_residual_scaled += qpwork.CTz; + if (box_constraints) { + qpwork.CTz.noalias() = qpresults.z.tail(qpmodel.dim); + qpwork.CTz.array() *= qpwork.i_scaled.array(); + qpwork.dual_residual_scaled += qpwork.CTz; + } +} /*! * Computes the objective function. * @@ -743,15 +908,12 @@ compute_objective(const ppd::Model& qpmodel, pp::Results& qpresults) /*! * Computes the objective function. * - * @param qpsettings solver settings. * @param qpresults solver results. * @param qpwork solver workspace. */ template void -compute_timings(const pp::Settings& qpsettings, - pp::Results& qpresults, - ppd::Workspace& qpwork) +compute_timings(pp::Results& qpresults, ppd::Workspace& qpwork) { qpresults.info.solve_time = qpwork.timer.elapsed().user; // in microseconds qpresults.info.run_time = @@ -976,7 +1138,7 @@ update_solver_status( // T& primal_feasibility_eq_lhs, T& primal_feasibility_in_lhs, T& primal_feasibility_lhs_new, - T& dual_feasibility_lhs, + T& dual_feasibility_lhs_new, T& dual_feasibility_rhs_0, T& dual_feasibility_rhs_1, T& dual_feasibility_rhs_3, @@ -1001,9 +1163,8 @@ update_solver_status( // (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0)); qpresults.info.pri_res = primal_feasibility_lhs_new; - if (is_primal_feasible) { - T dual_feasibility_lhs_new(dual_feasibility_lhs); + if (is_primal_feasible) { ppd::global_dual_residual(qpresults, qpwork, qpmodel, diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index a3d095bc2..9573d81df 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -252,8 +252,7 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // qp_random.g = -qp_random.C.transpose() * // z_sol; // make sure the LP is bounded within the feasible // set -// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l -// " +// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" // // << qp.l << std::endl; // pod::QP qp{ dim, n_eq, n_in }; // creating QP object // qp.settings.eps_abs = eps_abs; From c656ec6f7264e26af0b43e9d24b08292ebb9a518 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Thu, 15 May 2025 20:29:19 +0200 Subject: [PATCH 18/27] osqp/: polish: Code done and works on simple example. To test and benchmark. --- examples/cpp/osqp_overview-simple.cpp | 33 +- include/proxsuite/osqp/dense/solver.hpp | 470 ++++++++++++++++++- include/proxsuite/proxqp/dense/workspace.hpp | 14 +- include/proxsuite/proxqp/results.hpp | 9 +- include/proxsuite/proxqp/settings.hpp | 17 + include/proxsuite/proxqp/status.hpp | 8 + include/proxsuite/solvers/common/utils.hpp | 107 +++-- 7 files changed, 606 insertions(+), 52 deletions(-) diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp index 0791f12cc..9e0e385a2 100644 --- a/examples/cpp/osqp_overview-simple.cpp +++ b/examples/cpp/osqp_overview-simple.cpp @@ -16,7 +16,7 @@ int main() { T sparsity_factor = 0.15; - ppd::isize dim = 110; + ppd::isize dim = 200; ppd::isize n_eq(dim / 4); ppd::isize n_in(dim / 4); T strong_convexity_factor(1.e-2); @@ -36,20 +36,27 @@ main() // qp_random.u); // qp_proxqp.solve(); - // // Osqp without update - // pod::QP qp_osqp(dim, n_eq, n_in); + // Osqp without update + pod::QP qp_osqp(dim, n_eq, n_in); - // qp_osqp.settings.default_mu_eq = T(1.E-2); - // qp_osqp.settings.default_mu_in = T(1.E1); + qp_osqp.settings.default_mu_eq = T(1.E-2); + qp_osqp.settings.default_mu_in = T(1.E1); - // qp_osqp.init(qp_random.H, - // qp_random.g, - // qp_random.A, - // qp_random.b, - // qp_random.C, - // qp_random.l, - // qp_random.u); - // qp_osqp.solve(); + qp_osqp.settings.polish = true; + qp_osqp.settings.eps_abs = T(1.E-3); + + qp_osqp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp_osqp.solve(); + + // std::cout << "optimal x: " << qp_osqp.results.x << std::endl; + // std::cout << "optimal y: " << qp_osqp.results.y << std::endl; + // std::cout << "optimal z: " << qp_osqp.results.z << std::endl; // qp_osqp_1 with mu_update // pod::QP qp_osqp_1(dim, n_eq, n_in); diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 2fe545650..6059f03ed 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -345,7 +345,6 @@ is_infeasible(const Settings& qpsettings, * performed). * @param qpsettings solver settings. * @param qpresults solver results. - * @param ruiz ruiz preconditioner. */ template void @@ -355,7 +354,6 @@ admm_step(const Settings& qpsettings, Workspace& qpwork, const bool box_constraints, const isize n_constraints, - preconditioner::RuizEquilibration& ruiz, const DenseBackend dense_backend, const HessianType hessian_type) { @@ -430,6 +428,441 @@ admm_step(const Settings& qpsettings, qpwork.zeta_in = zeta_in_next; } +/*! + * Solution polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +polish(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + const DenseBackend dense_backend, + const HessianType hessian_type, + preconditioner::RuizEquilibration& ruiz, + T& primal_feasibility_lhs, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap, + T& scaled_eps) +{ + // Timing polishing + qpwork.timer_polish.stop(); + qpwork.timer_polish.start(); + + // ADMM solution + auto x_admm = qpresults.x; + auto y_admm = qpresults.y; + auto z_admm = qpresults.z; + + auto pri_res_admm = qpresults.info.pri_res; + auto dua_res_admm = qpresults.info.dua_res; + auto duality_gap_admm = qpresults.info.duality_gap; + + // Upper and lower active constraints + qpwork.active_set_low_eq.array() = (qpresults.y.array() < 0); + qpwork.active_set_up_eq.array() = (qpresults.y.array() > 0); + VecBool active_constraints_eq = + qpwork.active_set_up_eq || qpwork.active_set_low_eq; + isize num_active_constraints_eq = active_constraints_eq.count(); + isize num_active_constraints_eq_low = qpwork.active_set_low_eq.count(); + isize num_active_constraints_eq_up = qpwork.active_set_up_eq.count(); + + // active_set_low and active_setup_low already computed in ADMM + VecBool active_constraints_ineq = + qpwork.active_set_up || qpwork.active_set_low; + isize num_active_constraints_ineq = active_constraints_ineq.count(); + isize num_active_constraints_ineq_low = qpwork.active_set_low.count(); + isize num_active_constraints_ineq_up = qpwork.active_set_up.count(); + + isize num_active_constraints = + num_active_constraints_eq + num_active_constraints_ineq; + + isize inner_pb_dim = qpmodel.dim + num_active_constraints; + + if (num_active_constraints == 0) { + qpresults.info.polish_status = PolishStatus::POLISH_NO_ACTIVE_SET_FOUND; + return; + } + + // Build the reducted matrices of the constraints + Mat A_low(num_active_constraints_eq_low, qpmodel.dim); + Mat A_up(num_active_constraints_eq_up, qpmodel.dim); + + isize low_index = 0; + isize up_index = 0; + for (isize i = 0; i < qpmodel.n_eq; ++i) { + if (qpwork.active_set_low_eq(i)) { + A_low.row(low_index) = qpwork.A_scaled.row(i); + ++low_index; + } + if (qpwork.active_set_up_eq(i)) { + A_up.row(up_index) = qpwork.A_scaled.row(i); + ++up_index; + } + } + + Mat C_low(num_active_constraints_ineq_low, qpmodel.dim); + Mat C_up(num_active_constraints_ineq_up, qpmodel.dim); + + low_index = 0; + up_index = 0; + Vec tmp_low(qpmodel.dim); + Vec tmp_up(qpmodel.dim); + tmp_low.setZero(); + tmp_up.setZero(); + for (isize i = 0; i < n_constraints; ++i) { + if (qpwork.active_set_low(i)) { + if (i < qpmodel.n_in) { + C_low.row(low_index) = qpwork.C_scaled.row(i); + } else { + tmp_low(i - qpmodel.n_in) = qpwork.i_scaled(i - qpmodel.n_in); + C_low.row(low_index) = tmp_low; + tmp_low(i - qpmodel.n_in) = 0.; + } + ++low_index; + } + if (qpwork.active_set_up(i)) { + if (i < qpmodel.n_in) { + C_up.row(up_index) = qpwork.C_scaled.row(i); + } else { + tmp_up(i - qpmodel.n_in) = qpwork.i_scaled(i - qpmodel.n_in); + C_up.row(up_index) = tmp_up; + tmp_up(i - qpmodel.n_in) = 0.; + } + ++up_index; + } + } + + // Construction of K + isize row; + isize col; + + Mat k_polish(inner_pb_dim, inner_pb_dim); + Mat k_plus_delta_k_polish(inner_pb_dim, inner_pb_dim); + + switch (hessian_type) { + case HessianType::Dense: + k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) = qpwork.H_scaled; + break; + case HessianType::Zero: + k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim).setZero(); + break; + case HessianType::Diagonal: + k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) = qpwork.H_scaled; + break; + } + + col = qpmodel.dim; + k_polish.block(0, col, qpmodel.dim, num_active_constraints_eq_low) = + A_low.transpose(); + + col += num_active_constraints_eq_low; + k_polish.block(0, col, qpmodel.dim, num_active_constraints_ineq_low) = + C_low.transpose(); + + col += num_active_constraints_ineq_low; + k_polish.block(0, col, qpmodel.dim, num_active_constraints_eq_up) = + A_up.transpose(); + + col += num_active_constraints_eq_up; + k_polish.block(0, col, qpmodel.dim, num_active_constraints_ineq_up) = + C_up.transpose(); + + row = qpmodel.dim; + k_polish.block(row, 0, num_active_constraints_eq_low, qpmodel.dim) = A_low; + + row += num_active_constraints_eq_low; + k_polish.block(row, 0, num_active_constraints_ineq_low, qpmodel.dim) = C_low; + + row += num_active_constraints_ineq_low; + k_polish.block(row, 0, num_active_constraints_eq_up, qpmodel.dim) = A_up; + + row += num_active_constraints_eq_up; + k_polish.block(row, 0, num_active_constraints_ineq_up, qpmodel.dim) = C_up; + + k_polish.bottomRightCorner(num_active_constraints, num_active_constraints) + .setZero(); + + // Construction and factorization of K + Delta_K + k_plus_delta_k_polish = k_polish; + k_plus_delta_k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) + .diagonal() + .array() += qpsettings.delta; + k_plus_delta_k_polish + .bottomRightCorner(num_active_constraints, num_active_constraints) + .diagonal() + .array() -= qpsettings.delta; + + proxsuite::linalg::veg::dynstack::DynStackMut stack{ + proxsuite::linalg::veg::from_slice_mut, + qpwork.ldl_stack.as_mut(), + }; + + qpwork.ldl.factorize(k_plus_delta_k_polish.transpose(), stack); + + // Construction of rhs_polish + low_index = 0; + up_index = 0; + Vec b_low(num_active_constraints_eq_low); + Vec b_up(num_active_constraints_eq_up); + for (isize i = 0; i < qpmodel.n_eq; ++i) { + if (qpwork.active_set_low_eq(i)) { + b_low(low_index) = qpwork.b_scaled(i); + ++low_index; + } + if (qpwork.active_set_up_eq(i)) { + b_up(up_index) = qpwork.b_scaled(i); + ++up_index; + } + } + + low_index = 0; + up_index = 0; + Vec l_low(num_active_constraints_ineq_low); + Vec u_up(num_active_constraints_ineq_up); + for (isize i = 0; i < n_constraints; ++i) { + if (qpwork.active_set_low(i)) { + if (i < qpmodel.n_in) { + l_low(low_index) = qpwork.l_scaled(i); + } else { + l_low(low_index) = qpwork.l_box_scaled(i - qpmodel.n_in); + } + ++low_index; + } + if (qpwork.active_set_up(i)) { + if (i < qpmodel.n_in) { + u_up(up_index) = qpwork.u_scaled(i); + } else { + u_up(up_index) = qpwork.u_box_scaled(i - qpmodel.n_in); + } + ++up_index; + } + } + + Vec rhs_polish(inner_pb_dim); + + row = qpmodel.dim; + rhs_polish.head(row) = -qpwork.g_scaled; + rhs_polish.segment(row, num_active_constraints_eq_low) = b_low; + + row += num_active_constraints_eq_low; + rhs_polish.segment(row, num_active_constraints_ineq_low) = l_low; + + row += num_active_constraints_ineq_low; + rhs_polish.segment(row, num_active_constraints_eq_up) = b_up; + rhs_polish.tail(num_active_constraints_ineq_up) = u_up; + + // Solve the reduced system before iterative refinement + Vec hat_t(inner_pb_dim); + hat_t = rhs_polish; + + solve_linear_system(hat_t, + qpmodel, + qpresults, + qpwork, + n_constraints, + dense_backend, + inner_pb_dim, + stack); + + // Iterative refinement + Vec rhs_polish_refine(inner_pb_dim); + Vec delta_hat_t(inner_pb_dim); + + for (i64 iter = 0; iter < qpsettings.polish_refine_iter; ++iter) { + rhs_polish_refine = rhs_polish - k_polish * hat_t; + delta_hat_t = rhs_polish_refine; + + solve_linear_system(delta_hat_t, + qpmodel, + qpresults, + qpwork, + n_constraints, + dense_backend, + inner_pb_dim, + stack); + + hat_t = hat_t + delta_hat_t; + } + + // Update of x, y, z + qpresults.x = hat_t.head(qpmodel.dim); + + low_index = 0; + up_index = 0; + for (isize i = 0; i < qpmodel.n_eq; ++i) { + if (qpwork.active_set_low_eq(i)) { + qpresults.y(i) = hat_t(qpmodel.dim + low_index); + ++low_index; + } + if (qpwork.active_set_up_eq(i)) { + qpresults.y(i) = hat_t(qpmodel.dim + num_active_constraints_eq_low + + num_active_constraints_ineq_low + up_index); + ++up_index; + } + } + + low_index = 0; + up_index = 0; + for (isize i = 0; i < n_constraints; ++i) { + if (qpwork.active_set_low(i)) { + qpresults.z(i) = + hat_t(qpmodel.dim + num_active_constraints_eq_low + low_index); + ++low_index; + } + if (qpwork.active_set_up(i)) { + qpresults.z(i) = hat_t(qpmodel.dim + num_active_constraints_eq_low + + num_active_constraints_ineq_low + + num_active_constraints_eq_up + up_index); + ++up_index; + } + } + + // Timing polishing + qpwork.time_polishing = qpwork.timer_polish.elapsed().user; + + // Update of residuals + bool is_feasible = false; + + global_primal_residual(qpmodel, + qpresults, + qpsettings, + qpwork, + ruiz, + box_constraints, + primal_feasibility_lhs, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs); + + bool is_primal_feasible = + primal_feasibility_lhs <= + (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0)); + qpresults.info.pri_res = primal_feasibility_lhs; + + if (is_primal_feasible) { + global_dual_residual(qpresults, + qpwork, + qpmodel, + box_constraints, + ruiz, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + hessian_type); + qpresults.info.dua_res = dual_feasibility_lhs; + qpresults.info.duality_gap = duality_gap; + + bool is_dual_feasible = + dual_feasibility_lhs <= + (qpsettings.eps_abs + + qpsettings.eps_rel * + std::max( + std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), + std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2))); + + if (is_dual_feasible) { + if (qpsettings.check_duality_gap) { + if (std::fabs(qpresults.info.duality_gap) <= + qpsettings.eps_duality_gap_abs + + qpsettings.eps_duality_gap_rel * rhs_duality_gap) { + is_feasible = true; + } + } else { + is_feasible = true; + } + } + } + + // Check polish success + bool polish_success; + + if (qpsettings.check_duality_gap) { + bool polish_success_primal_dual = + ((qpresults.info.pri_res < pri_res_admm) && + (qpresults.info.dua_res < dua_res_admm)) + + || ((qpresults.info.pri_res < pri_res_admm) && + (qpresults.info.dua_res < 1e-10)) + + || ((qpresults.info.dua_res < dua_res_admm) && + (qpresults.info.pri_res < 1e-10)); + + polish_success = + ((qpresults.info.duality_gap < duality_gap_admm) && + polish_success_primal_dual) + + || (qpresults.info.duality_gap < 1e-9) && polish_success_primal_dual; + } else { + polish_success = ((qpresults.info.pri_res < pri_res_admm) && + (qpresults.info.dua_res < dua_res_admm)) + + || ((qpresults.info.pri_res < pri_res_admm) && + (qpresults.info.dua_res < 1e-10)) + + || ((qpresults.info.dua_res < dua_res_admm) && + (qpresults.info.pri_res < 1e-10)); + } + + if (!is_feasible) { + polish_success = false; + } + + switch (polish_success) { + case true: { + qpresults.info.polish_status = PolishStatus::POLISH_SUCCEED; + break; + } + case false: { + qpresults.info.polish_status = PolishStatus::POLISH_FAILED; + break; + } + } + + // Print polishing line + if (qpsettings.verbose) { + std::cout << "\033[1;34m[polishing]\033[0m" << std::endl; + std::cout << std::scientific << std::setw(2) << std::setprecision(2) + << " | primal residual=" << qpresults.info.pri_res + << " | dual residual=" << qpresults.info.dua_res + << " | duality gap=" << qpresults.info.duality_gap + << " | delta=" << qpsettings.delta + << " | feasible sol=" << (is_feasible ? "True" : "False") + << std::endl; + } + + // Go back if polish failed + if (!polish_success) { + qpresults.x = x_admm; + qpresults.y = y_admm; + qpresults.z = z_admm; + + qpresults.info.pri_res = pri_res_admm; + qpresults.info.dua_res = dua_res_admm; + qpresults.info.duality_gap = duality_gap_admm; + } +} /*! * Executes the OSQP algorithm. * @@ -542,7 +975,6 @@ qp_solve( // qpwork, box_constraints, n_constraints, - ruiz, dense_backend, hessian_type); @@ -558,8 +990,10 @@ qp_solve( // } qpwork.active_set_up.array() = - (qpwork.primal_residual_in_scaled_up.array() >= 0); - qpwork.active_set_low.array() = (qpresults.si.array() <= 0); + (qpwork.primal_residual_in_scaled_up.array() > + 0); // {zeta_in - u + z > 0} + qpwork.active_set_low.array() = + (qpresults.si.array() < 0); // {zeta_in - l + z < 0} T primal_feasibility_lhs_new(primal_feasibility_lhs); T dual_feasibility_lhs_new(dual_feasibility_lhs); @@ -605,6 +1039,32 @@ qp_solve( // } // outer iterations loop + if (qpsettings.polish) { + if (qpresults.info.status == QPSolverOutput::PROXQP_SOLVED) { + polish(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_lhs, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps); + } + } + proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); proxsuite::common::compute_objective(qpmodel, qpresults); diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index e2d569f3c..31d5ebe4b 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -66,6 +66,9 @@ struct Workspace Vec nu_eq; Vec nu_in; + VecBool active_set_low_eq; + VecBool active_set_up_eq; + //// First order residuals for line search Vec Hdx; @@ -111,6 +114,10 @@ struct Workspace ///// Fixed number iterations approach in mu update in OSQP isize last_iteration_update_mu; + ///// Timing polishing in OSQP + Timer timer_polish; + T time_polishing; + sparse::isize n_c; // final number of active inequalities /*! * Default constructor. @@ -135,6 +142,8 @@ struct Workspace , y_prev(n_eq) , zeta_eq(n_eq) , nu_eq(n_eq) + , active_set_low_eq(n_eq) + , active_set_up_eq(n_eq) , Hdx(dim) , Adx(n_eq) , dual_residual_scaled(dim) @@ -357,8 +366,9 @@ struct Workspace factorization_time_complete_kkt = 0.; time_since_last_update_mu = 0.; - last_iteration_update_mu = 0; + + time_polishing = 0.; } /*! * Clean-ups solver's workspace. @@ -418,6 +428,8 @@ struct Workspace time_since_last_update_mu = 0.; last_iteration_update_mu = 0; + + time_polishing = 0.; } }; } // namespace dense diff --git a/include/proxsuite/proxqp/results.hpp b/include/proxsuite/proxqp/results.hpp index 987d694e7..68b2236b9 100644 --- a/include/proxsuite/proxqp/results.hpp +++ b/include/proxsuite/proxqp/results.hpp @@ -42,6 +42,9 @@ struct Info sparse::isize rho_updates; QPSolverOutput status; + ///// polishing osqp + PolishStatus polish_status; + //// timings T setup_time; T solve_time; @@ -139,6 +142,7 @@ struct Results info.duality_gap = 0.; info.iterative_residual = 0.; info.status = QPSolverOutput::PROXQP_NOT_RUN; + info.polish_status = PolishStatus::POLISH_NOT_RUN; info.sparse_backend = SparseBackend::Automatic; info.minimal_H_eigenvalue_estimate = 0.; } @@ -169,7 +173,9 @@ struct Results info.dua_res = 0.; info.duality_gap = 0.; info.iterative_residual = 0.; - info.status = QPSolverOutput::PROXQP_MAX_ITER_REACHED; + info.status = + QPSolverOutput::PROXQP_MAX_ITER_REACHED; // TODO: PROXQP_NOT_RUN instead ? + info.polish_status = PolishStatus::POLISH_NOT_RUN; info.sparse_backend = SparseBackend::Automatic; } void cold_start(optional> settings = nullopt) @@ -214,6 +220,7 @@ operator==(const Info& info1, const Info& info2) info1.iter == info2.iter && info1.iter_ext == info2.iter_ext && info1.mu_updates == info2.mu_updates && info1.rho_updates == info2.rho_updates && info1.status == info2.status && + info1.polish_status == info2.polish_status && info1.setup_time == info2.setup_time && info1.solve_time == info2.solve_time && info1.run_time == info2.run_time && info1.objValue == info2.objValue && info1.pri_res == info2.pri_res && diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index 6baa91933..a63a6e87f 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -158,6 +158,10 @@ struct Settings UpdateMuIterationCriteria update_mu_iteration_criteria; isize interval_update_mu; + bool polish; + isize polish_refine_iter; + T delta; + isize preconditioner_max_iter; T preconditioner_accuracy; T eps_primal_inf; @@ -248,6 +252,10 @@ struct Settings * time. * @param interval_update_mu minimum number of ADMM iterations between two mu * updates in OSQP if iteration based criteria + * @param polish if set to true, performs solution polishing in OSQP + * @param polish_refine_iter number of iterative refinements in polishing in + * OSQP + * @param delta regularization parameter in solution polishing in OSQP * @param preconditioner_max_iter maximal number of authorized iterations for * the preconditioner. * @param preconditioner_accuracy accuracy level of the preconditioner. @@ -324,6 +332,9 @@ struct Settings UpdateMuIterationCriteria update_mu_iteration_criteria = UpdateMuIterationCriteria::FixedNumberIterations, isize interval_update_mu = 10, + bool polish = false, + isize polish_refine_iter = 3, + T delta = 1.e-6, isize preconditioner_max_iter = 10, T preconditioner_accuracy = 1.e-3, T eps_primal_inf = 1.E-4, @@ -384,6 +395,9 @@ struct Settings percentage_factorization_time_update_mu) , update_mu_iteration_criteria(update_mu_iteration_criteria) , interval_update_mu(interval_update_mu) + , polish(polish) + , polish_refine_iter(polish_refine_iter) + , delta(delta) , preconditioner_max_iter(preconditioner_max_iter) , preconditioner_accuracy(preconditioner_accuracy) , eps_primal_inf(eps_primal_inf) @@ -474,6 +488,9 @@ operator==(const Settings& settings1, const Settings& settings2) settings1.update_mu_iteration_criteria == settings2.update_mu_iteration_criteria && settings1.interval_update_mu == settings2.interval_update_mu && + settings1.polish == settings2.polish && + settings1.polish_refine_iter == settings2.polish_refine_iter && + settings1.delta == settings2.delta && settings1.preconditioner_max_iter == settings2.preconditioner_max_iter && settings1.preconditioner_accuracy == settings2.preconditioner_accuracy && settings1.eps_primal_inf == settings2.eps_primal_inf && diff --git a/include/proxsuite/proxqp/status.hpp b/include/proxsuite/proxqp/status.hpp index 55c5c8389..1ed870540 100644 --- a/include/proxsuite/proxqp/status.hpp +++ b/include/proxsuite/proxqp/status.hpp @@ -41,6 +41,14 @@ enum struct PreconditionerStatus IDENTITY // do not execute, hence use identity preconditioner (for init // method) }; +// POLISH OSQP STATUS +enum struct PolishStatus +{ + POLISH_SUCCEED, // better solution than without polishing. + POLISH_FAILED, // worst solution than without polishing. + POLISH_NO_ACTIVE_SET_FOUND, // no active set found to perform polishing. + POLISH_NOT_RUN // polishing has not been run yet. +}; } // namespace proxqp } // namespace proxsuite diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index f7672dc02..b47bd8254 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -226,6 +226,31 @@ print_solver_statistics(const pp::Settings& qpsettings, } } + if (qp_solver == QPSolver::OSQP) { + switch (qpresults.info.polish_status) { + case pp::PolishStatus::POLISH_SUCCEED: { + std::cout << "polishing: " + << "Succeed" << std::endl; + break; + } + case pp::PolishStatus::POLISH_FAILED: { + std::cout << "polishing: " + << "Failed" << std::endl; + break; + } + case pp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { + std::cout << "polishing: " + << "Not run because no active set found" << std::endl; + break; + } + case pp::PolishStatus::POLISH_NOT_RUN: { + std::cout << "polishing: " + << "Not run" << std::endl; + break; + } + } + } + if (qpsettings.compute_timings) std::cout << "run time [μs]: " << qpresults.info.solve_time << std::endl; std::cout << "--------------------------------------------------------" @@ -756,39 +781,57 @@ compute_scaled_primal_residual_ineq(const pp::Settings& qpsettings, ppdp::RuizEquilibration& ruiz, QPSolver qp_solver) { - ruiz.scale_primal_residual_in_place_in( - pp::VectorViewMut{ pp::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( - pp::VectorViewMut{ pp::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) - if (qp_solver == QPSolver::PROXQP) { - switch (qpsettings.merit_function_type) { - case pp::MeritFunctionType::GPDAL: - qpwork.primal_residual_in_scaled_up += - (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; - break; - case pp::MeritFunctionType::PDAL: - break; + switch (qp_solver) { + case QPSolver::PROXQP: { + ruiz.scale_primal_residual_in_place_in( + pp::VectorViewMut{ pp::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( + pp::VectorViewMut{ pp::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) + switch (qpsettings.merit_function_type) { + case pp::MeritFunctionType::GPDAL: + qpwork.primal_residual_in_scaled_up += + (qpsettings.alpha_gpdal - 1.) * qpresults.info.mu_in * qpresults.z; + break; + case pp::MeritFunctionType::PDAL: + break; + } + qpresults.si = 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) + qpresults.si.head(qpmodel.n_in) -= + qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + if (box_constraints) { + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= + qpwork.u_box_scaled; // contains now scaled(Cx-u+z_prev*mu_in) + qpresults.si.tail(qpmodel.dim) -= + qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) + } + break; + } + case QPSolver::OSQP: { + qpwork.primal_residual_in_scaled_up = qpwork.zeta_in + qpwork.z_prev; + qpresults.si = qpwork.primal_residual_in_scaled_up; + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) -= + qpwork.u_scaled; // contains now scaled(zeta_in-u+z_prev) + qpresults.si.head(qpmodel.n_in) -= + qpwork.l_scaled; // contains now scaled(zeta_in-l+z_prev) + if (box_constraints) { + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= + qpwork.u_box_scaled; // contains now scaled(zeta-u+z_prev) + qpresults.si.tail(qpmodel.dim) -= + qpwork.l_box_scaled; // contains now scaled(zeta-l+z_prev) + } + break; } - } - qpresults.si = 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) - qpresults.si.head(qpmodel.n_in) -= - qpwork.l_scaled; // contains now scaled(Cx-l+z_prev*mu_in) - if (box_constraints) { - qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) -= - qpwork.u_box_scaled; // contains now scaled(Cx-u+z_prev*mu_in) - qpresults.si.tail(qpmodel.dim) -= - qpwork.l_box_scaled; // contains now scaled(Cx-l+z_prev*mu_in) } } /*! From 93277bfc0eeb41a400ef373e63bfcef9ff2f4aad Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Sat, 17 May 2025 18:21:49 +0200 Subject: [PATCH 19/27] osqp/ polish: Implementation strategy regarding unconstrained problems and no active set found --- examples/cpp/osqp_overview-simple.cpp | 4 +- include/proxsuite/osqp/dense/solver.hpp | 448 +++++++++++++-------- include/proxsuite/proxqp/dense/solver.hpp | 5 +- include/proxsuite/proxqp/settings.hpp | 15 +- include/proxsuite/solvers/common/utils.hpp | 35 +- test/src/osqp_cvxpy.cpp | 255 ++++++------ test/src/osqp_dense_qp_eq.cpp | 31 +- test/src/osqp_dense_qp_solve.cpp | 16 +- test/src/osqp_dense_qp_with_eq_and_in.cpp | 417 ++++++++++--------- 9 files changed, 739 insertions(+), 487 deletions(-) diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp index 9e0e385a2..5c7c7c56d 100644 --- a/examples/cpp/osqp_overview-simple.cpp +++ b/examples/cpp/osqp_overview-simple.cpp @@ -43,7 +43,9 @@ main() qp_osqp.settings.default_mu_in = T(1.E1); qp_osqp.settings.polish = true; - qp_osqp.settings.eps_abs = T(1.E-3); + + qp_osqp.settings.eps_abs = T(1.E-9); + qp_osqp.settings.high_accuracy = true; qp_osqp.init(qp_random.H, qp_random.g, diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 6059f03ed..01e11943f 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -19,6 +19,7 @@ #include "proxsuite/proxqp/dense/preconditioner/ruiz.hpp" #include "proxsuite/proxqp/settings.hpp" #include "proxsuite/proxqp/results.hpp" +#include "proxsuite/proxqp/status.hpp" #include "proxsuite/solvers/common/utils.hpp" #include @@ -35,18 +36,14 @@ using namespace proxsuite::proxqp::dense; * @param qpwork solver workspace. * @param qpmodel QP problem model as defined by the user (without any scaling * performed). - * @param qpsettings solver settings. * @param qpresults solver results. */ template T -compute_update_ratio_primal_dual(const Settings& qpsettings, - const Model& qpmodel, +compute_update_ratio_primal_dual(const Model& qpmodel, Results& qpresults, Workspace& qpwork, const bool box_constraints, - const isize n_constraints, - const DenseBackend dense_backend, const HessianType hessian_type) { proxsuite::common::global_primal_residual_scaled( @@ -157,15 +154,8 @@ update_mu(const Settings& qpsettings, } if (iteration_condition) { - T update_ratio_primal_dual = - compute_update_ratio_primal_dual(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - n_constraints, - dense_backend, - hessian_type); + T update_ratio_primal_dual = compute_update_ratio_primal_dual( + qpmodel, qpresults, qpwork, box_constraints, hessian_type); // std::cout << "update_ratio_update_mu :" << update_ratio_primal_dual << // std::endl; @@ -354,8 +344,7 @@ admm_step(const Settings& qpsettings, Workspace& qpwork, const bool box_constraints, const isize n_constraints, - const DenseBackend dense_backend, - const HessianType hessian_type) + const DenseBackend dense_backend) { // Note: // In the context of a library (proxsuite) to implement different solvers, we @@ -428,6 +417,163 @@ admm_step(const Settings& qpsettings, qpwork.zeta_in = zeta_in_next; } +/*! + * Iterations of the ADMM algorithm in OSQP. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + * @param iter_start starting index of the first iteration. + */ +template +void +admm( // + const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + isize n_constraints, + const DenseBackend& dense_backend, + const HessianType& hessian_type, + preconditioner::RuizEquilibration& ruiz, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& primal_feasibility_lhs, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap, + T& scaled_eps, + T& scaled_eps_rel, + i64 iter_start) +{ + for (i64 iter = iter_start; iter < qpsettings.max_iter; ++iter) { + + T new_mu_in(qpresults.info.mu_in); + T new_mu_eq(qpresults.info.mu_eq); + T new_mu_in_inv(qpresults.info.mu_in_inv); + T new_mu_eq_inv(qpresults.info.mu_eq_inv); + + bool is_solved_qp = + proxsuite::common::is_solved(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + common::QPSolver::OSQP, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel, + iter); + if (is_solved_qp) { + break; + } + + qpresults.info.iter_ext += 1; + + qpwork.x_prev = qpresults.x; + qpwork.y_prev = qpresults.y; + qpwork.z_prev = qpresults.z; + + proxsuite::common::compute_scaled_primal_residual_ineq( + qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + common::QPSolver::OSQP); + + admm_step(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend); + + bool is_infeasible_qp = is_infeasible(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + hessian_type); + if (is_infeasible_qp) { + break; + } + + qpwork.active_set_up.array() = + (qpwork.primal_residual_in_scaled_up.array() > + 0); // {zeta_in - u + z > 0} + qpwork.active_set_low.array() = + (qpresults.si.array() < 0); // {zeta_in - l + z < 0} + + T primal_feasibility_lhs_new(primal_feasibility_lhs); + T dual_feasibility_lhs_new(dual_feasibility_lhs); + proxsuite::common::update_solver_status(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs_new, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel); + + if (qpsettings.update_mu) { + update_mu(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + primal_feasibility_lhs, + primal_feasibility_lhs_new, + dual_feasibility_lhs, + dual_feasibility_lhs_new, + new_mu_eq, + new_mu_in, + new_mu_eq_inv, + new_mu_in_inv, + iter); + } + + } // outer iterations loop +} /*! * Solution polishing. * @@ -458,8 +604,7 @@ polish(const Settings& qpsettings, T& dual_feasibility_rhs_1, T& dual_feasibility_rhs_3, T& rhs_duality_gap, - T& duality_gap, - T& scaled_eps) + T& duality_gap) { // Timing polishing qpwork.timer_polish.stop(); @@ -469,6 +614,7 @@ polish(const Settings& qpsettings, auto x_admm = qpresults.x; auto y_admm = qpresults.y; auto z_admm = qpresults.z; + auto zeta_in_admm = qpwork.zeta_in; auto pri_res_admm = qpresults.info.pri_res; auto dua_res_admm = qpresults.info.dua_res; @@ -497,6 +643,11 @@ polish(const Settings& qpsettings, if (num_active_constraints == 0) { qpresults.info.polish_status = PolishStatus::POLISH_NO_ACTIVE_SET_FOUND; + if (qpsettings.verbose) { + std::cout << "\033[1;34m[polishing: no active set found. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") << "]\033[0m" + << std::endl; + } return; } @@ -701,7 +852,7 @@ polish(const Settings& qpsettings, hat_t = hat_t + delta_hat_t; } - // Update of x, y, z + // Update of (x, y, z) qpresults.x = hat_t.head(qpmodel.dim); low_index = 0; @@ -734,6 +885,20 @@ polish(const Settings& qpsettings, } } + // Projection of the dual solution into the normal cone N_C(zeta) + Vec tmp_z(n_constraints); + tmp_z = qpresults.z + qpwork.zeta_in; + if (box_constraints) { + qpwork.zeta_in.head(qpmodel.n_in) = qpwork.l_scaled.cwiseMax( + qpresults.z.head(qpmodel.n_in).cwiseMin(qpwork.u_scaled)); + qpwork.zeta_in.tail(qpmodel.dim) = qpwork.l_box_scaled.cwiseMax( + qpresults.z.tail(qpmodel.dim).cwiseMin(qpwork.u_box_scaled)); + } else { + qpresults.z = + qpwork.l_scaled.cwiseMax(qpwork.zeta_in.cwiseMin(qpwork.u_scaled)); + } + qpresults.z = tmp_z - qpwork.zeta_in; + // Timing polishing qpwork.time_polishing = qpwork.timer_polish.elapsed().user; @@ -754,8 +919,9 @@ polish(const Settings& qpsettings, bool is_primal_feasible = primal_feasibility_lhs <= - (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0)); + (qpsettings.eps_abs + + qpsettings.eps_rel * + std::max(primal_feasibility_eq_rhs_0, primal_feasibility_in_rhs_0)); qpresults.info.pri_res = primal_feasibility_lhs; if (is_primal_feasible) { @@ -813,7 +979,7 @@ polish(const Settings& qpsettings, ((qpresults.info.duality_gap < duality_gap_admm) && polish_success_primal_dual) - || (qpresults.info.duality_gap < 1e-9) && polish_success_primal_dual; + || ((qpresults.info.duality_gap < 1e-9) && polish_success_primal_dual); } else { polish_success = ((qpresults.info.pri_res < pri_res_admm) && (qpresults.info.dua_res < dua_res_admm)) @@ -829,15 +995,10 @@ polish(const Settings& qpsettings, polish_success = false; } - switch (polish_success) { - case true: { - qpresults.info.polish_status = PolishStatus::POLISH_SUCCEED; - break; - } - case false: { - qpresults.info.polish_status = PolishStatus::POLISH_FAILED; - break; - } + if (polish_success) { + qpresults.info.polish_status = PolishStatus::POLISH_SUCCEED; + } else { + qpresults.info.polish_status = PolishStatus::POLISH_FAILED; } // Print polishing line @@ -847,9 +1008,31 @@ polish(const Settings& qpsettings, << " | primal residual=" << qpresults.info.pri_res << " | dual residual=" << qpresults.info.dua_res << " | duality gap=" << qpresults.info.duality_gap - << " | delta=" << qpsettings.delta - << " | feasible sol=" << (is_feasible ? "True" : "False") - << std::endl; + << " | delta=" << qpsettings.delta << std::endl; + switch (qpresults.info.polish_status) { + case PolishStatus::POLISH_SUCCEED: { + std::cout << "\033[1;34m[polishing: succeed]\033[0m" << std::endl; + break; + } + case PolishStatus::POLISH_FAILED: { + if (!is_feasible) { + std::cout << "\033[1;34m[polishing: infeasible solution. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") + << "]\033[0m" << std::endl; + } else { + std::cout << "\033[1;34m[polishing: failed. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") + << "]\033[0m" << std::endl; + } + break; + } + case PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { + break; + } + case PolishStatus::POLISH_NOT_RUN: { + break; + } + } } // Go back if polish failed @@ -857,6 +1040,7 @@ polish(const Settings& qpsettings, qpresults.x = x_admm; qpresults.y = y_admm; qpresults.z = z_admm; + qpwork.zeta_in = zeta_in_admm; qpresults.info.pri_res = pri_res_admm; qpresults.info.dua_res = dua_res_admm; @@ -914,132 +1098,52 @@ qp_solve( // T duality_gap(0); T rhs_duality_gap(0); - T scaled_eps(qpsettings.eps_abs); - - for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { - - T new_mu_in(qpresults.info.mu_in); - T new_mu_eq(qpresults.info.mu_eq); - T new_mu_in_inv(qpresults.info.mu_in_inv); - T new_mu_eq_inv(qpresults.info.mu_eq_inv); - - // proxsuite::proxqp::Timer timer_admm_iter; - // timer_admm_iter.stop(); - // std::cout << "Time iter: " << timer_admm_iter.elapsed().user << - // std::endl; timer_admm_iter.start(); - - bool is_solved_qp = - proxsuite::common::is_solved(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - hessian_type, - ruiz, - common::QPSolver::OSQP, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps, - iter); - if (is_solved_qp) { - break; - } - - qpresults.info.iter_ext += 1; - - qpwork.x_prev = qpresults.x; - qpwork.y_prev = qpresults.y; - qpwork.z_prev = qpresults.z; - - proxsuite::common::compute_scaled_primal_residual_ineq( - qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - ruiz, - common::QPSolver::OSQP); - - admm_step(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - n_constraints, - dense_backend, - hessian_type); - - bool is_infeasible_qp = is_infeasible(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - ruiz, - hessian_type); - if (is_infeasible_qp) { - break; - } - - qpwork.active_set_up.array() = - (qpwork.primal_residual_in_scaled_up.array() > - 0); // {zeta_in - u + z > 0} - qpwork.active_set_low.array() = - (qpresults.si.array() < 0); // {zeta_in - l + z < 0} - T primal_feasibility_lhs_new(primal_feasibility_lhs); - T dual_feasibility_lhs_new(dual_feasibility_lhs); - proxsuite::common::update_solver_status(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - hessian_type, - ruiz, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs_new, - dual_feasibility_lhs_new, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps); + bool polish_solution = qpsettings.polish; + if (qpmodel.n_in + qpmodel.n_eq == 0) { + polish_solution = false; + } - if (qpsettings.update_mu) { - update_mu(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - n_constraints, - dense_backend, - hessian_type, - primal_feasibility_lhs, - primal_feasibility_lhs_new, - dual_feasibility_lhs, - dual_feasibility_lhs_new, - new_mu_eq, - new_mu_in, - new_mu_eq_inv, - new_mu_in_inv, - iter); + T scaled_eps; + T scaled_eps_rel; + if (polish_solution) { + if (qpsettings.high_accuracy) { + scaled_eps = 1e-5; + scaled_eps_rel = (qpsettings.eps_rel == 0) ? 0 : 1e-5; + } else { + scaled_eps = 1e-3; + scaled_eps_rel = (qpsettings.eps_rel == 0) ? 0 : 1e-3; } + } else { + scaled_eps = qpsettings.eps_abs; + scaled_eps_rel = qpsettings.eps_rel; + } - } // outer iterations loop - - if (qpsettings.polish) { + admm(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel, + 0); + + if (polish_solution) { if (qpresults.info.status == QPSolverOutput::PROXQP_SOLVED) { polish(qpsettings, qpmodel, @@ -1060,8 +1164,38 @@ qp_solve( // dual_feasibility_rhs_1, dual_feasibility_rhs_3, rhs_duality_gap, + duality_gap); + + if ((qpresults.info.polish_status == + PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qpresults.info.polish_status == PolishStatus::POLISH_FAILED) && + qpsettings.resume_admm) { + scaled_eps = qpsettings.eps_abs; + scaled_eps_rel = qpsettings.eps_rel; + admm(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, duality_gap, - scaled_eps); + scaled_eps, + scaled_eps_rel, + qpresults.info.iter_ext); + } } } diff --git a/include/proxsuite/proxqp/dense/solver.hpp b/include/proxsuite/proxqp/dense/solver.hpp index 3a9eee0c9..3333c638a 100644 --- a/include/proxsuite/proxqp/dense/solver.hpp +++ b/include/proxsuite/proxqp/dense/solver.hpp @@ -1140,6 +1140,7 @@ qp_solve( // T duality_gap(0); T rhs_duality_gap(0); T scaled_eps(qpsettings.eps_abs); + T scaled_eps_rel(qpsettings.eps_rel); for (i64 iter = 0; iter < qpsettings.max_iter; ++iter) { @@ -1168,6 +1169,7 @@ qp_solve( // rhs_duality_gap, duality_gap, scaled_eps, + scaled_eps_rel, iter); if (is_solved) { break; @@ -1244,7 +1246,8 @@ qp_solve( // dual_feasibility_rhs_3, rhs_duality_gap, duality_gap, - scaled_eps); + scaled_eps, + scaled_eps_rel); if (qpsettings.bcl_update) { bcl_update(qpsettings, diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index a63a6e87f..916980020 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -135,6 +135,8 @@ struct Settings T eps_abs; T eps_rel; + bool high_accuracy; + isize max_iter; isize max_iter_in; isize safe_guard; @@ -161,6 +163,7 @@ struct Settings bool polish; isize polish_refine_iter; T delta; + bool resume_admm; isize preconditioner_max_iter; T preconditioner_accuracy; @@ -216,6 +219,8 @@ struct Settings * in osqp. * @param eps_abs asbolute stopping criterion of the solver. * @param eps_rel relative stopping criterion of the solver. + * @param high_accuracy if set to true, epsilon for ADMM set to 1e-5 instead + * of 1e-3 in osqp. * @param max_iter maximal number of authorized iteration. * @param max_iter_in maximal number of authorized iterations for an inner * loop. @@ -256,6 +261,8 @@ struct Settings * @param polish_refine_iter number of iterative refinements in polishing in * OSQP * @param delta regularization parameter in solution polishing in OSQP + * @param resume_admm if set to true, resumes to ADMM iterations after polish + * failed to found active sets or provide a better solution. * @param preconditioner_max_iter maximal number of authorized iterations for * the preconditioner. * @param preconditioner_accuracy accuracy level of the preconditioner. @@ -308,6 +315,7 @@ struct Settings T cold_reset_mu_in_inv_osqp = 1.1, T eps_abs = 1.e-5, T eps_rel = 0, + bool high_accuracy = false, isize max_iter = 10000, isize max_iter_in = 1500, isize safe_guard = 1.E4, @@ -332,9 +340,10 @@ struct Settings UpdateMuIterationCriteria update_mu_iteration_criteria = UpdateMuIterationCriteria::FixedNumberIterations, isize interval_update_mu = 10, - bool polish = false, + bool polish = true, isize polish_refine_iter = 3, T delta = 1.e-6, + bool resume_admm = true, isize preconditioner_max_iter = 10, T preconditioner_accuracy = 1.e-3, T eps_primal_inf = 1.E-4, @@ -375,6 +384,7 @@ struct Settings , cold_reset_mu_in_inv_osqp(cold_reset_mu_in_inv_osqp) , eps_abs(eps_abs) , eps_rel(eps_rel) + , high_accuracy(high_accuracy) , max_iter(max_iter) , max_iter_in(max_iter_in) , safe_guard(safe_guard) @@ -398,6 +408,7 @@ struct Settings , polish(polish) , polish_refine_iter(polish_refine_iter) , delta(delta) + , resume_admm(resume_admm) , preconditioner_max_iter(preconditioner_max_iter) , preconditioner_accuracy(preconditioner_accuracy) , eps_primal_inf(eps_primal_inf) @@ -465,6 +476,7 @@ operator==(const Settings& settings1, const Settings& settings2) settings2.cold_reset_mu_in_inv_osqp && settings1.eps_abs == settings2.eps_abs && settings1.eps_rel == settings2.eps_rel && + settings1.high_accuracy == settings2.high_accuracy && settings1.max_iter == settings2.max_iter && settings1.max_iter_in == settings2.max_iter_in && settings1.safe_guard == settings2.safe_guard && @@ -491,6 +503,7 @@ operator==(const Settings& settings1, const Settings& settings2) settings1.polish == settings2.polish && settings1.polish_refine_iter == settings2.polish_refine_iter && settings1.delta == settings2.delta && + settings1.resume_admm == settings2.resume_admm && settings1.preconditioner_max_iter == settings2.preconditioner_max_iter && settings1.preconditioner_accuracy == settings2.preconditioner_accuracy && settings1.eps_primal_inf == settings2.eps_primal_inf && diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index b47bd8254..76eba9055 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -236,11 +236,15 @@ print_solver_statistics(const pp::Settings& qpsettings, case pp::PolishStatus::POLISH_FAILED: { std::cout << "polishing: " << "Failed" << std::endl; + std::cout << "Resumed ADMM algorithm option: " + << (qpsettings.resume_admm ? "ON" : "OFF") << std::endl; break; } case pp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { std::cout << "polishing: " << "Not run because no active set found" << std::endl; + std::cout << "Resumed ADMM algorithm option: " + << (qpsettings.resume_admm ? "ON" : "OFF") << std::endl; break; } case pp::PolishStatus::POLISH_NOT_RUN: { @@ -1035,6 +1039,7 @@ is_solved( // T& rhs_duality_gap, T& duality_gap, T& scaled_eps, + T& scaled_eps_rel, plv::i64 iter) { @@ -1068,16 +1073,16 @@ is_solved( // qpresults.info.duality_gap = duality_gap; T rhs_pri(scaled_eps); - if (qpsettings.eps_rel != 0) { - rhs_pri += qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0); + if (scaled_eps_rel != 0) { + rhs_pri += scaled_eps_rel * std::max(primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0); } bool is_primal_feasible = primal_feasibility_lhs <= rhs_pri; - T rhs_dua(qpsettings.eps_abs); - if (qpsettings.eps_rel != 0) { + T rhs_dua(scaled_eps); + if (scaled_eps_rel != 0) { rhs_dua += - qpsettings.eps_rel * + scaled_eps_rel * std::max(std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2)); } @@ -1097,11 +1102,10 @@ is_solved( // compute_objective(qpmodel, qpresults); - std::cout << "\033[1;32m[outer iteration " << iter + 1 << "]\033[0m" - << std::endl; - switch (qp_solver) { case common::QPSolver::PROXQP: { + std::cout << "\033[1;32m[outer iteration " << iter + 1 << "]\033[0m" + << std::endl; std::cout << std::scientific << std::setw(2) << std::setprecision(2) << " | primal residual=" << qpresults.info.pri_res << " | dual residual=" << qpresults.info.dua_res @@ -1110,6 +1114,8 @@ is_solved( // << " | rho=" << qpresults.info.rho << std::endl; break; case common::QPSolver::OSQP: { + std::cout << "\033[1;32m[admm iteration " << iter + 1 << "]\033[0m" + << std::endl; std::cout << std::scientific << std::setw(2) << std::setprecision(2) << " | primal residual=" << qpresults.info.pri_res << " | dual residual=" << qpresults.info.dua_res @@ -1187,7 +1193,8 @@ update_solver_status( // T& dual_feasibility_rhs_3, T& rhs_duality_gap, T& duality_gap, - T& scaled_eps) + T& scaled_eps, + T& scaled_eps_rel) { ppd::global_primal_residual(qpmodel, qpresults, @@ -1203,8 +1210,8 @@ update_solver_status( // bool is_primal_feasible = primal_feasibility_lhs_new <= - (scaled_eps + qpsettings.eps_rel * std::max(primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0)); + (scaled_eps + scaled_eps_rel * std::max(primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0)); qpresults.info.pri_res = primal_feasibility_lhs_new; if (is_primal_feasible) { @@ -1225,8 +1232,8 @@ update_solver_status( // bool is_dual_feasible = dual_feasibility_lhs_new <= - (qpsettings.eps_abs + - qpsettings.eps_rel * + (scaled_eps + + scaled_eps_rel * std::max( std::max(dual_feasibility_rhs_3, dual_feasibility_rhs_0), std::max(dual_feasibility_rhs_1, qpwork.dual_feasibility_rhs_2))); diff --git a/test/src/osqp_cvxpy.cpp b/test/src/osqp_cvxpy.cpp index 7bdada706..3a9a35b70 100644 --- a/test/src/osqp_cvxpy.cpp +++ b/test/src/osqp_cvxpy.cpp @@ -22,67 +22,79 @@ using Mat = template using Vec = Eigen::Matrix; -DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility") -{ - - std::cout << "---3 dim test case from cvxpy, check feasibility " << std::endl; - T eps_abs = T(1e-9); - ppd::isize dim = 3; - - Mat H = Mat(dim, dim); - H << 13.0, 12.0, -2.0, 12.0, 17.0, 6.0, -2.0, 6.0, 12.0; - - Vec g = Vec(dim); - g << -22.0, -14.5, 13.0; - - Mat C = Mat(dim, dim); - C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0; - - Vec l = Vec(dim); - l << -1.0, -1.0, -1.0; - - Vec u = Vec(dim); - u << 1.0, 1.0, 1.0; - // pp::Results results = pod::solve( - // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); - pp::Results results = pod::solve(H, - g, - nullopt, - nullopt, - C, - l, - u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - T(1.E-6), - T(1.E-2), - T(1.E1)); - - T pri_res = (helpers::positive_part(C * results.x - u) + - helpers::negative_part(C * results.x - l)) - .lpNorm(); - T dua_res = - (H * results.x + g + C.transpose() * results.z).lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - +// // 1 +// DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility") +// { + +// std::cout << "---3 dim test case from cvxpy, check feasibility " << +// std::endl; T eps_abs = T(1e-9); ppd::isize dim = 3; + +// Mat H = Mat(dim, dim); +// H << 13.0, 12.0, -2.0, 12.0, 17.0, 6.0, -2.0, 6.0, 12.0; + +// Vec g = Vec(dim); +// g << -22.0, -14.5, 13.0; + +// Mat C = Mat(dim, dim); +// C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0; + +// Vec l = Vec(dim); +// l << -1.0, -1.0, -1.0; + +// Vec u = Vec(dim); +// u << 1.0, 1.0, 1.0; +// // pp::Results results = pod::solve( +// // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, +// 0); pp::Results results = pod::solve(H, +// g, +// nullopt, +// nullopt, +// C, +// l, +// u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// T(1.E-6), +// T(1.E-2), +// T(1.E1)); + +// T pri_res = (helpers::positive_part(C * results.x - u) + +// helpers::negative_part(C * results.x - l)) +// .lpNorm(); +// T dua_res = +// (H * results.x + g + C.transpose() * +// results.z).lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// 2 DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") { std::cout << "---simple test case from cvxpy, check feasibility " << std::endl; T eps_abs = T(1e-8); + // Solution polishing + // In that case, the polishing algorithm does not find any upper or lower + // constraint. Then the algorithm as described in the solver's paper would + // stop at a low precision (default 1e-3) and the test would fail. Original + // feature in this implementation (C): Introduction of the option resume_admm + // that consists in return to the ADMM iterations in case of polishing failing + // or no active sets founds. In that case, the QPs are directly solved, if + // possible, without the polishing. Note: Option set to true by default, as it + // allows the solver to pass the tests and problems in benchmarks, while keep + // providing relevant information (from polish status). ppd::isize dim = 1; Mat H = Mat(dim, dim); @@ -130,70 +142,79 @@ DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } -DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check that " - "solver stays there") -{ - - std::cout << "---simple test case from cvxpy, init with solution, check that " - "solver stays there" - << std::endl; - T eps_abs = T(1e-4); - ppd::isize dim = 1; - - Mat H = Mat(dim, dim); - H << 20.0; - - Vec g = Vec(dim); - g << -10.0; - - Mat C = Mat(dim, dim); - C << 1.0; - - Vec l = Vec(dim); - l << 0.0; - - Vec u = Vec(dim); - u << 1.0; - - T x_sol = 0.5; - - proxqp::isize n_in(1); - proxqp::isize n_eq(0); - pod::QP qp{ dim, n_eq, n_in }; - qp.settings.eps_abs = eps_abs; - - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - - qp.init(H, g, nullopt, nullopt, C, u, l); - - ppd::Vec x = ppd::Vec(dim); - ppd::Vec z = ppd::Vec(n_in); - x << 0.5; - z << 0.0; - qp.solve(x, nullopt, z); - - T pri_res = (helpers::positive_part(C * qp.results.x - u) + - helpers::negative_part(C * qp.results.x - l)) - .lpNorm(); - T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) - .lpNorm(); - - DOCTEST_CHECK(qp.results.info.iter <= 0); - DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} +// // 3 +// DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check +// that " +// "solver stays there") +// { + +// std::cout << "---simple test case from cvxpy, init with solution, check +// that " +// "solver stays there" +// << std::endl; +// T eps_abs = T(1e-4); +// ppd::isize dim = 1; + +// Mat H = Mat(dim, dim); +// H << 20.0; + +// Vec g = Vec(dim); +// g << -10.0; + +// Mat C = Mat(dim, dim); +// C << 1.0; + +// Vec l = Vec(dim); +// l << 0.0; + +// Vec u = Vec(dim); +// u << 1.0; + +// T x_sol = 0.5; + +// proxqp::isize n_in(1); +// proxqp::isize n_eq(0); +// pod::QP qp{ dim, n_eq, n_in }; +// qp.settings.eps_abs = eps_abs; + +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); + +// qp.init(H, g, nullopt, nullopt, C, u, l); + +// ppd::Vec x = ppd::Vec(dim); +// ppd::Vec z = ppd::Vec(n_in); +// x << 0.5; +// z << 0.0; +// qp.solve(x, nullopt, z); + +// T pri_res = (helpers::positive_part(C * qp.results.x - u) + +// helpers::negative_part(C * qp.results.x - l)) +// .lpNorm(); +// T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) +// .lpNorm(); + +// DOCTEST_CHECK(qp.results.info.iter_ext <= 0); +// DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish) { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; +// } +// std::cout << "setup timing " << qp.results.info.setup_time << " solve time +// " +// << qp.results.info.solve_time << std::endl; +// } \ No newline at end of file diff --git a/test/src/osqp_dense_qp_eq.cpp b/test/src/osqp_dense_qp_eq.cpp index 872aeb61c..5dbbd5905 100644 --- a/test/src/osqp_dense_qp_eq.cpp +++ b/test/src/osqp_dense_qp_eq.cpp @@ -1,6 +1,7 @@ // // Copyright (c) 2022 - 2024 INRIA // +#include "proxsuite/proxqp/status.hpp" #include #include #include @@ -102,8 +103,16 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality constraints " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } } } DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " @@ -158,8 +167,16 @@ DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } } } @@ -219,8 +236,16 @@ DOCTEST_TEST_CASE("linear problem with equality with equality constraints and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } } } diff --git a/test/src/osqp_dense_qp_solve.cpp b/test/src/osqp_dense_qp_solve.cpp index 1223c39b9..92ea57b21 100644 --- a/test/src/osqp_dense_qp_solve.cpp +++ b/test/src/osqp_dense_qp_solve.cpp @@ -67,7 +67,7 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -93,7 +93,7 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -147,7 +147,7 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -200,7 +200,7 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -258,7 +258,7 @@ DOCTEST_TEST_CASE( << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -313,7 +313,7 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -367,7 +367,7 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; @@ -426,7 +426,7 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " << " neq: " << n_eq << " nin: " << n_in << std::endl; std::cout << "primal residual: " << pri_res << std::endl; std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << results.info.iter_ext + std::cout << "total number of iteration (admm): " << results.info.iter_ext << std::endl; std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index 9573d81df..d53733d9c 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -13,188 +13,90 @@ using T = double; using namespace proxsuite; namespace pod = proxsuite::osqp::dense; -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and inequality constraints " - "and increasing dimension using wrapper API") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints and increasing dimension using wrapper API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - - proxqp::isize n_eq(dim / 4); - proxqp::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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext - << std::endl; - } -} - -DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " - "constraints and increasing dimension using the API") -{ - - std::cout - << "---testing sparse random strongly convex qp with box inequality " - "constraints and increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - - proxqp::isize n_eq(0); - proxqp::isize n_in(dim); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp_random = proxqp::utils::dense_box_constrained_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); +// DOCTEST_TEST_CASE( +// "sparse random strongly convex qp with equality and inequality constraints +// " "and increasing dimension using wrapper API") +// { - std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext - << std::endl; - } -} +// std::cout +// << "---testing sparse random strongly convex qp with equality and " +// "inequality constraints and increasing dimension using wrapper API---" +// << std::endl; +// T sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// proxqp::utils::rand::set_seed(1); +// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " - "constraints and increasing dimension using the API") -{ +// proxqp::isize n_eq(dim / 4); +// proxqp::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); - std::cout - << "---testing sparse random not strongly convex qp with inequality " - "constraints and increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - proxqp::isize n_in(dim / 2); - proxqp::isize n_eq(0); - proxqp::dense::Model qp_random = - proxqp::utils::dense_not_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor); +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// qp.solve(); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter_ext - << std::endl; - } -} +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) +// { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; +// } +// } +// } -// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate -// inequality " +// DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " // "constraints and increasing dimension using the API") // { // std::cout -// << "---testing sparse random strongly convex qp with degenerate " -// "inequality constraints and increasing dimension using the API---" +// << "---testing sparse random strongly convex qp with box inequality " +// "constraints and increasing dimension using the API---" // << std::endl; -// T sparsity_factor = 0.45; +// T sparsity_factor = 0.15; // T eps_abs = T(1e-9); -// T strong_convexity_factor(1e-2); // proxqp::utils::rand::set_seed(1); // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// proxqp::isize m(dim / 4); -// proxqp::isize n_in(2 * m); + // proxqp::isize n_eq(0); -// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( -// dim, -// n_eq, -// m, // it n_in = 2 * m, it doubles the inequality constraints -// sparsity_factor, -// strong_convexity_factor); +// proxqp::isize n_in(dim); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_box_constrained_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); // pod::QP qp{ dim, n_eq, n_in }; // creating QP object // qp.settings.eps_abs = eps_abs; // qp.settings.eps_rel = 0; @@ -208,8 +110,6 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // qp_random.l, // qp_random.u); // qp.solve(); -// DOCTEST_CHECK(qp.results.info.status == -// proxqp::QPSolverOutput::PROXQP_SOLVED); // 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) + @@ -226,18 +126,28 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // << " nin: " << n_in << std::endl; // std::cout << "primal residual: " << pri_res << std::endl; // std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration: " << qp.results.info.iter_ext +// std::cout << "number of iterations: " << qp.results.info.iter_ext // << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) +// { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; +// } // } // } -// DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " -// "increasing dimension using the API") +// DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " +// "constraints and increasing dimension using the API") // { -// srand(1); -// std::cout << "---testing linear problem with inequality constraints and " -// "increasing dimension using the API---" -// << std::endl; + +// std::cout +// << "---testing sparse random not strongly convex qp with inequality " +// "constraints and increasing dimension using the API---" +// << std::endl; // T sparsity_factor = 0.15; // T eps_abs = T(1e-9); // proxqp::utils::rand::set_seed(1); @@ -247,19 +157,12 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // proxqp::dense::Model qp_random = // proxqp::utils::dense_not_strongly_convex_qp( // dim, n_eq, n_in, sparsity_factor); -// qp_random.H.setZero(); -// auto z_sol = proxqp::utils::rand::vector_rand(n_in); -// qp_random.g = -qp_random.C.transpose() * -// z_sol; // make sure the LP is bounded within the feasible -// set -// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" -// // << qp.l << std::endl; + // pod::QP qp{ dim, n_eq, n_in }; // creating QP object // qp.settings.eps_abs = eps_abs; // qp.settings.eps_rel = 0; // qp.settings.default_mu_eq = T(1.E-2); // qp.settings.default_mu_in = T(1.E1); -// // qp.settings.verbose = false; // qp.init(qp_random.H, // qp_random.g, // qp_random.A, @@ -284,7 +187,151 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // << " nin: " << n_in << std::endl; // std::cout << "primal residual: " << pri_res << std::endl; // std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration: " << qp.results.info.iter_ext +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) +// { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter // << std::endl; +// } // } // } + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate +// inequality " +// "constraints and increasing dimension using the API") +// { + +// std::cout +// << "---testing sparse random strongly convex qp with degenerate " +// "inequality constraints and increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.45; +// T eps_abs = T(1e-9); +// T strong_convexity_factor(1e-2); +// proxqp::utils::rand::set_seed(1); +// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// proxqp::isize m(dim / 4); +// proxqp::isize n_in(2 * m); +// proxqp::isize n_eq(0); +// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( +// dim, +// n_eq, +// m, // it n_in = 2 * m, it doubles the inequality constraints +// sparsity_factor, +// strong_convexity_factor); +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// qp.solve(); +// DOCTEST_CHECK(qp.results.info.status == +// proxqp::QPSolverOutput::PROXQP_SOLVED); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) +// { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; +// } +// } +// } + +DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " + "increasing dimension using the API") +{ + srand(1); + std::cout << "---testing linear problem with inequality constraints and " + "increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + proxqp::isize n_in(dim / 2); + proxqp::isize n_eq(0); + proxqp::dense::Model qp_random = + proxqp::utils::dense_not_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor); + qp_random.H.setZero(); + auto z_sol = proxqp::utils::rand::vector_rand(n_in); + qp_random.g = -qp_random.C.transpose() * + z_sol; // make sure the LP is bounded within the feasible set + // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" + // << qp.l << std::endl; + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + if (dim == 610) { + qp.settings.verbose = true; + } + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } + } +} From 7b359127ae095c017fc88ac653f48ad7ed088a9d Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Sun, 18 May 2025 17:46:57 +0200 Subject: [PATCH 20/27] osqp/: Split polish function in several functions --- include/proxsuite/osqp/dense/solver.hpp | 604 +- test/CMakeLists.txt | 2 + test/src/osqp_cvxpy.cpp | 141 +- test/src/osqp_dense_maros_meszaros.cpp | 170 + test/src/osqp_dense_qp_solve.cpp | 670 +- test/src/osqp_dense_qp_with_eq_and_in.cpp | 200 +- test/src/osqp_dense_qp_wrapper.cpp | 7674 +++++++++++++++++++++ 7 files changed, 8835 insertions(+), 626 deletions(-) create mode 100644 test/src/osqp_dense_maros_meszaros.cpp create mode 100644 test/src/osqp_dense_qp_wrapper.cpp diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 01e11943f..13e5ba6a1 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -29,6 +29,7 @@ namespace dense { using namespace proxsuite::proxqp; using namespace proxsuite::proxqp::dense; +namespace plv = proxsuite::linalg::veg; /*! * Computes the scaled primal - dual residual ratio to update mu in OSQP. @@ -575,7 +576,7 @@ admm( // } // outer iterations loop } /*! - * Solution polishing. + * Find the active sets in solution polishing. * * @param qpwork solver workspace. * @param qpmodel QP problem model as defined by the user (without any scaling @@ -585,76 +586,62 @@ admm( // */ template void -polish(const Settings& qpsettings, - const Model& qpmodel, - Results& qpresults, - Workspace& qpwork, - const bool box_constraints, - const isize n_constraints, - const DenseBackend dense_backend, - const HessianType hessian_type, - preconditioner::RuizEquilibration& ruiz, - T& primal_feasibility_lhs, - T& primal_feasibility_eq_rhs_0, - T& primal_feasibility_in_rhs_0, - T& primal_feasibility_eq_lhs, - T& primal_feasibility_in_lhs, - T& dual_feasibility_lhs, - T& dual_feasibility_rhs_0, - T& dual_feasibility_rhs_1, - T& dual_feasibility_rhs_3, - T& rhs_duality_gap, - T& duality_gap) +find_active_sets(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + VecBool& active_constraints_eq, + isize& num_active_constraints_eq, + isize& num_active_constraints_eq_low, + isize& num_active_constraints_eq_up, + VecBool& active_constraints_ineq, + isize& num_active_constraints_ineq, + isize& num_active_constraints_ineq_low, + isize& num_active_constraints_ineq_up, + isize& num_active_constraints, + isize& inner_pb_dim) { - // Timing polishing - qpwork.timer_polish.stop(); - qpwork.timer_polish.start(); - - // ADMM solution - auto x_admm = qpresults.x; - auto y_admm = qpresults.y; - auto z_admm = qpresults.z; - auto zeta_in_admm = qpwork.zeta_in; - - auto pri_res_admm = qpresults.info.pri_res; - auto dua_res_admm = qpresults.info.dua_res; - auto duality_gap_admm = qpresults.info.duality_gap; - // Upper and lower active constraints qpwork.active_set_low_eq.array() = (qpresults.y.array() < 0); qpwork.active_set_up_eq.array() = (qpresults.y.array() > 0); - VecBool active_constraints_eq = - qpwork.active_set_up_eq || qpwork.active_set_low_eq; - isize num_active_constraints_eq = active_constraints_eq.count(); - isize num_active_constraints_eq_low = qpwork.active_set_low_eq.count(); - isize num_active_constraints_eq_up = qpwork.active_set_up_eq.count(); + active_constraints_eq = qpwork.active_set_up_eq || qpwork.active_set_low_eq; + num_active_constraints_eq = active_constraints_eq.count(); + num_active_constraints_eq_low = qpwork.active_set_low_eq.count(); + num_active_constraints_eq_up = qpwork.active_set_up_eq.count(); // active_set_low and active_setup_low already computed in ADMM - VecBool active_constraints_ineq = - qpwork.active_set_up || qpwork.active_set_low; - isize num_active_constraints_ineq = active_constraints_ineq.count(); - isize num_active_constraints_ineq_low = qpwork.active_set_low.count(); - isize num_active_constraints_ineq_up = qpwork.active_set_up.count(); + active_constraints_ineq = qpwork.active_set_up || qpwork.active_set_low; + num_active_constraints_ineq = active_constraints_ineq.count(); + num_active_constraints_ineq_low = qpwork.active_set_low.count(); + num_active_constraints_ineq_up = qpwork.active_set_up.count(); - isize num_active_constraints = + num_active_constraints = num_active_constraints_eq + num_active_constraints_ineq; - isize inner_pb_dim = qpmodel.dim + num_active_constraints; - - if (num_active_constraints == 0) { - qpresults.info.polish_status = PolishStatus::POLISH_NO_ACTIVE_SET_FOUND; - if (qpsettings.verbose) { - std::cout << "\033[1;34m[polishing: no active set found. " - << (qpsettings.resume_admm ? "Resume ADMM" : "") << "]\033[0m" - << std::endl; - } - return; - } - - // Build the reducted matrices of the constraints - Mat A_low(num_active_constraints_eq_low, qpmodel.dim); - Mat A_up(num_active_constraints_eq_up, qpmodel.dim); - + inner_pb_dim = qpmodel.dim + num_active_constraints; +} +/*! + * Build the reduced matrices of constraints in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +build_reduced_constraints_matrices( // + const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const isize n_constraints, + Mat& A_low, + Mat& A_up, + Mat& C_low, + Mat& C_up) +{ isize low_index = 0; isize up_index = 0; for (isize i = 0; i < qpmodel.n_eq; ++i) { @@ -668,9 +655,6 @@ polish(const Settings& qpsettings, } } - Mat C_low(num_active_constraints_ineq_low, qpmodel.dim); - Mat C_up(num_active_constraints_ineq_up, qpmodel.dim); - low_index = 0; up_index = 0; Vec tmp_low(qpmodel.dim); @@ -699,13 +683,39 @@ polish(const Settings& qpsettings, ++up_index; } } - +} +/*! + * Build the matrices K and K + Delta_K in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + */ +template +void +build_kkt_matrices_polishing( // + const Settings& qpsettings, + const Model& qpmodel, + Workspace& qpwork, + const HessianType hessian_type, + Mat& k_polish, + Mat& k_plus_delta_k_polish, + Mat A_low, + Mat A_up, + Mat C_low, + Mat C_up, + isize num_active_constraints_eq_low, + isize num_active_constraints_ineq_low, + isize num_active_constraints_eq_up, + isize num_active_constraints_ineq_up, + isize num_active_constraints) +{ // Construction of K isize row; isize col; - Mat k_polish(inner_pb_dim, inner_pb_dim); - Mat k_plus_delta_k_polish(inner_pb_dim, inner_pb_dim); + std::cout << "B" << std::endl; switch (hessian_type) { case HessianType::Dense: @@ -719,6 +729,8 @@ polish(const Settings& qpsettings, break; } + std::cout << "C" << std::endl; + col = qpmodel.dim; k_polish.block(0, col, qpmodel.dim, num_active_constraints_eq_low) = A_low.transpose(); @@ -750,8 +762,11 @@ polish(const Settings& qpsettings, k_polish.bottomRightCorner(num_active_constraints, num_active_constraints) .setZero(); - // Construction and factorization of K + Delta_K + std::cout << "D" << std::endl; + + // Construction of K + Delta_K k_plus_delta_k_polish = k_polish; + std::cout << "E" << std::endl; k_plus_delta_k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) .diagonal() .array() += qpsettings.delta; @@ -760,16 +775,32 @@ polish(const Settings& qpsettings, .diagonal() .array() -= qpsettings.delta; - proxsuite::linalg::veg::dynstack::DynStackMut stack{ - proxsuite::linalg::veg::from_slice_mut, - qpwork.ldl_stack.as_mut(), - }; - - qpwork.ldl.factorize(k_plus_delta_k_polish.transpose(), stack); - - // Construction of rhs_polish - low_index = 0; - up_index = 0; + std::cout << "F" << std::endl; +} +/*! + * Build the right hand side (-g, l_L, u_U) in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + */ +template +void +build_rhs_polishing( // + const Settings& qpsettings, + const Model& qpmodel, + Workspace& qpwork, + const HessianType hessian_type, + const isize n_constraints, + Vec& rhs_polish, + isize num_active_constraints_eq_low, + isize num_active_constraints_ineq_low, + isize num_active_constraints_eq_up, + isize num_active_constraints_ineq_up) +{ + isize low_index = 0; + isize up_index = 0; Vec b_low(num_active_constraints_eq_low); Vec b_up(num_active_constraints_eq_up); for (isize i = 0; i < qpmodel.n_eq; ++i) { @@ -806,7 +837,8 @@ polish(const Settings& qpsettings, } } - Vec rhs_polish(inner_pb_dim); + isize row; + isize col; row = qpmodel.dim; rhs_polish.head(row) = -qpwork.g_scaled; @@ -818,11 +850,32 @@ polish(const Settings& qpsettings, row += num_active_constraints_ineq_low; rhs_polish.segment(row, num_active_constraints_eq_up) = b_up; rhs_polish.tail(num_active_constraints_ineq_up) = u_up; - +} +/*! + * Iterative refinement in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + */ +template +void +iterative_refinement_polishing( // + const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const DenseBackend& dense_backend, + const isize n_constraints, + Vec& hat_t, + Vec rhs_polish, + Mat k_polish, + isize inner_pb_dim, + plv::dynstack::DynStackMut& stack) +{ // Solve the reduced system before iterative refinement - Vec hat_t(inner_pb_dim); hat_t = rhs_polish; - solve_linear_system(hat_t, qpmodel, qpresults, @@ -851,12 +904,33 @@ polish(const Settings& qpsettings, hat_t = hat_t + delta_hat_t; } - - // Update of (x, y, z) +} +/*! + * Update primal and dual variables in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solver results. + */ +template +void +update_variables_polishing( // + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + Vec hat_t, + isize num_active_constraints_eq_low, + isize num_active_constraints_eq_up, + isize num_active_constraints_ineq_low) +{ + // Get (x, y, z) from hat_t qpresults.x = hat_t.head(qpmodel.dim); - low_index = 0; - up_index = 0; + isize low_index = 0; + isize up_index = 0; for (isize i = 0; i < qpmodel.n_eq; ++i) { if (qpwork.active_set_low_eq(i)) { qpresults.y(i) = hat_t(qpmodel.dim + low_index); @@ -898,13 +972,40 @@ polish(const Settings& qpsettings, qpwork.l_scaled.cwiseMax(qpwork.zeta_in.cwiseMin(qpwork.u_scaled)); } qpresults.z = tmp_z - qpwork.zeta_in; - - // Timing polishing - qpwork.time_polishing = qpwork.timer_polish.elapsed().user; - - // Update of residuals - bool is_feasible = false; - +} +/*! + * Update residuals in polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpresults solver results. + * @param qpsettings solver settings. + */ +template +void +update_residuals_polishing( // + const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + const HessianType hessian_type, + preconditioner::RuizEquilibration& ruiz, + T& primal_feasibility_lhs, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap, + bool& is_feasible) +{ global_primal_residual(qpmodel, qpresults, qpsettings, @@ -960,10 +1061,24 @@ polish(const Settings& qpsettings, } } } - - // Check polish success - bool polish_success; - +} +/*! + * Check the success of solution polishing. + * + * @param qpresults solver results. + * @param qpsettings solver settings. + */ +template +void +check_success_polishing( // + const Settings& qpsettings, + Results& qpresults, + T pri_res_admm, + T dua_res_admm, + T duality_gap_admm, + bool is_feasible, + bool& polish_success) +{ if (qpsettings.check_duality_gap) { bool polish_success_primal_dual = ((qpresults.info.pri_res < pri_res_admm) && @@ -994,6 +1109,268 @@ polish(const Settings& qpsettings, if (!is_feasible) { polish_success = false; } +} +/*! + * Print polishing line after the ADMM iterations. + * + * @param qpresults solver results. + * @param qpsettings solver settings. + */ +template +void +print_polishing_line( // + const Settings& qpsettings, + Results& qpresults, + bool is_feasible) +{ + std::cout << "\033[1;34m[polishing]\033[0m" << std::endl; + std::cout << std::scientific << std::setw(2) << std::setprecision(2) + << " | primal residual=" << qpresults.info.pri_res + << " | dual residual=" << qpresults.info.dua_res + << " | duality gap=" << qpresults.info.duality_gap + << " | delta=" << qpsettings.delta << std::endl; + switch (qpresults.info.polish_status) { + case PolishStatus::POLISH_SUCCEED: { + std::cout << "\033[1;34m[polishing: succeed]\033[0m" << std::endl; + break; + } + case PolishStatus::POLISH_FAILED: { + if (!is_feasible) { + std::cout << "\033[1;34m[polishing: infeasible solution. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") << "]\033[0m" + << std::endl; + } else { + std::cout << "\033[1;34m[polishing: failed. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") << "]\033[0m" + << std::endl; + } + break; + } + case PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { + break; + } + case PolishStatus::POLISH_NOT_RUN: { + break; + } + } +} +/*! + * Solution polishing. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + */ +template +void +polish(const Settings& qpsettings, + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const bool box_constraints, + const isize n_constraints, + const DenseBackend dense_backend, + const HessianType hessian_type, + preconditioner::RuizEquilibration& ruiz, + T& primal_feasibility_lhs, + T& primal_feasibility_eq_rhs_0, + T& primal_feasibility_in_rhs_0, + T& primal_feasibility_eq_lhs, + T& primal_feasibility_in_lhs, + T& dual_feasibility_lhs, + T& dual_feasibility_rhs_0, + T& dual_feasibility_rhs_1, + T& dual_feasibility_rhs_3, + T& rhs_duality_gap, + T& duality_gap) +{ + + // Timing polishing + qpwork.timer_polish.stop(); + qpwork.timer_polish.start(); + + // ADMM solution + auto x_admm = qpresults.x; + auto y_admm = qpresults.y; + auto z_admm = qpresults.z; + auto zeta_in_admm = qpwork.zeta_in; + + auto pri_res_admm = qpresults.info.pri_res; + auto dua_res_admm = qpresults.info.dua_res; + auto duality_gap_admm = qpresults.info.duality_gap; + + // Upper and lower active constraints + VecBool active_constraints_eq; + isize num_active_constraints_eq; + isize num_active_constraints_eq_low; + isize num_active_constraints_eq_up; + + VecBool active_constraints_ineq; + isize num_active_constraints_ineq; + isize num_active_constraints_ineq_low; + isize num_active_constraints_ineq_up; + + isize num_active_constraints; + isize inner_pb_dim; + + find_active_sets(qpsettings, + qpmodel, + qpresults, + qpwork, + active_constraints_eq, + num_active_constraints_eq, + num_active_constraints_eq_low, + num_active_constraints_eq_up, + active_constraints_ineq, + num_active_constraints_ineq, + num_active_constraints_ineq_low, + num_active_constraints_ineq_up, + num_active_constraints, + inner_pb_dim); + + std::cout << "qpresults.z: " << qpresults.z << std::endl; + std::cout << "active_set_up: " << qpwork.active_set_up << std::endl; + std::cout << "active_set_low: " << qpwork.active_set_low << std::endl; + std::cout << "active_constraints_ineq: " << active_constraints_ineq + << std::endl; + + if (num_active_constraints == 0) { + qpresults.info.polish_status = PolishStatus::POLISH_NO_ACTIVE_SET_FOUND; + if (qpsettings.verbose) { + std::cout << "\033[1;34m[polishing: no active set found. " + << (qpsettings.resume_admm ? "Resume ADMM" : "") << "]\033[0m" + << std::endl; + } + return; + } + + // Build the reduced matrices of the constraints + Mat A_low(num_active_constraints_eq_low, qpmodel.dim); + Mat A_up(num_active_constraints_eq_up, qpmodel.dim); + + Mat C_low(num_active_constraints_ineq_low, qpmodel.dim); + Mat C_up(num_active_constraints_ineq_up, qpmodel.dim); + + build_reduced_constraints_matrices(qpsettings, + qpmodel, + qpresults, + qpwork, + n_constraints, + A_low, + A_up, + C_low, + C_up); + + // Build the KKT and regularized KKT matrices + Mat k_polish(inner_pb_dim, inner_pb_dim); + Mat k_plus_delta_k_polish(inner_pb_dim, inner_pb_dim); + + std::cout << "A" << std::endl; + + build_kkt_matrices_polishing(qpsettings, + qpmodel, + qpwork, + hessian_type, + k_polish, + k_plus_delta_k_polish, + A_low, + A_up, + C_low, + C_up, + num_active_constraints_eq_low, + num_active_constraints_ineq_low, + num_active_constraints_eq_up, + num_active_constraints_ineq_up, + num_active_constraints); + + std::cout << "G" << std::endl; + + proxsuite::linalg::veg::dynstack::DynStackMut stack{ + proxsuite::linalg::veg::from_slice_mut, + qpwork.ldl_stack.as_mut(), + }; + + qpwork.ldl.factorize(k_plus_delta_k_polish.transpose(), stack); + + // Construction of rhs_polish + Vec rhs_polish(inner_pb_dim); + + build_rhs_polishing(qpsettings, + qpmodel, + qpwork, + hessian_type, + n_constraints, + rhs_polish, + num_active_constraints_eq_low, + num_active_constraints_ineq_low, + num_active_constraints_eq_up, + num_active_constraints_ineq_up); + + // Iterative refinement + Vec hat_t(inner_pb_dim); + + iterative_refinement_polishing(qpsettings, + qpmodel, + qpresults, + qpwork, + dense_backend, + n_constraints, + hat_t, + rhs_polish, + k_polish, + inner_pb_dim, + stack); + + // Update of (x, y, z) + update_variables_polishing(qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + hat_t, + num_active_constraints_eq_low, + num_active_constraints_eq_up, + num_active_constraints_ineq_low); + + // Timing polishing + qpwork.time_polishing = qpwork.timer_polish.elapsed().user; + + // Update of residuals + bool is_feasible = false; + + update_residuals_polishing(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + hessian_type, + ruiz, + primal_feasibility_lhs, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + is_feasible); + + // Check polish success + bool polish_success; + + check_success_polishing(qpsettings, + qpresults, + pri_res_admm, + dua_res_admm, + duality_gap_admm, + is_feasible, + polish_success); if (polish_success) { qpresults.info.polish_status = PolishStatus::POLISH_SUCCEED; @@ -1003,36 +1380,7 @@ polish(const Settings& qpsettings, // Print polishing line if (qpsettings.verbose) { - std::cout << "\033[1;34m[polishing]\033[0m" << std::endl; - std::cout << std::scientific << std::setw(2) << std::setprecision(2) - << " | primal residual=" << qpresults.info.pri_res - << " | dual residual=" << qpresults.info.dua_res - << " | duality gap=" << qpresults.info.duality_gap - << " | delta=" << qpsettings.delta << std::endl; - switch (qpresults.info.polish_status) { - case PolishStatus::POLISH_SUCCEED: { - std::cout << "\033[1;34m[polishing: succeed]\033[0m" << std::endl; - break; - } - case PolishStatus::POLISH_FAILED: { - if (!is_feasible) { - std::cout << "\033[1;34m[polishing: infeasible solution. " - << (qpsettings.resume_admm ? "Resume ADMM" : "") - << "]\033[0m" << std::endl; - } else { - std::cout << "\033[1;34m[polishing: failed. " - << (qpsettings.resume_admm ? "Resume ADMM" : "") - << "]\033[0m" << std::endl; - } - break; - } - case PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { - break; - } - case PolishStatus::POLISH_NOT_RUN: { - break; - } - } + print_polishing_line(qpsettings, qpresults, is_feasible); } // Go back if polish failed diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bdf78a543..e5a5e2694 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,6 +45,7 @@ proxsuite_test(osqp_dense_qp_unconstrained src/osqp_dense_unconstrained_qp.cpp) proxsuite_test(osqp_dense_qp_solve src/osqp_dense_qp_solve.cpp) proxsuite_test(osqp_dense_qp_with_eq_and_in src/osqp_dense_qp_with_eq_and_in.cpp) proxsuite_test(osqp_cvxpy src/osqp_cvxpy.cpp) +proxsuite_test(osqp_dense_qp_wrapper src/osqp_dense_qp_wrapper.cpp) proxsuite_test(dense_ruiz_equilibration src/dense_ruiz_equilibration.cpp) proxsuite_test(dense_qp_eq src/dense_qp_eq.cpp) proxsuite_test(dense_qp_with_eq_and_in src/dense_qp_with_eq_and_in.cpp) @@ -93,6 +94,7 @@ endif() if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT MSVC) proxsuite_test(dense_maros_meszaros src/dense_maros_meszaros.cpp) + proxsuite_test(osqp_dense_maros_meszaros src/osqp_dense_maros_meszaros.cpp) proxsuite_test(sparse_maros_meszaros src/sparse_maros_meszaros.cpp) endif() diff --git a/test/src/osqp_cvxpy.cpp b/test/src/osqp_cvxpy.cpp index 3a9a35b70..4891b3a88 100644 --- a/test/src/osqp_cvxpy.cpp +++ b/test/src/osqp_cvxpy.cpp @@ -78,6 +78,76 @@ using Vec = Eigen::Matrix; // << results.info.solve_time << std::endl; // } +// // 3 +// DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check +// that " +// "solver stays there") +// { + +// std::cout << "---simple test case from cvxpy, init with solution, check +// that " +// "solver stays there" +// << std::endl; +// T eps_abs = T(1e-4); +// ppd::isize dim = 1; + +// Mat H = Mat(dim, dim); +// H << 20.0; + +// Vec g = Vec(dim); +// g << -10.0; + +// Mat C = Mat(dim, dim); +// C << 1.0; + +// Vec l = Vec(dim); +// l << 0.0; + +// Vec u = Vec(dim); +// u << 1.0; + +// T x_sol = 0.5; + +// proxqp::isize n_in(1); +// proxqp::isize n_eq(0); +// pod::QP qp{ dim, n_eq, n_in }; +// qp.settings.eps_abs = eps_abs; + +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); + +// qp.init(H, g, nullopt, nullopt, C, u, l); + +// ppd::Vec x = ppd::Vec(dim); +// ppd::Vec z = ppd::Vec(n_in); +// x << 0.5; +// z << 0.0; +// qp.solve(x, nullopt, z); + +// T pri_res = (helpers::positive_part(C * qp.results.x - u) + +// helpers::negative_part(C * qp.results.x - l)) +// .lpNorm(); +// T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) +// .lpNorm(); + +// DOCTEST_CHECK(qp.results.info.iter_ext <= 0); +// DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish) { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; +// } +// std::cout << "setup timing " << qp.results.info.setup_time << " solve time" +// << qp.results.info.solve_time << std::endl; +// } + // 2 DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") { @@ -147,74 +217,3 @@ DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } - -// // 3 -// DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check -// that " -// "solver stays there") -// { - -// std::cout << "---simple test case from cvxpy, init with solution, check -// that " -// "solver stays there" -// << std::endl; -// T eps_abs = T(1e-4); -// ppd::isize dim = 1; - -// Mat H = Mat(dim, dim); -// H << 20.0; - -// Vec g = Vec(dim); -// g << -10.0; - -// Mat C = Mat(dim, dim); -// C << 1.0; - -// Vec l = Vec(dim); -// l << 0.0; - -// Vec u = Vec(dim); -// u << 1.0; - -// T x_sol = 0.5; - -// proxqp::isize n_in(1); -// proxqp::isize n_eq(0); -// pod::QP qp{ dim, n_eq, n_in }; -// qp.settings.eps_abs = eps_abs; - -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); - -// qp.init(H, g, nullopt, nullopt, C, u, l); - -// ppd::Vec x = ppd::Vec(dim); -// ppd::Vec z = ppd::Vec(n_in); -// x << 0.5; -// z << 0.0; -// qp.solve(x, nullopt, z); - -// T pri_res = (helpers::positive_part(C * qp.results.x - u) + -// helpers::negative_part(C * qp.results.x - l)) -// .lpNorm(); -// T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) -// .lpNorm(); - -// DOCTEST_CHECK(qp.results.info.iter_ext <= 0); -// DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext -// << std::endl; -// if (qp.settings.polish) { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; -// } -// std::cout << "setup timing " << qp.results.info.setup_time << " solve time -// " -// << qp.results.info.solve_time << std::endl; -// } \ No newline at end of file diff --git a/test/src/osqp_dense_maros_meszaros.cpp b/test/src/osqp_dense_maros_meszaros.cpp new file mode 100644 index 000000000..31f2c8165 --- /dev/null +++ b/test/src/osqp_dense_maros_meszaros.cpp @@ -0,0 +1,170 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include + +using namespace proxsuite; +namespace pod = proxsuite::osqp::dense; + +#define MAROS_MESZAROS_DIR PROBLEM_PATH "/data/maros_meszaros_data/" + +char const* files[] = { + MAROS_MESZAROS_DIR "AUG2D.mat", MAROS_MESZAROS_DIR "AUG2DC.mat", + MAROS_MESZAROS_DIR "AUG2DCQP.mat", MAROS_MESZAROS_DIR "AUG2DQP.mat", + MAROS_MESZAROS_DIR "AUG3D.mat", MAROS_MESZAROS_DIR "AUG3DC.mat", + MAROS_MESZAROS_DIR "AUG3DCQP.mat", MAROS_MESZAROS_DIR "AUG3DQP.mat", + MAROS_MESZAROS_DIR "BOYD1.mat", MAROS_MESZAROS_DIR "BOYD2.mat", + MAROS_MESZAROS_DIR "CONT-050.mat", MAROS_MESZAROS_DIR "CONT-100.mat", + MAROS_MESZAROS_DIR "CONT-101.mat", MAROS_MESZAROS_DIR "CONT-200.mat", + MAROS_MESZAROS_DIR "CONT-201.mat", MAROS_MESZAROS_DIR "CONT-300.mat", + MAROS_MESZAROS_DIR "CVXQP1_L.mat", MAROS_MESZAROS_DIR "CVXQP1_M.mat", + MAROS_MESZAROS_DIR "CVXQP1_S.mat", MAROS_MESZAROS_DIR "CVXQP2_L.mat", + MAROS_MESZAROS_DIR "CVXQP2_M.mat", MAROS_MESZAROS_DIR "CVXQP2_S.mat", + MAROS_MESZAROS_DIR "CVXQP3_L.mat", MAROS_MESZAROS_DIR "CVXQP3_M.mat", + MAROS_MESZAROS_DIR "CVXQP3_S.mat", MAROS_MESZAROS_DIR "DPKLO1.mat", + MAROS_MESZAROS_DIR "DTOC3.mat", MAROS_MESZAROS_DIR "DUAL1.mat", + MAROS_MESZAROS_DIR "DUAL2.mat", MAROS_MESZAROS_DIR "DUAL3.mat", + MAROS_MESZAROS_DIR "DUAL4.mat", MAROS_MESZAROS_DIR "DUALC1.mat", + MAROS_MESZAROS_DIR "DUALC2.mat", MAROS_MESZAROS_DIR "DUALC5.mat", + MAROS_MESZAROS_DIR "DUALC8.mat", MAROS_MESZAROS_DIR "EXDATA.mat", + MAROS_MESZAROS_DIR "GENHS28.mat", MAROS_MESZAROS_DIR "GOULDQP2.mat", + MAROS_MESZAROS_DIR "GOULDQP3.mat", MAROS_MESZAROS_DIR "HS118.mat", + MAROS_MESZAROS_DIR "HS21.mat", MAROS_MESZAROS_DIR "HS268.mat", + MAROS_MESZAROS_DIR "HS35.mat", MAROS_MESZAROS_DIR "HS35MOD.mat", + MAROS_MESZAROS_DIR "HS51.mat", MAROS_MESZAROS_DIR "HS52.mat", + MAROS_MESZAROS_DIR "HS53.mat", MAROS_MESZAROS_DIR "HS76.mat", + MAROS_MESZAROS_DIR "HUES-MOD.mat", MAROS_MESZAROS_DIR "HUESTIS.mat", + MAROS_MESZAROS_DIR "KSIP.mat", MAROS_MESZAROS_DIR "LASER.mat", + MAROS_MESZAROS_DIR "LISWET1.mat", MAROS_MESZAROS_DIR "LISWET10.mat", + MAROS_MESZAROS_DIR "LISWET11.mat", MAROS_MESZAROS_DIR "LISWET12.mat", + MAROS_MESZAROS_DIR "LISWET2.mat", MAROS_MESZAROS_DIR "LISWET3.mat", + MAROS_MESZAROS_DIR "LISWET4.mat", MAROS_MESZAROS_DIR "LISWET5.mat", + MAROS_MESZAROS_DIR "LISWET6.mat", MAROS_MESZAROS_DIR "LISWET7.mat", + MAROS_MESZAROS_DIR "LISWET8.mat", MAROS_MESZAROS_DIR "LISWET9.mat", + MAROS_MESZAROS_DIR "LOTSCHD.mat", MAROS_MESZAROS_DIR "MOSARQP1.mat", + MAROS_MESZAROS_DIR "MOSARQP2.mat", MAROS_MESZAROS_DIR "POWELL20.mat", + MAROS_MESZAROS_DIR "PRIMAL1.mat", MAROS_MESZAROS_DIR "PRIMAL2.mat", + MAROS_MESZAROS_DIR "PRIMAL3.mat", MAROS_MESZAROS_DIR "PRIMAL4.mat", + MAROS_MESZAROS_DIR "PRIMALC1.mat", MAROS_MESZAROS_DIR "PRIMALC2.mat", + MAROS_MESZAROS_DIR "PRIMALC5.mat", MAROS_MESZAROS_DIR "PRIMALC8.mat", + MAROS_MESZAROS_DIR "Q25FV47.mat", MAROS_MESZAROS_DIR "QADLITTL.mat", + MAROS_MESZAROS_DIR "QAFIRO.mat", MAROS_MESZAROS_DIR "QBANDM.mat", + MAROS_MESZAROS_DIR "QBEACONF.mat", MAROS_MESZAROS_DIR "QBORE3D.mat", + MAROS_MESZAROS_DIR "QBRANDY.mat", MAROS_MESZAROS_DIR "QCAPRI.mat", + MAROS_MESZAROS_DIR "QE226.mat", MAROS_MESZAROS_DIR "QETAMACR.mat", + MAROS_MESZAROS_DIR "QFFFFF80.mat", MAROS_MESZAROS_DIR "QFORPLAN.mat", + MAROS_MESZAROS_DIR "QGFRDXPN.mat", MAROS_MESZAROS_DIR "QGROW15.mat", + MAROS_MESZAROS_DIR "QGROW22.mat", MAROS_MESZAROS_DIR "QGROW7.mat", + MAROS_MESZAROS_DIR "QISRAEL.mat", MAROS_MESZAROS_DIR "QPCBLEND.mat", + MAROS_MESZAROS_DIR "QPCBOEI1.mat", MAROS_MESZAROS_DIR "QPCBOEI2.mat", + MAROS_MESZAROS_DIR "QPCSTAIR.mat", MAROS_MESZAROS_DIR "QPILOTNO.mat", + MAROS_MESZAROS_DIR "QPTEST.mat", MAROS_MESZAROS_DIR "QRECIPE.mat", + MAROS_MESZAROS_DIR "QSC205.mat", MAROS_MESZAROS_DIR "QSCAGR25.mat", + MAROS_MESZAROS_DIR "QSCAGR7.mat", MAROS_MESZAROS_DIR "QSCFXM1.mat", + MAROS_MESZAROS_DIR "QSCFXM2.mat", MAROS_MESZAROS_DIR "QSCFXM3.mat", + MAROS_MESZAROS_DIR "QSCORPIO.mat", MAROS_MESZAROS_DIR "QSCRS8.mat", + MAROS_MESZAROS_DIR "QSCSD1.mat", MAROS_MESZAROS_DIR "QSCSD6.mat", + MAROS_MESZAROS_DIR "QSCSD8.mat", MAROS_MESZAROS_DIR "QSCTAP1.mat", + MAROS_MESZAROS_DIR "QSCTAP2.mat", MAROS_MESZAROS_DIR "QSCTAP3.mat", + MAROS_MESZAROS_DIR "QSEBA.mat", MAROS_MESZAROS_DIR "QSHARE1B.mat", + MAROS_MESZAROS_DIR "QSHARE2B.mat", MAROS_MESZAROS_DIR "QSHELL.mat", + MAROS_MESZAROS_DIR "QSHIP04L.mat", MAROS_MESZAROS_DIR "QSHIP04S.mat", + MAROS_MESZAROS_DIR "QSHIP08L.mat", MAROS_MESZAROS_DIR "QSHIP08S.mat", + MAROS_MESZAROS_DIR "QSHIP12L.mat", MAROS_MESZAROS_DIR "QSHIP12S.mat", + MAROS_MESZAROS_DIR "QSIERRA.mat", MAROS_MESZAROS_DIR "QSTAIR.mat", + MAROS_MESZAROS_DIR "QSTANDAT.mat", MAROS_MESZAROS_DIR "S268.mat", + MAROS_MESZAROS_DIR "STADAT1.mat", MAROS_MESZAROS_DIR "STADAT2.mat", + MAROS_MESZAROS_DIR "STADAT3.mat", MAROS_MESZAROS_DIR "STCQP1.mat", + MAROS_MESZAROS_DIR "STCQP2.mat", MAROS_MESZAROS_DIR "TAME.mat", + MAROS_MESZAROS_DIR "UBH1.mat", MAROS_MESZAROS_DIR "VALUES.mat", + MAROS_MESZAROS_DIR "YAO.mat", MAROS_MESZAROS_DIR "ZECEVIC2.mat", +}; + +TEST_CASE("dense maros meszaros using the api") +{ + using T = double; + using isize = proxqp::utils::isize; + proxsuite::proxqp::Timer timer; + T elapsed_time = 0.0; + + for (auto const* file : files) { + auto qp = load_qp(file); + isize n = qp.P.rows(); + isize n_eq_in = qp.A.rows(); + + const bool skip = n > 1000 || n_eq_in > 1000; + if (skip) { + std::cout << " path: " << qp.filename << " n: " << n + << " n_eq+n_in: " << n_eq_in << " - skipping" << std::endl; + } else { + std::cout << " path: " << qp.filename << " n: " << n + << " n_eq+n_in: " << n_eq_in << std::endl; + } + + if (!skip) { + + auto preprocessed = preprocess_qp(qp); + auto& H = preprocessed.H; + auto& A = preprocessed.A; + auto& C = preprocessed.C; + auto& g = preprocessed.g; + auto& b = preprocessed.b; + auto& u = preprocessed.u; + auto& l = preprocessed.l; + + isize dim = H.rows(); + isize n_eq = A.rows(); + isize n_in = C.rows(); + timer.stop(); + timer.start(); + pod::QP qp{ + dim, n_eq, n_in, false, proxsuite::proxqp::DenseBackend::PrimalDualLDLT + }; // creating QP object + qp.init(H, g, A, b, C, l, u); + + qp.settings.eps_abs = 2e-8; + qp.settings.eps_rel = 0; + qp.settings.eps_primal_inf = 1e-12; + qp.settings.eps_dual_inf = 1e-12; + auto& eps = qp.settings.eps_abs; + + for (size_t it = 0; it < 2; ++it) { + if (it > 0) + qp.settings.initial_guess = proxsuite::proxqp::InitialGuessStatus:: + WARM_START_WITH_PREVIOUS_RESULT; + + qp.solve(); + const auto& x = qp.results.x; + const auto& y = qp.results.y; + const auto& z = qp.results.z; + + T prim_eq = proxqp::dense::infty_norm(A * x - b); + T prim_in = + proxqp::dense::infty_norm(helpers::positive_part(C * x - u) + + helpers::negative_part(C * x - l)); + std::cout << "primal residual " << std::max(prim_eq, prim_in) + << std::endl; + std::cout << "dual residual " + << proxqp::dense::infty_norm(H * x + g + A.transpose() * y + + C.transpose() * z) + << std::endl; + std::cout << "iter " << qp.results.info.iter << std::endl; + CHECK(proxqp::dense::infty_norm(H * x + g + A.transpose() * y + + C.transpose() * z) < 2 * eps); + CHECK(proxqp::dense::infty_norm(A * x - b) > -eps); + CHECK((C * x - l).minCoeff() > -eps); + CHECK((C * x - u).maxCoeff() < eps); + + if (it > 0) { + CHECK(qp.results.info.iter == 0); + } + } + timer.stop(); + elapsed_time += timer.elapsed().user; + } + } + std::cout << "timings total : \t" << elapsed_time * 1e-3 << "ms" << std::endl; +} diff --git a/test/src/osqp_dense_qp_solve.cpp b/test/src/osqp_dense_qp_solve.cpp index 92ea57b21..7683fb724 100644 --- a/test/src/osqp_dense_qp_solve.cpp +++ b/test/src/osqp_dense_qp_solve.cpp @@ -100,334 +100,342 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") } } -DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " - "inequality constraints: test solve function") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test solve function---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - nullopt, - T(1E-2), - T(1E1)); - - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " - "inequality constraints: test solve with different rho value") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test solve with different rho value---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - T(1.E-7), - T(1E-2), - T(1E1)); - DOCTEST_CHECK(results.info.rho == T(1.E-7)); - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test solve with different mu_eq and mu_in values") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test solve with different mu_eq and " - "mu_in values---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - nullopt, - T(1.E-1), - T(1.E0)); - // Note: - // Different alternative values than for proxqp, due to different - // suggestions for default_mu_eq and default_mu_in between proxqp paper - // and osqp paper - but same update (*10 for mu_eq and /10 for mu_in) - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " - "inequality constraints: test warm starting") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test warm starting---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - auto x_wm = pp::utils::rand::vector_rand(dim); - auto y_wm = pp::utils::rand::vector_rand(n_eq); - auto z_wm = pp::utils::rand::vector_rand(n_in); - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - x_wm, - y_wm, - z_wm, - eps_abs, - 0, - nullopt, - T(1E-2), - T(1E1)); - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " - "inequality constraints: test verbose = true") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test verbose = true ---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - bool verbose = true; - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - nullopt, - T(1E-2), - T(1E1), - verbose); - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " - "inequality constraints: test no initial guess") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test no initial guess ---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - pp::utils::rand::set_seed(1); - ppd::isize dim = 10; - - ppd::isize n_eq(dim / 4); - ppd::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pp::InitialGuessStatus initial_guess = - pp::InitialGuessStatus::NO_INITIAL_GUESS; - pp::Results results = pod::solve(qp.H, - qp.g, - qp.A, - qp.b, - qp.C, - qp.l, - qp.u, - nullopt, - nullopt, - nullopt, - eps_abs, - 0, - nullopt, - T(1E-2), - T(1E1), - nullopt, - true, - true, - nullopt, - initial_guess); - T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), - (helpers::positive_part(qp.C * results.x - qp.u) + - helpers::negative_part(qp.C * results.x - qp.l)) - .lpNorm()); - T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + - qp.C.transpose() * results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration (admm): " << results.info.iter_ext - << std::endl; - std::cout << "setup timing " << results.info.setup_time << " solve time " - << results.info.solve_time << std::endl; -} +// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " +// "inequality constraints: test solve function") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test solve function---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// nullopt, +// T(1E-2), +// T(1E1)); + +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " +// "inequality constraints: test solve with different rho +// value") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test solve with different rho +// value---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// T(1.E-7), +// T(1E-2), +// T(1E1)); +// DOCTEST_CHECK(results.info.rho == T(1.E-7)); +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// DOCTEST_TEST_CASE( +// "sparse random strongly convex qp with equality and " +// "inequality constraints: test solve with different mu_eq and mu_in values") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test solve with different mu_eq and " +// "mu_in values---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// nullopt, +// T(1.E-1), +// T(1.E0)); +// // Note: +// // Different alternative values than for proxqp, due to different +// // suggestions for default_mu_eq and default_mu_in between proxqp paper +// // and osqp paper - but same update (*10 for mu_eq and /10 for mu_in) +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " +// "inequality constraints: test warm starting") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test warm starting---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// auto x_wm = pp::utils::rand::vector_rand(dim); +// auto y_wm = pp::utils::rand::vector_rand(n_eq); +// auto z_wm = pp::utils::rand::vector_rand(n_in); +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// x_wm, +// y_wm, +// z_wm, +// eps_abs, +// 0, +// nullopt, +// T(1E-2), +// T(1E1)); +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " +// "inequality constraints: test verbose = true") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test verbose = true ---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// bool verbose = true; +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// nullopt, +// T(1E-2), +// T(1E1), +// verbose); +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } + +// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " +// "inequality constraints: test no initial guess") +// { + +// std::cout << "---testing sparse random strongly convex qp with equality and +// " +// "inequality constraints: test no initial guess ---" +// << std::endl; +// double sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// pp::utils::rand::set_seed(1); +// ppd::isize dim = 10; + +// ppd::isize n_eq(dim / 4); +// ppd::isize n_in(dim / 4); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// pp::InitialGuessStatus initial_guess = +// pp::InitialGuessStatus::NO_INITIAL_GUESS; +// pp::Results results = pod::solve(qp.H, +// qp.g, +// qp.A, +// qp.b, +// qp.C, +// qp.l, +// qp.u, +// nullopt, +// nullopt, +// nullopt, +// eps_abs, +// 0, +// nullopt, +// T(1E-2), +// T(1E1), +// nullopt, +// true, +// true, +// nullopt, +// initial_guess); +// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), +// (helpers::positive_part(qp.C * results.x - qp.u) + +// helpers::negative_part(qp.C * results.x - qp.l)) +// .lpNorm()); +// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + +// qp.C.transpose() * results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration (admm): " << results.info.iter_ext +// << std::endl; +// std::cout << "setup timing " << results.info.setup_time << " solve time " +// << results.info.solve_time << std::endl; +// } diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index d53733d9c..0a2eb7231 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -14,8 +14,8 @@ using namespace proxsuite; namespace pod = proxsuite::osqp::dense; // DOCTEST_TEST_CASE( -// "sparse random strongly convex qp with equality and inequality constraints -// " "and increasing dimension using wrapper API") +// "sparse random strongly convex qp with equality and inequality constraints" +// "and increasing dimension using wrapper API") // { // std::cout @@ -201,34 +201,112 @@ namespace pod = proxsuite::osqp::dense; // } // } -// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate -// inequality " -// "constraints and increasing dimension using the API") -// { +DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate inequality " + "constraints and increasing dimension using the API") +{ -// std::cout -// << "---testing sparse random strongly convex qp with degenerate " -// "inequality constraints and increasing dimension using the API---" -// << std::endl; -// T sparsity_factor = 0.45; + std::cout + << "---testing sparse random strongly convex qp with degenerate " + "inequality constraints and increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.45; + T eps_abs = T(1e-9); + T strong_convexity_factor(1e-2); + proxqp::utils::rand::set_seed(1); + // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + // for (proxqp::isize dim = 10; dim < 110; dim += 100) { + proxqp::isize dim = 10; + { + proxqp::isize m(dim / 4); + proxqp::isize n_in(2 * m); + proxqp::isize n_eq(0); + proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( + dim, + n_eq, + m, // it n_in = 2 * m, it doubles the inequality constraints + sparsity_factor, + strong_convexity_factor); + std::cout << "dim: " << dim << std::endl; + std::cout << "n_in: " << n_in << std::endl; + std::cout << "qp_random.C" << std::endl << qp_random.C << std::endl; + std::cout << "qp_random.l" << std::endl << qp_random.l << std::endl; + std::cout << "qp_random.u" << std::endl << qp_random.u << std::endl; + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); // Memory error here + // DOCTEST_CHECK(qp.results.info.status == + // proxqp::QPSolverOutput::PROXQP_SOLVED); + // 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()); + // 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) + // .lpNorm(); + // DOCTEST_CHECK(pri_res <= eps_abs); + // DOCTEST_CHECK(dua_res <= eps_abs); + + // std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + // << " nin: " << n_in << std::endl; + // std::cout << "primal residual: " << pri_res << std::endl; + // std::cout << "dual residual: " << dua_res << std::endl; + // std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + // << std::endl; + // if (qp.settings.polish && + // !(qp.results.info.polish_status == + // proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + // qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) + // { + // std::cout << "number of iterations (polishing): " << + // qp.settings.polish_refine_iter + // << std::endl; + // } + } +} + +// DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " +// "increasing dimension using the API") +// { +// srand(1); +// std::cout << "---testing linear problem with inequality constraints and " +// "increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.15; // T eps_abs = T(1e-9); -// T strong_convexity_factor(1e-2); // proxqp::utils::rand::set_seed(1); // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// proxqp::isize m(dim / 4); -// proxqp::isize n_in(2 * m); +// proxqp::isize n_in(dim / 2); // proxqp::isize n_eq(0); -// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( -// dim, -// n_eq, -// m, // it n_in = 2 * m, it doubles the inequality constraints -// sparsity_factor, -// strong_convexity_factor); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_not_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor); +// qp_random.H.setZero(); +// auto z_sol = proxqp::utils::rand::vector_rand(n_in); +// qp_random.g = -qp_random.C.transpose() * +// z_sol; // make sure the LP is bounded within the feasible +// set +// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" +// // << qp.l << std::endl; // pod::QP qp{ dim, n_eq, n_in }; // creating QP object // qp.settings.eps_abs = eps_abs; // qp.settings.eps_rel = 0; // qp.settings.default_mu_eq = T(1.E-2); // qp.settings.default_mu_in = T(1.E1); +// if (dim == 610) { +// qp.settings.verbose = true; +// } // qp.init(qp_random.H, // qp_random.g, // qp_random.A, @@ -237,8 +315,6 @@ namespace pod = proxsuite::osqp::dense; // qp_random.l, // qp_random.u); // qp.solve(); -// DOCTEST_CHECK(qp.results.info.status == -// proxqp::QPSolverOutput::PROXQP_SOLVED); // 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) + @@ -258,80 +334,12 @@ namespace pod = proxsuite::osqp::dense; // std::cout << "number of iterations (admm): " << qp.results.info.iter_ext // << std::endl; // if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) -// { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) { +// std::cout << "number of iterations (polishing): " +// << qp.settings.polish_refine_iter << std::endl; // } // } // } - -DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " - "increasing dimension using the API") -{ - srand(1); - std::cout << "---testing linear problem with inequality constraints and " - "increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - proxqp::isize n_in(dim / 2); - proxqp::isize n_eq(0); - proxqp::dense::Model qp_random = - proxqp::utils::dense_not_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor); - qp_random.H.setZero(); - auto z_sol = proxqp::utils::rand::vector_rand(n_in); - qp_random.g = -qp_random.C.transpose() * - z_sol; // make sure the LP is bounded within the feasible set - // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" - // << qp.l << std::endl; - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - if (dim == 610) { - qp.settings.verbose = true; - } - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "number of iterations (admm): " << qp.results.info.iter_ext - << std::endl; - if (qp.settings.polish && - !(qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NOT_RUN)) { - std::cout << "number of iterations (polishing): " - << qp.settings.polish_refine_iter << std::endl; - } - } -} diff --git a/test/src/osqp_dense_qp_wrapper.cpp b/test/src/osqp_dense_qp_wrapper.cpp new file mode 100644 index 000000000..bbf69ce64 --- /dev/null +++ b/test/src/osqp_dense_qp_wrapper.cpp @@ -0,0 +1,7674 @@ +// +// Copyright (c) 2022-2024 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; +using namespace proxsuite::proxqp; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with inequality constraints" + "and empty equality constraints") +{ + std::cout << "---testing sparse random strongly convex qp with inequality " + "constraints " + "and empty equality constraints---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + dense::isize n_eq(0); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + + // Testing with empty but properly sized matrix A of size (0, 10) + std::cout << "Solving QP with" << std::endl; + std::cout << "dim: " << dim << std::endl; + std::cout << "n_eq: " << n_eq << std::endl; + std::cout << "n_in: " << n_in << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; + std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + qp.init(qp_random.H, + qp_random.g, + nullopt, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); + + T pri_res = + (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(); + T dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // Testing with empty matrix A of size (0, 0) + qp_random.A = Eigen::MatrixXd(); + qp_random.b = Eigen::VectorXd(); + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + + std::cout << "Solving QP with" << std::endl; + std::cout << "dim: " << dim << std::endl; + std::cout << "n_eq: " << n_eq << std::endl; + std::cout << "n_in: " << n_in << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; + std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = (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(); + dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // Testing with nullopt + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + + qp3.init(qp_random.H, + qp_random.g, + nullopt, + nullopt, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + + pri_res = (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(); + dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + 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") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update H---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating H" << std::endl; + qp_random.H.setIdentity(); + qp.update(qp_random.H, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update A") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update A---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating A" << std::endl; + qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + qp.update(nullopt, nullopt, qp_random.A, nullopt, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update C") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update C---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating C" << std::endl; + qp_random.C = utils::rand::sparse_matrix_rand_not_compressed( + n_in, dim, sparsity_factor); + qp.update(nullopt, nullopt, nullopt, nullopt, qp_random.C, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update b") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update b---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + auto x_sol = utils::rand::vector_rand(dim); + qp_random.b = qp_random.A * x_sol; + qp.update(nullopt, nullopt, nullopt, qp_random.b, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update u") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update u---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + auto x_sol = utils::rand::vector_rand(dim); + auto delta = utils::Vec(n_in); + for (isize i = 0; i < n_in; ++i) { + delta(i) = utils::rand::uniform_rand(); + } + + qp_random.u = qp_random.C * x_sol + delta; + qp.update(nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, qp_random.u); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update g") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update g---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating g" << std::endl; + auto g = utils::rand::vector_rand(dim); + + qp_random.g = g; + qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and inequality " + "constraints: test update H and A and b and u and l") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update H and A and b and u and l---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + qp_random.H = utils::rand::sparse_positive_definite_rand_not_compressed( + dim, strong_convexity_factor, sparsity_factor); + qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + auto x_sol = utils::rand::vector_rand(dim); + auto delta = utils::Vec(n_in); + for (proxqp::isize i = 0; i < n_in; ++i) { + delta(i) = utils::rand::uniform_rand(); + } + qp_random.b = qp_random.A * x_sol; + qp_random.u = qp_random.C * x_sol + delta; + qp_random.l = qp_random.C * x_sol - delta; + qp.update(qp_random.H, + nullopt, + qp_random.A, + qp_random.b, + nullopt, + qp_random.l, + qp_random.u); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update rho") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update rho---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "rho : " << qp.results.info.rho << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.e-7), + nullopt, + nullopt); // restart the problem with default options + std::cout << "after upating" << std::endl; + std::cout << "rho : " << qp.results.info.rho << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.e-7), + nullopt, + nullopt); + std::cout << "rho : " << qp2.results.info.rho << std::endl; + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update mu_eq and mu_in") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update mu_eq and mu_in---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + nullopt, + T(1.e-2), + T(1.e-3)); + + std::cout << "after upating" << std::endl; + std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + nullopt, + T(1.e-2), + T(1.e-3)); + qp2.solve(); + std::cout << "mu_in : " << qp2.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp2.results.info.mu_eq << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + auto x_wm = utils::rand::vector_rand(dim); + auto y_wm = utils::rand::vector_rand(n_eq); + auto z_wm = utils::rand::vector_rand(n_in); + std::cout << "proposed warm start" << std::endl; + std::cout << "x_wm : " << x_wm << std::endl; + std::cout << "y_wm : " << y_wm << std::endl; + std::cout << "z_wm : " << z_wm << std::endl; + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + qp.solve(x_wm, y_wm, z_wm); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(x_wm, y_wm, z_wm); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test dense init") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test dense init---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init( + Eigen::Matrix( + qp_random.H), + qp_random.g, + Eigen::Matrix( + qp_random.A), + qp_random.b, + Eigen::Matrix( + qp_random.C), + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test with no initial guess") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test with equality constrained initial guess") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test with warm start with previous result") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with warm start with previous result---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + + auto x = qp.results.x; + auto y = qp.results.y; + auto z = qp.results.z; + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.ruiz.scale_primal_in_place({ from_eigen, x }); + qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); + qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.solve(x, y, z); + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp.update( + nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, false); + 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()); + 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) + .lpNorm(); + std::cout << "------using API solving qp with dim with qp after warm start " + "with previous result: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test with cold start option") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with cold start option---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + + auto x = qp.results.x; + auto y = qp.results.y; + auto z = qp.results.z; + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.ruiz.scale_primal_in_place({ from_eigen, x }); + qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); + qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.solve(x, y, z); + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp.update( + nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, 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()); + 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) + .lpNorm(); + std::cout << "------using API solving qp with dim with qp after warm start " + "with cold start option: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with cold start option: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at initialization") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at initialization---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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, + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp with " + "preconditioner derived: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp.ruiz.delta << " ruiz scalar factor " + << qp.ruiz.c << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + false); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp without preconditioner derivation: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " + << qp2.ruiz.c << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at update") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at update---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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, + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp with " + "preconditioner derived: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true); // rederive preconditioner with previous options, i.e., redo + // exact same derivations + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with preconditioner re derived " + "after an update (should get exact same results): " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + std::cout << "------using API solving qp with preconditioner derivation and " + "another object QP: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + qp2.update( + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + false); // use previous preconditioner: should get same result as well + qp2.solve(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp without preconditioner derivation: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " + << qp2.ruiz.c << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +///// TESTS ALL INITIAL GUESS OPTIONS FOR MULTIPLE SOLVES AT ONCE +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with no initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with equality " + "constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with equality constrained initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with equality " + "constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with cold start " + "initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with cold start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with warm start") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: warm start test from init") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace for qp2 : " << qp2.work.dirty << std::endl; + qp2.solve(qp.results.x, qp.results.y, qp.results.z); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve with new QP object" << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +/// TESTS WITH UPDATE + INITIAL GUESS OPTIONS + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update and multiple solve at once with " + "no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with no initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "equality constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with equality constrained initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with equality " + "constrained initial guess and then warm start with previous results") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "cold start initial guess and then cold start option") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with cold start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + update_preconditioner); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "warm start") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + auto x_wm = qp.results.x; // keep previous result + auto y_wm = qp.results.y; + auto z_wm = qp.results.z; + bool update_preconditioner = true; + // test with a false update (the warm start should give the exact solution) + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; + qp.solve(x_wm, y_wm, z_wm); + 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()); + 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) + .lpNorm(); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + x_wm = qp.results.x; // keep previous result + y_wm = qp.results.y; + z_wm = qp.results.z; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + // try now with a real update + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; + qp.solve(x_wm, y_wm, z_wm); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fifth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "ProxQP::dense: Test initializaton with rho for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test initializaton with rho for different initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp.solve(); + CHECK(qp.results.info.rho == T(1.E-7)); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp2.solve(); + CHECK(qp2.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp3.solve(); + CHECK(qp3.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp4.solve(); + CHECK(qp4.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + CHECK(qp5.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test g update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test g update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + auto old_g = qp_random.g; + qp_random.g = utils::rand::vector_rand(dim); + qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + 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()); + 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) + .lpNorm(); + CHECK((qp.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + old_g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK((qp2.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + old_g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK((qp3.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + old_g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK((qp4.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + old_g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp5.solve(); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK((qp5.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test A update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test A update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + auto new_A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + qp.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp.solve(); + pri_res = + std::max((new_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()); + dua_res = + (qp_random.H * qp.results.x + qp_random.g + + new_A.transpose() * qp.results.y + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + CHECK((qp.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp2.solve(); + pri_res = std::max( + (new_A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + new_A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK((qp2.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp3.solve(); + pri_res = std::max( + (new_A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + new_A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK((qp3.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp4.solve(); + pri_res = std::max( + (new_A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + new_A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK((qp4.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp5.solve(); + pri_res = std::max( + (new_A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + new_A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK((qp5.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test rho update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test rho update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + 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()); + 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) + .lpNorm(); + CHECK(qp.results.info.rho == T(1.E-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(qp2.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(qp3.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(qp4.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp5.solve(); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(qp5.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test g update for different warm start with previous " + "result option") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + + std::cout << "Test rho update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // a new linear cost slightly modified + auto g = qp_random.g * 0.95; + + qp.update(nullopt, g, nullopt, nullopt, nullopt, nullopt, nullopt); + 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()); + dua_res = + (qp_random.H * qp.results.x + g + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = + (qp_random.H * qp2.results.x + g + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using warm start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using warm start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + qp.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + qp3.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using cold start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using cold start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using equality constrained initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using no initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using warm start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using warm start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + qp2.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.verbose = true; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using cold start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using cold start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after several solves " + "using equality constrained initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using no initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +TEST_CASE("ProxQP::dense: init must be called before update") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + // call update without init, update calls init internally + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + qp.update(qp_random.H, + qp_random.g, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + 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()); + 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) + .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::pod::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; + //////////////// + + pod::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; + } + + pod::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 + pod::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 + pod::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 + pod::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); +} +TEST_CASE("ProxQP::dense: test primal infeasibility solving") +{ + double sparsity_factor = 0.15; + T eps_abs = T(1e-5); + utils::rand::set_seed(1); + dense::isize dim = 20; + + dense::isize n_eq(dim / 4); + dense::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + // create infeasible problem + qp_random.b.array() += T(10.); + qp_random.u.array() -= T(100.); + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp.settings.primal_infeasibility_solving = true; + qp.settings.eps_primal_inf = T(1.E-4); + qp.settings.eps_dual_inf = T(1.E-4); + qp.settings.verbose = true; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); + + proxsuite::proxqp::utils::Vec rhs_dim(dim); + proxsuite::proxqp::utils::Vec rhs_n_eq(n_eq); + rhs_n_eq.setOnes(); + proxsuite::proxqp::utils::Vec rhs_n_in(n_in); + rhs_n_in.setOnes(); + rhs_dim.noalias() = + qp_random.A.transpose() * rhs_n_eq + qp_random.C.transpose() * rhs_n_in; + T scaled_eps = (rhs_dim).lpNorm() * eps_abs; + + T pri_res = + (qp_random.A.transpose() * (qp_random.A * qp.results.x - qp_random.b) + + qp_random.C.transpose() * + (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(); + T dua_res = (qp_random.H.selfadjointView() * qp.results.x + + qp_random.g + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= scaled_eps); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +TEST_CASE("ProxQP::dense: estimate of minimal eigenvalues using Eigen") +{ + double sparsity_factor = 1.; + T tol = T(1e-6); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-1.); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= + tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += 100 * random_diag.array(); + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +TEST_CASE( + "ProxQP::dense: test estimate of minimal eigenvalue using manual choice") +{ + double sparsity_factor = 1.; + T tol = T(1e-6); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-1.); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + -1); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= + tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + minimal_eigenvalue); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += 100 * random_diag.array(); + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + minimal_eigenvalue); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +TEST_CASE( + "ProxQP::dense: test estimate of minimal eigenvalue using power iteration") +{ + double sparsity_factor = 1.; + T tol = T(1e-3); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-0.5); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK( + std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 0.5) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += + 100 * random_diag.array(); // add some random values to dense matrix + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +DOCTEST_TEST_CASE("check that model.is_valid function for symmetric matrices " + "works for epsilon precision") +{ + Eigen::Matrix matrix = Eigen::Matrix::Random(); + Eigen::Matrix symmetric_mat = matrix + matrix.transpose(); + + symmetric_mat(0, 1) = + symmetric_mat(1, 0) + std::numeric_limits::epsilon(); + + // compare the two checks for symmetry with and without tolerance + bool is_symmetric_without_tolerance = + symmetric_mat.isApprox(symmetric_mat.transpose(), 0.0); + bool is_symmetric_with_tolerance = symmetric_mat.isApprox( + symmetric_mat.transpose(), + std::numeric_limits::epsilon()); + DOCTEST_CHECK(is_symmetric_without_tolerance == false); + DOCTEST_CHECK(is_symmetric_with_tolerance == true); + + // initialize a model with a symmetric matrix as Hessian, this runs + // model.is_valid() that performs the check above + pod::QP qp(3, 0, 0); + qp.init(symmetric_mat, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); +} + +TEST_CASE("ProxQP::dense: test memory allocation when estimating biggest " + "eigenvalue with power iteration") +{ + double sparsity_factor = 1.; + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + Eigen::Matrix H; + Eigen::VectorXd dw(2), rhs(2), err_v(2); + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(1234); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-0.5); + H = qp_random.H; + PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + dense::power_iteration(H, dw, rhs, err_v, 1.E-6, 10000); + PROXSUITE_EIGEN_MALLOC_ALLOWED(); +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with" + "inequality constraints: test PrimalLDLT backend mu update") +{ + + std::cout << "---testing sparse random strongly convex qp with" + "inequality constraints: test PrimalLDLT backend mu update---" + << std::endl; + double sparsity_factor = 1; + utils::rand::set_seed(1); + isize dim = 3; + isize n_eq(0); + isize n_in(9); + 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); + pod::QP qp{ + dim, + n_eq, + n_in, + false, + proxsuite::proxqp::HessianType::Dense, + proxsuite::proxqp::DenseBackend::PrimalLDLT + }; // creating QP object + T eps_abs = T(1e-7); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.compute_timings = true; + qp.settings.verbose = true; + qp.init(qp_random.H, + qp_random.g, + nullopt, + nullopt, + qp_random.C, + nullopt, + qp_random.u); + qp.solve(); + + DOCTEST_CHECK(qp.results.info.mu_updates > 0); + + T pri_res = (helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) + .lpNorm(); + T dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} From d7cad12f403b84dc604b69626c330e98717cb05c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Mon, 19 May 2025 14:49:28 +0200 Subject: [PATCH 21/27] osqp/: mu update: fixed bad performances from error in the calculation of the primal dual residual ratio --- examples/cpp/osqp_overview-simple.cpp | 66 +- include/proxsuite/osqp/dense/solver.hpp | 52 +- include/proxsuite/proxqp/settings.hpp | 13 +- include/proxsuite/solvers/common/utils.hpp | 11 +- test/src/osqp_cvxpy.cpp | 247 ++++---- test/src/osqp_dense_qp_solve.cpp | 672 ++++++++++----------- test/src/osqp_dense_qp_with_eq_and_in.cpp | 452 +++++++------- 7 files changed, 737 insertions(+), 776 deletions(-) diff --git a/examples/cpp/osqp_overview-simple.cpp b/examples/cpp/osqp_overview-simple.cpp index 5c7c7c56d..3d6c3d2ef 100644 --- a/examples/cpp/osqp_overview-simple.cpp +++ b/examples/cpp/osqp_overview-simple.cpp @@ -16,7 +16,7 @@ int main() { T sparsity_factor = 0.15; - ppd::isize dim = 200; + ppd::isize dim = 10; ppd::isize n_eq(dim / 4); ppd::isize n_in(dim / 4); T strong_convexity_factor(1.e-2); @@ -24,56 +24,24 @@ main() ppd::Model qp_random = pp::utils::dense_strongly_convex_qp( dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - // // Proxqp - // ppd::QP qp_proxqp(dim, n_eq, n_in); + pod::QP qp(dim, n_eq, n_in); - // qp_proxqp.init(qp_random.H, - // qp_random.g, - // qp_random.A, - // qp_random.b, - // qp_random.C, - // qp_random.l, - // qp_random.u); - // qp_proxqp.solve(); + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); - // Osqp without update - pod::QP qp_osqp(dim, n_eq, n_in); + qp.settings.update_mu = true; + qp.settings.polish = true; - qp_osqp.settings.default_mu_eq = T(1.E-2); - qp_osqp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); - qp_osqp.settings.polish = true; - - qp_osqp.settings.eps_abs = T(1.E-9); - qp_osqp.settings.high_accuracy = true; - - qp_osqp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp_osqp.solve(); - - // std::cout << "optimal x: " << qp_osqp.results.x << std::endl; - // std::cout << "optimal y: " << qp_osqp.results.y << std::endl; - // std::cout << "optimal z: " << qp_osqp.results.z << std::endl; - - // qp_osqp_1 with mu_update - // pod::QP qp_osqp_1(dim, n_eq, n_in); - - // qp_osqp_1.settings.default_mu_eq = T(1.E-2); - // qp_osqp_1.settings.default_mu_in = T(1.E1); - - // qp_osqp_1.settings.update_mu = true; - - // qp_osqp_1.init(qp_random.H, - // qp_random.g, - // qp_random.A, - // qp_random.b, - // qp_random.C, - // qp_random.l, - // qp_random.u); - // qp_osqp_1.solve(); + 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; } diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 13e5ba6a1..b23ff02b3 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -157,8 +157,6 @@ update_mu(const Settings& qpsettings, if (iteration_condition) { T update_ratio_primal_dual = compute_update_ratio_primal_dual( qpmodel, qpresults, qpwork, box_constraints, hessian_type); - // std::cout << "update_ratio_update_mu :" << update_ratio_primal_dual << - // std::endl; bool value_condition = update_ratio_primal_dual > qpsettings.threshold_ratio_update_mu || @@ -182,9 +180,9 @@ update_mu(const Settings& qpsettings, qpsettings.mu_max_in_inv_osqp); } - if (primal_feasibility_lhs_new >= primal_feasibility_lhs - 1e-6 && - dual_feasibility_lhs_new >= dual_feasibility_lhs - 1e-6 && - qpresults.info.mu_in <= T(1e-3)) { + if (primal_feasibility_lhs_new >= primal_feasibility_lhs && + dual_feasibility_lhs_new >= dual_feasibility_lhs && + (qpresults.info.mu_in <= T(1e-3) || qpresults.info.mu_in >= T(1e5))) { new_mu_in = qpsettings.cold_reset_mu_in_osqp; new_mu_eq = qpsettings.cold_reset_mu_eq_osqp; new_mu_in_inv = qpsettings.cold_reset_mu_in_inv_osqp; @@ -715,8 +713,6 @@ build_kkt_matrices_polishing( // isize row; isize col; - std::cout << "B" << std::endl; - switch (hessian_type) { case HessianType::Dense: k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) = qpwork.H_scaled; @@ -729,8 +725,6 @@ build_kkt_matrices_polishing( // break; } - std::cout << "C" << std::endl; - col = qpmodel.dim; k_polish.block(0, col, qpmodel.dim, num_active_constraints_eq_low) = A_low.transpose(); @@ -762,11 +756,8 @@ build_kkt_matrices_polishing( // k_polish.bottomRightCorner(num_active_constraints, num_active_constraints) .setZero(); - std::cout << "D" << std::endl; - // Construction of K + Delta_K k_plus_delta_k_polish = k_polish; - std::cout << "E" << std::endl; k_plus_delta_k_polish.topLeftCorner(qpmodel.dim, qpmodel.dim) .diagonal() .array() += qpsettings.delta; @@ -774,8 +765,6 @@ build_kkt_matrices_polishing( // .bottomRightCorner(num_active_constraints, num_active_constraints) .diagonal() .array() -= qpsettings.delta; - - std::cout << "F" << std::endl; } /*! * Build the right hand side (-g, l_L, u_U) in polishing. @@ -1230,12 +1219,6 @@ polish(const Settings& qpsettings, num_active_constraints, inner_pb_dim); - std::cout << "qpresults.z: " << qpresults.z << std::endl; - std::cout << "active_set_up: " << qpwork.active_set_up << std::endl; - std::cout << "active_set_low: " << qpwork.active_set_low << std::endl; - std::cout << "active_constraints_ineq: " << active_constraints_ineq - << std::endl; - if (num_active_constraints == 0) { qpresults.info.polish_status = PolishStatus::POLISH_NO_ACTIVE_SET_FOUND; if (qpsettings.verbose) { @@ -1267,8 +1250,6 @@ polish(const Settings& qpsettings, Mat k_polish(inner_pb_dim, inner_pb_dim); Mat k_plus_delta_k_polish(inner_pb_dim, inner_pb_dim); - std::cout << "A" << std::endl; - build_kkt_matrices_polishing(qpsettings, qpmodel, qpwork, @@ -1285,8 +1266,6 @@ polish(const Settings& qpsettings, num_active_constraints_ineq_up, num_active_constraints); - std::cout << "G" << std::endl; - proxsuite::linalg::veg::dynstack::DynStackMut stack{ proxsuite::linalg::veg::from_slice_mut, qpwork.ldl_stack.as_mut(), @@ -1419,6 +1398,7 @@ qp_solve( // { PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + // Setup proxsuite::common::setup_solver(qpsettings, qpmodel, qpresults, @@ -1467,6 +1447,29 @@ qp_solve( // scaled_eps_rel = qpsettings.eps_rel; } + // qpwork.active_set_low_eq.array() = (qpresults.y.array() < 0); + // qpwork.active_set_low_eq.array() = (qpresults.y.array() > 0); + + // qpwork.x_prev = qpresults.x; + // qpwork.y_prev = qpresults.y; + // qpwork.z_prev = qpresults.z; + + // proxsuite::common::compute_scaled_primal_residual_ineq( + // qpsettings, + // qpmodel, + // qpresults, + // qpwork, + // box_constraints, + // ruiz, + // common::QPSolver::OSQP); + + // qpwork.active_set_up.array() = + // (qpwork.primal_residual_in_scaled_up.array() > + // 0); // {zeta_in - u + z > 0} + // qpwork.active_set_low.array() = + // (qpresults.si.array() < 0); // {zeta_in - l + z < 0} + + // Body of solve admm(qpsettings, qpmodel, qpresults, @@ -1547,6 +1550,7 @@ qp_solve( // } } + // End of solve proxsuite::common::unscale_solver( qpsettings, qpmodel, qpresults, box_constraints, ruiz); proxsuite::common::compute_objective(qpmodel, qpresults); diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index 916980020..59e23d8c9 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -309,10 +309,11 @@ struct Settings T cold_reset_mu_in = 1. / 1.1, T cold_reset_mu_eq_inv = 1.1, T cold_reset_mu_in_inv = 1.1, - T cold_reset_mu_eq_osqp = 1. / 1.1, - T cold_reset_mu_in_osqp = 1. / 1.1, - T cold_reset_mu_eq_inv_osqp = 1.1, - T cold_reset_mu_in_inv_osqp = 1.1, + T cold_reset_mu_eq_osqp = + 1.E-2, // TODO: tune (given scenari, algo, experiments) + T cold_reset_mu_in_osqp = 1.E1, // idem + T cold_reset_mu_eq_inv_osqp = 1.E2, // idem + T cold_reset_mu_in_inv_osqp = 1.E-1, // idem T eps_abs = 1.e-5, T eps_rel = 0, bool high_accuracy = false, @@ -333,13 +334,13 @@ struct Settings bool check_duality_gap = false, T eps_duality_gap_abs = 1.e-4, T eps_duality_gap_rel = 0, - bool update_mu = false, + bool update_mu = true, T threshold_ratio_update_mu = 5.0, T threshold_ratio_update_mu_inv = 0.2, T percentage_factorization_time_update_mu = 0.4, UpdateMuIterationCriteria update_mu_iteration_criteria = UpdateMuIterationCriteria::FixedNumberIterations, - isize interval_update_mu = 10, + isize interval_update_mu = 50, bool polish = true, isize polish_refine_iter = 3, T delta = 1.e-6, diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 76eba9055..44c4961f1 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -865,15 +865,18 @@ global_primal_residual_scaled(const ppd::Model& qpmodel, qpresults.si.head(qpmodel.n_in) = helpers::positive_part( - qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - qpmodel.u) + + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - + qpwork.u_scaled) + helpers::negative_part( - qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - qpmodel.l); + qpwork.primal_residual_in_scaled_up.head(qpmodel.n_in) - qpwork.l_scaled); if (box_constraints) { qpresults.si.tail(qpmodel.dim) = helpers::positive_part( - qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - qpmodel.u_box) + + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - + qpwork.u_box_scaled) + helpers::negative_part( - qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - qpmodel.l_box); + qpwork.primal_residual_in_scaled_up.tail(qpmodel.dim) - + qpwork.l_box_scaled); } qpwork.primal_residual_scaled.head(qpmodel.n_eq) = qpresults.se; diff --git a/test/src/osqp_cvxpy.cpp b/test/src/osqp_cvxpy.cpp index 4891b3a88..ac353e93f 100644 --- a/test/src/osqp_cvxpy.cpp +++ b/test/src/osqp_cvxpy.cpp @@ -22,131 +22,61 @@ using Mat = template using Vec = Eigen::Matrix; -// // 1 -// DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility") -// { - -// std::cout << "---3 dim test case from cvxpy, check feasibility " << -// std::endl; T eps_abs = T(1e-9); ppd::isize dim = 3; - -// Mat H = Mat(dim, dim); -// H << 13.0, 12.0, -2.0, 12.0, 17.0, 6.0, -2.0, 6.0, 12.0; - -// Vec g = Vec(dim); -// g << -22.0, -14.5, 13.0; - -// Mat C = Mat(dim, dim); -// C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0; - -// Vec l = Vec(dim); -// l << -1.0, -1.0, -1.0; - -// Vec u = Vec(dim); -// u << 1.0, 1.0, 1.0; -// // pp::Results results = pod::solve( -// // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, -// 0); pp::Results results = pod::solve(H, -// g, -// nullopt, -// nullopt, -// C, -// l, -// u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// T(1.E-6), -// T(1.E-2), -// T(1.E1)); - -// T pri_res = (helpers::positive_part(C * results.x - u) + -// helpers::negative_part(C * results.x - l)) -// .lpNorm(); -// T dua_res = -// (H * results.x + g + C.transpose() * -// results.z).lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// // 3 -// DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check -// that " -// "solver stays there") -// { - -// std::cout << "---simple test case from cvxpy, init with solution, check -// that " -// "solver stays there" -// << std::endl; -// T eps_abs = T(1e-4); -// ppd::isize dim = 1; - -// Mat H = Mat(dim, dim); -// H << 20.0; - -// Vec g = Vec(dim); -// g << -10.0; - -// Mat C = Mat(dim, dim); -// C << 1.0; - -// Vec l = Vec(dim); -// l << 0.0; - -// Vec u = Vec(dim); -// u << 1.0; - -// T x_sol = 0.5; - -// proxqp::isize n_in(1); -// proxqp::isize n_eq(0); -// pod::QP qp{ dim, n_eq, n_in }; -// qp.settings.eps_abs = eps_abs; - -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); - -// qp.init(H, g, nullopt, nullopt, C, u, l); - -// ppd::Vec x = ppd::Vec(dim); -// ppd::Vec z = ppd::Vec(n_in); -// x << 0.5; -// z << 0.0; -// qp.solve(x, nullopt, z); - -// T pri_res = (helpers::positive_part(C * qp.results.x - u) + -// helpers::negative_part(C * qp.results.x - l)) -// .lpNorm(); -// T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) -// .lpNorm(); - -// DOCTEST_CHECK(qp.results.info.iter_ext <= 0); -// DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext -// << std::endl; -// if (qp.settings.polish) { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; -// } -// std::cout << "setup timing " << qp.results.info.setup_time << " solve time" -// << qp.results.info.solve_time << std::endl; -// } +// 1 +DOCTEST_TEST_CASE("3 dim test case from cvxpy, check feasibility") +{ + + std::cout << "---3 dim test case from cvxpy, check feasibility " << std::endl; + T eps_abs = T(1e-9); + ppd::isize dim = 3; + + Mat H = Mat(dim, dim); + H << 13.0, 12.0, -2.0, 12.0, 17.0, 6.0, -2.0, 6.0, 12.0; + + Vec g = Vec(dim); + g << -22.0, -14.5, 13.0; + + Mat C = Mat(dim, dim); + C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0; + + Vec l = Vec(dim); + l << -1.0, -1.0, -1.0; + + Vec u = Vec(dim); + u << 1.0, 1.0, 1.0; + // pp::Results results = pod::solve( + // H, g, nullopt, nullopt, C, l, u, nullopt, nullopt, nullopt, eps_abs, 0); + pp::Results results = pod::solve(H, + g, + nullopt, + nullopt, + C, + l, + u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + T(1.E-6), + T(1.E-2), + T(1.E1)); + + T pri_res = (helpers::positive_part(C * results.x - u) + + helpers::negative_part(C * results.x - l)) + .lpNorm(); + T dua_res = + (H * results.x + g + C.transpose() * results.z).lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} // 2 DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") @@ -217,3 +147,70 @@ DOCTEST_TEST_CASE("simple test case from cvxpy, check feasibility") std::cout << "setup timing " << results.info.setup_time << " solve time " << results.info.solve_time << std::endl; } + +// 3 +DOCTEST_TEST_CASE("simple test case from cvxpy, init with solution, check that " + "solver stays there") +{ + + std::cout << "---simple test case from cvxpy, init with solution, check that " + "solver stays there" + << std::endl; + T eps_abs = T(1e-4); + ppd::isize dim = 1; + + Mat H = Mat(dim, dim); + H << 20.0; + + Vec g = Vec(dim); + g << -10.0; + + Mat C = Mat(dim, dim); + C << 1.0; + + Vec l = Vec(dim); + l << 0.0; + + Vec u = Vec(dim); + u << 1.0; + + T x_sol = 0.5; + + proxqp::isize n_in(1); + proxqp::isize n_eq(0); + pod::QP qp{ dim, n_eq, n_in }; + qp.settings.eps_abs = eps_abs; + + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + + qp.init(H, g, nullopt, nullopt, C, u, l); + + ppd::Vec x = ppd::Vec(dim); + ppd::Vec z = ppd::Vec(n_in); + x << 0.5; + z << 0.0; + qp.solve(x, nullopt, z); + + T pri_res = (helpers::positive_part(C * qp.results.x - u) + + helpers::negative_part(C * qp.results.x - l)) + .lpNorm(); + T dua_res = (H * qp.results.x + g + C.transpose() * qp.results.z) + .lpNorm(); + + DOCTEST_CHECK(qp.results.info.iter_ext <= 0); + DOCTEST_CHECK((x_sol - qp.results.x.coeff(0, 0)) <= eps_abs); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } + std::cout << "setup timing " << qp.results.info.setup_time << " solve time" + << qp.results.info.solve_time << std::endl; +} diff --git a/test/src/osqp_dense_qp_solve.cpp b/test/src/osqp_dense_qp_solve.cpp index 7683fb724..6e9d9f2b1 100644 --- a/test/src/osqp_dense_qp_solve.cpp +++ b/test/src/osqp_dense_qp_solve.cpp @@ -100,342 +100,336 @@ DOCTEST_TEST_CASE("proxqp::dense: test init with fixed sizes matrices") } } -// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " -// "inequality constraints: test solve function") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test solve function---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// nullopt, -// T(1E-2), -// T(1E1)); - -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " -// "inequality constraints: test solve with different rho -// value") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test solve with different rho -// value---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// T(1.E-7), -// T(1E-2), -// T(1E1)); -// DOCTEST_CHECK(results.info.rho == T(1.E-7)); -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// DOCTEST_TEST_CASE( -// "sparse random strongly convex qp with equality and " -// "inequality constraints: test solve with different mu_eq and mu_in values") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test solve with different mu_eq and " -// "mu_in values---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// nullopt, -// T(1.E-1), -// T(1.E0)); -// // Note: -// // Different alternative values than for proxqp, due to different -// // suggestions for default_mu_eq and default_mu_in between proxqp paper -// // and osqp paper - but same update (*10 for mu_eq and /10 for mu_in) -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " -// "inequality constraints: test warm starting") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test warm starting---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// auto x_wm = pp::utils::rand::vector_rand(dim); -// auto y_wm = pp::utils::rand::vector_rand(n_eq); -// auto z_wm = pp::utils::rand::vector_rand(n_in); -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// x_wm, -// y_wm, -// z_wm, -// eps_abs, -// 0, -// nullopt, -// T(1E-2), -// T(1E1)); -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " -// "inequality constraints: test verbose = true") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test verbose = true ---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// bool verbose = true; -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// nullopt, -// T(1E-2), -// T(1E1), -// verbose); -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } - -// DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " -// "inequality constraints: test no initial guess") -// { - -// std::cout << "---testing sparse random strongly convex qp with equality and -// " -// "inequality constraints: test no initial guess ---" -// << std::endl; -// double sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// pp::utils::rand::set_seed(1); -// ppd::isize dim = 10; - -// ppd::isize n_eq(dim / 4); -// ppd::isize n_in(dim / 4); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// pp::InitialGuessStatus initial_guess = -// pp::InitialGuessStatus::NO_INITIAL_GUESS; -// pp::Results results = pod::solve(qp.H, -// qp.g, -// qp.A, -// qp.b, -// qp.C, -// qp.l, -// qp.u, -// nullopt, -// nullopt, -// nullopt, -// eps_abs, -// 0, -// nullopt, -// T(1E-2), -// T(1E1), -// nullopt, -// true, -// true, -// nullopt, -// initial_guess); -// T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), -// (helpers::positive_part(qp.C * results.x - qp.u) + -// helpers::negative_part(qp.C * results.x - qp.l)) -// .lpNorm()); -// T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + -// qp.C.transpose() * results.z) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); - -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "total number of iteration (admm): " << results.info.iter_ext -// << std::endl; -// std::cout << "setup timing " << results.info.setup_time << " solve time " -// << results.info.solve_time << std::endl; -// } +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test solve function") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve function---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1)); + + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different rho " + "value") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different rho " + "value---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + T(1.E-7), + T(1E-2), + T(1E1)); + DOCTEST_CHECK(results.info.rho == T(1.E-7)); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different mu_eq and mu_in values") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test solve with different mu_eq and " + "mu_in values---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1.E-1), + T(1.E0)); + // Note: + // Different alternative values than for proxqp, due to different + // suggestions for default_mu_eq and default_mu_in between proxqp paper + // and osqp paper - but same update (*10 for mu_eq and /10 for mu_in) + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + auto x_wm = pp::utils::rand::vector_rand(dim); + auto y_wm = pp::utils::rand::vector_rand(n_eq); + auto z_wm = pp::utils::rand::vector_rand(n_in); + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + x_wm, + y_wm, + z_wm, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1)); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test verbose = true") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test verbose = true ---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + bool verbose = true; + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1), + verbose); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE("sparse random strongly convex qp with equality and " + "inequality constraints: test no initial guess") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test no initial guess ---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + pp::utils::rand::set_seed(1); + ppd::isize dim = 10; + + ppd::isize n_eq(dim / 4); + ppd::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pp::InitialGuessStatus initial_guess = + pp::InitialGuessStatus::NO_INITIAL_GUESS; + pp::Results results = pod::solve(qp.H, + qp.g, + qp.A, + qp.b, + qp.C, + qp.l, + qp.u, + nullopt, + nullopt, + nullopt, + eps_abs, + 0, + nullopt, + T(1E-2), + T(1E1), + nullopt, + true, + true, + nullopt, + initial_guess); + T pri_res = std::max((qp.A * results.x - qp.b).lpNorm(), + (helpers::positive_part(qp.C * results.x - qp.u) + + helpers::negative_part(qp.C * results.x - qp.l)) + .lpNorm()); + T dua_res = (qp.H * results.x + qp.g + qp.A.transpose() * results.y + + qp.C.transpose() * results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration (admm): " << results.info.iter_ext + << std::endl; + std::cout << "setup timing " << results.info.setup_time << " solve time " + << results.info.solve_time << std::endl; +} diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index 0a2eb7231..c1b556558 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -13,224 +13,147 @@ using T = double; using namespace proxsuite; namespace pod = proxsuite::osqp::dense; -// DOCTEST_TEST_CASE( -// "sparse random strongly convex qp with equality and inequality constraints" -// "and increasing dimension using wrapper API") -// { - -// std::cout -// << "---testing sparse random strongly convex qp with equality and " -// "inequality constraints and increasing dimension using wrapper API---" -// << std::endl; -// T sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// proxqp::utils::rand::set_seed(1); -// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - -// proxqp::isize n_eq(dim / 4); -// proxqp::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); - -// pod::QP qp{ dim, n_eq, n_in }; // creating QP object -// qp.settings.eps_abs = eps_abs; -// qp.settings.eps_rel = 0; -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); -// qp.init(qp_random.H, -// qp_random.g, -// qp_random.A, -// qp_random.b, -// qp_random.C, -// qp_random.l, -// qp_random.u); -// 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()); -// 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) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and inequality constraints" + "and increasing dimension using wrapper API") +{ -// std::cout << "------using API solving qp with dim: " << dim -// << " neq: " << n_eq << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext -// << std::endl; -// if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) -// { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; -// } -// } -// } + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints and increasing dimension using wrapper API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " -// "constraints and increasing dimension using the API") -// { + proxqp::isize n_eq(dim / 4); + proxqp::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); -// std::cout -// << "---testing sparse random strongly convex qp with box inequality " -// "constraints and increasing dimension using the API---" -// << std::endl; -// T sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// proxqp::utils::rand::set_seed(1); -// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); -// proxqp::isize n_eq(0); -// proxqp::isize n_in(dim); -// T strong_convexity_factor(1.e-2); -// proxqp::dense::Model qp_random = -// proxqp::utils::dense_box_constrained_qp( -// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); -// pod::QP qp{ dim, n_eq, n_in }; // creating QP object -// qp.settings.eps_abs = eps_abs; -// qp.settings.eps_rel = 0; -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); -// qp.init(qp_random.H, -// qp_random.g, -// qp_random.A, -// qp_random.b, -// qp_random.C, -// qp_random.l, -// qp_random.u); -// 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()); -// 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) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); -// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq -// << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "number of iterations: " << qp.results.info.iter_ext -// << std::endl; -// if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) -// { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; -// } -// } -// } + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } + } +} -// DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " -// "constraints and increasing dimension using the API") -// { +DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " + "constraints and increasing dimension using the API") +{ -// std::cout -// << "---testing sparse random not strongly convex qp with inequality " -// "constraints and increasing dimension using the API---" -// << std::endl; -// T sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// proxqp::utils::rand::set_seed(1); -// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// proxqp::isize n_in(dim / 2); -// proxqp::isize n_eq(0); -// proxqp::dense::Model qp_random = -// proxqp::utils::dense_not_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor); + std::cout + << "---testing sparse random strongly convex qp with box inequality " + "constraints and increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// pod::QP qp{ dim, n_eq, n_in }; // creating QP object -// qp.settings.eps_abs = eps_abs; -// qp.settings.eps_rel = 0; -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); -// qp.init(qp_random.H, -// qp_random.g, -// qp_random.A, -// qp_random.b, -// qp_random.C, -// qp_random.l, -// qp_random.u); -// 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()); -// 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) -// .lpNorm(); -// DOCTEST_CHECK(pri_res <= eps_abs); -// DOCTEST_CHECK(dua_res <= eps_abs); + proxqp::isize n_eq(0); + proxqp::isize n_in(dim); + T strong_convexity_factor(1.e-2); + proxqp::dense::Model qp_random = proxqp::utils::dense_box_constrained_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); -// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq -// << " nin: " << n_in << std::endl; -// std::cout << "primal residual: " << pri_res << std::endl; -// std::cout << "dual residual: " << dua_res << std::endl; -// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext -// << std::endl; -// if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) -// { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; -// } -// } -// } + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations: " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } + } +} -DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate inequality " +DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " "constraints and increasing dimension using the API") { std::cout - << "---testing sparse random strongly convex qp with degenerate " - "inequality constraints and increasing dimension using the API---" + << "---testing sparse random not strongly convex qp with inequality " + "constraints and increasing dimension using the API---" << std::endl; - T sparsity_factor = 0.45; + T sparsity_factor = 0.15; T eps_abs = T(1e-9); - T strong_convexity_factor(1e-2); proxqp::utils::rand::set_seed(1); - // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - // for (proxqp::isize dim = 10; dim < 110; dim += 100) { - proxqp::isize dim = 10; - { - proxqp::isize m(dim / 4); - proxqp::isize n_in(2 * m); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + proxqp::isize n_in(dim / 2); proxqp::isize n_eq(0); - proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( - dim, - n_eq, - m, // it n_in = 2 * m, it doubles the inequality constraints - sparsity_factor, - strong_convexity_factor); - std::cout << "dim: " << dim << std::endl; - std::cout << "n_in: " << n_in << std::endl; - std::cout << "qp_random.C" << std::endl << qp_random.C << std::endl; - std::cout << "qp_random.l" << std::endl << qp_random.l << std::endl; - std::cout << "qp_random.u" << std::endl << qp_random.u << std::endl; + proxqp::dense::Model qp_random = + proxqp::utils::dense_not_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; qp.settings.eps_rel = 0; @@ -243,39 +166,110 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate inequality " qp_random.C, qp_random.l, qp_random.u); - qp.solve(); // Memory error here - // DOCTEST_CHECK(qp.results.info.status == - // proxqp::QPSolverOutput::PROXQP_SOLVED); - // 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()); - // 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) - // .lpNorm(); - // DOCTEST_CHECK(pri_res <= eps_abs); - // DOCTEST_CHECK(dua_res <= eps_abs); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); - // std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - // << " nin: " << n_in << std::endl; - // std::cout << "primal residual: " << pri_res << std::endl; - // std::cout << "dual residual: " << dua_res << std::endl; - // std::cout << "number of iterations (admm): " << qp.results.info.iter_ext - // << std::endl; - // if (qp.settings.polish && - // !(qp.results.info.polish_status == - // proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - // qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) - // { - // std::cout << "number of iterations (polishing): " << - // qp.settings.polish_refine_iter - // << std::endl; - // } + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } } } +// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate +// inequality " +// "constraints and increasing dimension using the API") +// { + +// std::cout +// << "---testing sparse random strongly convex qp with degenerate " +// "inequality constraints and increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.45; +// T eps_abs = T(1e-9); +// T strong_convexity_factor(1e-2); +// proxqp::utils::rand::set_seed(1); +// // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// // for (proxqp::isize dim = 10; dim < 110; dim += 100) { +// proxqp::isize dim = 10; +// { +// proxqp::isize m(dim / 4); +// proxqp::isize n_in(2 * m); +// proxqp::isize n_eq(0); +// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( +// dim, +// n_eq, +// m, // it n_in = 2 * m, it doubles the inequality constraints +// sparsity_factor, +// strong_convexity_factor); +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// qp.solve(); // Memory error here +// // DOCTEST_CHECK(qp.results.info.status == +// // proxqp::QPSolverOutput::PROXQP_SOLVED); +// // 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()); +// // 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) +// // .lpNorm(); +// // DOCTEST_CHECK(pri_res <= eps_abs); +// // DOCTEST_CHECK(dua_res <= eps_abs); + +// // std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// // << " nin: " << n_in << std::endl; +// // std::cout << "primal residual: " << pri_res << std::endl; +// // std::cout << "dual residual: " << dua_res << std::endl; +// // std::cout << "number of iterations (admm): " << +// qp.results.info.iter_ext +// // << std::endl; +// // if (qp.settings.polish && +// // !(qp.results.info.polish_status == +// // proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// // qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) +// // { +// // std::cout << "number of iterations (polishing): " << +// // qp.settings.polish_refine_iter +// // << std::endl; +// // } +// } +// } + // DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " // "increasing dimension using the API") // { From 841d01b559c6b6bc5b7821bd77619bf34771517c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Mon, 19 May 2025 19:03:29 +0200 Subject: [PATCH 22/27] osqp/: Polishing: Handled the case where ADMM is solved at initialization and compute active sets for polishing --- include/proxsuite/osqp/dense/solver.hpp | 79 ++++++++++++++------ include/proxsuite/proxqp/dense/workspace.hpp | 5 ++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index b23ff02b3..26565292e 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -485,6 +485,9 @@ admm( // scaled_eps_rel, iter); if (is_solved_qp) { + if (iter == 0) { + qpwork.admm_solved_at_init = true; + } break; } @@ -528,6 +531,14 @@ admm( // qpwork.active_set_low.array() = (qpresults.si.array() < 0); // {zeta_in - l + z < 0} + // std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; + // std::cout << "In admm:" << std::endl; + // std::cout << "z:" << std::endl << qpresults.z << std::endl; + // std::cout << "active_set_low:" << std::endl << qpwork.active_set_low << + // std::endl; std::cout << "active_set_up:" << std::endl << + // qpwork.active_set_up << std::endl; std::cout << + // "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; + T primal_feasibility_lhs_new(primal_feasibility_lhs); T dual_feasibility_lhs_new(dual_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, @@ -588,6 +599,8 @@ find_active_sets(const Settings& qpsettings, const Model& qpmodel, Results& qpresults, Workspace& qpwork, + const bool box_constraints, + preconditioner::RuizEquilibration& ruiz, VecBool& active_constraints_eq, isize& num_active_constraints_eq, isize& num_active_constraints_eq_low, @@ -599,7 +612,7 @@ find_active_sets(const Settings& qpsettings, isize& num_active_constraints, isize& inner_pb_dim) { - // Upper and lower active constraints + // Equality qpwork.active_set_low_eq.array() = (qpresults.y.array() < 0); qpwork.active_set_up_eq.array() = (qpresults.y.array() > 0); active_constraints_eq = qpwork.active_set_up_eq || qpwork.active_set_low_eq; @@ -607,7 +620,33 @@ find_active_sets(const Settings& qpsettings, num_active_constraints_eq_low = qpwork.active_set_low_eq.count(); num_active_constraints_eq_up = qpwork.active_set_up_eq.count(); - // active_set_low and active_setup_low already computed in ADMM + // Inequality + if (qpwork.admm_solved_at_init) { + // Compute active sets that could not be computed in ADMM + qpwork.z_prev = qpresults.z; + if (box_constraints) { + qpwork.zeta_in.head(qpmodel.n_in) = qpwork.C_scaled * qpresults.x; + qpwork.zeta_in.tail(qpmodel.dim).array() = + qpwork.i_scaled.array() * qpresults.x.array(); + } else { + qpwork.zeta_in = qpwork.C_scaled * qpresults.x; + } + + proxsuite::common::compute_scaled_primal_residual_ineq( + qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + ruiz, + common::QPSolver::OSQP); + + qpwork.active_set_up.array() = + (qpwork.primal_residual_in_scaled_up.array() > + 0); // {zeta_in - u + z > 0} + qpwork.active_set_low.array() = + (qpresults.si.array() < 0); // {zeta_in - l + z < 0} + } active_constraints_ineq = qpwork.active_set_up || qpwork.active_set_low; num_active_constraints_ineq = active_constraints_ineq.count(); num_active_constraints_ineq_low = qpwork.active_set_low.count(); @@ -617,6 +656,17 @@ find_active_sets(const Settings& qpsettings, num_active_constraints_eq + num_active_constraints_ineq; inner_pb_dim = qpmodel.dim + num_active_constraints; + + // std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; + // std::cout << "In find_active_sets" << std::endl; + // std::cout << "y:" << std::endl << qpresults.y << std::endl; + // std::cout << "active_set_low_eq:" << std::endl << qpwork.active_set_low_eq + // << std::endl; std::cout << "active_set_up_eq:" << std::endl << + // qpwork.active_set_up_eq << std::endl; std::cout << "z:" << std::endl << + // qpresults.z << std::endl; std::cout << "active_set_low:" << std::endl << + // qpwork.active_set_low << std::endl; std::cout << "active_set_up:" << + // std::endl << qpwork.active_set_up << std::endl; std::cout << + // "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; } /*! * Build the reduced matrices of constraints in polishing. @@ -1208,6 +1258,8 @@ polish(const Settings& qpsettings, qpmodel, qpresults, qpwork, + box_constraints, + ruiz, active_constraints_eq, num_active_constraints_eq, num_active_constraints_eq_low, @@ -1447,28 +1499,6 @@ qp_solve( // scaled_eps_rel = qpsettings.eps_rel; } - // qpwork.active_set_low_eq.array() = (qpresults.y.array() < 0); - // qpwork.active_set_low_eq.array() = (qpresults.y.array() > 0); - - // qpwork.x_prev = qpresults.x; - // qpwork.y_prev = qpresults.y; - // qpwork.z_prev = qpresults.z; - - // proxsuite::common::compute_scaled_primal_residual_ineq( - // qpsettings, - // qpmodel, - // qpresults, - // qpwork, - // box_constraints, - // ruiz, - // common::QPSolver::OSQP); - - // qpwork.active_set_up.array() = - // (qpwork.primal_residual_in_scaled_up.array() > - // 0); // {zeta_in - u + z > 0} - // qpwork.active_set_low.array() = - // (qpresults.si.array() < 0); // {zeta_in - l + z < 0} - // Body of solve admm(qpsettings, qpmodel, @@ -1523,6 +1553,7 @@ qp_solve( // qpsettings.resume_admm) { scaled_eps = qpsettings.eps_abs; scaled_eps_rel = qpsettings.eps_rel; + // TODO: Go back to the full KKT matrix of ADMM after a POLISH_FAILED admm(qpsettings, qpmodel, qpresults, diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index 31d5ebe4b..04a0b3798 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -69,6 +69,8 @@ struct Workspace VecBool active_set_low_eq; VecBool active_set_up_eq; + bool admm_solved_at_init; + //// First order residuals for line search Vec Hdx; @@ -144,6 +146,7 @@ struct Workspace , nu_eq(n_eq) , active_set_low_eq(n_eq) , active_set_up_eq(n_eq) + , admm_solved_at_init(false) , Hdx(dim) , Adx(n_eq) , dual_residual_scaled(dim) @@ -419,6 +422,8 @@ struct Workspace active_inequalities(i) = false; } + admm_solved_at_init = false; + constraints_changed = false; dirty = false; refactorize = false; From d4967342a3f9c513ae8dc2e1916ac654fed0e6cc Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 20 May 2025 09:47:55 +0200 Subject: [PATCH 23/27] osqp/: Add function setup_solver_resume_admm to go back to the kkt of the problem when polish failed --- include/proxsuite/osqp/dense/solver.hpp | 45 ++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 26565292e..5a7a8e911 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -1426,6 +1426,31 @@ polish(const Settings& qpsettings, qpresults.info.duality_gap = duality_gap_admm; } } +/*! + * Retrieve and factorize the KKT matrix in ADMM after polishing failed. + * + * @param qpwork solver workspace. + * @param qpmodel QP problem model as defined by the user (without any scaling + * performed). + * @param qpsettings solver settings. + * @param qpresults solver results. + * @param ruiz ruiz preconditioner. + */ +template +void +setup_solver_resume_admm( // + const Model& qpmodel, + Results& qpresults, + Workspace& qpwork, + const isize n_constraints, + const DenseBackend& dense_backend, + const HessianType& hessian_type) +{ + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + proxsuite::common::setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); +} /*! * Executes the OSQP algorithm. * @@ -1547,13 +1572,23 @@ qp_solve( // rhs_duality_gap, duality_gap); - if ((qpresults.info.polish_status == - PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - qpresults.info.polish_status == PolishStatus::POLISH_FAILED) && - qpsettings.resume_admm) { + bool resume = + qpsettings.resume_admm && + (qpresults.info.polish_status == + PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qpresults.info.polish_status == PolishStatus::POLISH_FAILED); + + if (resume) { + if (qpresults.info.polish_status == PolishStatus::POLISH_FAILED) { + setup_solver_resume_admm(qpmodel, + qpresults, + qpwork, + n_constraints, + dense_backend, + hessian_type); + } scaled_eps = qpsettings.eps_abs; scaled_eps_rel = qpsettings.eps_rel; - // TODO: Go back to the full KKT matrix of ADMM after a POLISH_FAILED admm(qpsettings, qpmodel, qpresults, From b02ddf070213649d2f6e3134b9e1b52d41ea55c3 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 20 May 2025 09:50:55 +0200 Subject: [PATCH 24/27] osqp/: Refrase code for setup solver resume admm --- include/proxsuite/osqp/dense/solver.hpp | 35 +++---------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 5a7a8e911..5d8cd697d 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -1426,31 +1426,6 @@ polish(const Settings& qpsettings, qpresults.info.duality_gap = duality_gap_admm; } } -/*! - * Retrieve and factorize the KKT matrix in ADMM after polishing failed. - * - * @param qpwork solver workspace. - * @param qpmodel QP problem model as defined by the user (without any scaling - * performed). - * @param qpsettings solver settings. - * @param qpresults solver results. - * @param ruiz ruiz preconditioner. - */ -template -void -setup_solver_resume_admm( // - const Model& qpmodel, - Results& qpresults, - Workspace& qpwork, - const isize n_constraints, - const DenseBackend& dense_backend, - const HessianType& hessian_type) -{ - proxsuite::proxqp::dense::setup_factorization( - qpwork, qpmodel, qpresults, dense_backend, hessian_type); - proxsuite::common::setup_factorization_complete_kkt( - qpwork, qpmodel, qpresults, dense_backend, n_constraints); -} /*! * Executes the OSQP algorithm. * @@ -1580,12 +1555,10 @@ qp_solve( // if (resume) { if (qpresults.info.polish_status == PolishStatus::POLISH_FAILED) { - setup_solver_resume_admm(qpmodel, - qpresults, - qpwork, - n_constraints, - dense_backend, - hessian_type); + proxsuite::proxqp::dense::setup_factorization( + qpwork, qpmodel, qpresults, dense_backend, hessian_type); + proxsuite::common::setup_factorization_complete_kkt( + qpwork, qpmodel, qpresults, dense_backend, n_constraints); } scaled_eps = qpsettings.eps_abs; scaled_eps_rel = qpsettings.eps_rel; From 65c746e16e97d9fd8abf357dc110b99bd4f1e985 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 20 May 2025 14:45:12 +0200 Subject: [PATCH 25/27] osqp/: formatting --- include/proxsuite/osqp/dense/solver.hpp | 152 +- include/proxsuite/proxqp/dense/workspace.hpp | 5 - include/proxsuite/proxqp/results.hpp | 45 +- include/proxsuite/proxqp/settings.hpp | 17 +- include/proxsuite/solvers/common/utils.hpp | 65 +- test/src/osqp_dense_maros_meszaros.cpp | 170 - test/src/osqp_dense_qp_with_eq_and_in.cpp | 169 +- test/src/osqp_dense_qp_wrapper.cpp | 7674 ------------------ 8 files changed, 292 insertions(+), 8005 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index 5d8cd697d..c76a31391 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -486,7 +486,7 @@ admm( // iter); if (is_solved_qp) { if (iter == 0) { - qpwork.admm_solved_at_init = true; + qpresults.info.admm_solved_at_init = true; } break; } @@ -621,7 +621,7 @@ find_active_sets(const Settings& qpsettings, num_active_constraints_eq_up = qpwork.active_set_up_eq.count(); // Inequality - if (qpwork.admm_solved_at_init) { + if (qpresults.info.admm_solved_at_init) { // Compute active sets that could not be computed in ADMM qpwork.z_prev = qpresults.z; if (box_constraints) { @@ -1226,6 +1226,9 @@ polish(const Settings& qpsettings, T& duality_gap) { + // Update number of polish calls + qpresults.info.polish_calls += 1; + // Timing polishing qpwork.timer_polish.stop(); qpwork.timer_polish.start(); @@ -1554,37 +1557,132 @@ qp_solve( // qpresults.info.polish_status == PolishStatus::POLISH_FAILED); if (resume) { + qpresults.info.resumed_admm = true; + if (qpresults.info.polish_status == PolishStatus::POLISH_FAILED) { + // Retrieve the complete KKT matrix of the problem as the failed + // polish changed the stack and qpwork.ldl proxsuite::proxqp::dense::setup_factorization( qpwork, qpmodel, qpresults, dense_backend, hessian_type); proxsuite::common::setup_factorization_complete_kkt( qpwork, qpmodel, qpresults, dense_backend, n_constraints); } - scaled_eps = qpsettings.eps_abs; - scaled_eps_rel = qpsettings.eps_rel; - admm(qpsettings, - qpmodel, - qpresults, - qpwork, - box_constraints, - n_constraints, - dense_backend, - hessian_type, - ruiz, - primal_feasibility_eq_rhs_0, - primal_feasibility_in_rhs_0, - primal_feasibility_eq_lhs, - primal_feasibility_in_lhs, - primal_feasibility_lhs, - dual_feasibility_lhs, - dual_feasibility_rhs_0, - dual_feasibility_rhs_1, - dual_feasibility_rhs_3, - rhs_duality_gap, - duality_gap, - scaled_eps, - scaled_eps_rel, - qpresults.info.iter_ext); + + if (qpsettings.high_accuracy == false && qpsettings.try_high_accuracy) { + // Resume with ADMM at "high" precision (1e-5), then polish + // prefered when ADMM only at settings precision (typically 1e-9) + // may not converge + qpresults.info.tried_high_accuracy = true; + + scaled_eps = 1e-5; + scaled_eps_rel = (qpsettings.eps_rel == 0) ? 0 : 1e-5; + admm(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel, + qpresults.info.iter_ext); + + if (qpresults.info.status == QPSolverOutput::PROXQP_SOLVED) { + polish(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_lhs, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap); + + bool resume_second = + (qpresults.info.polish_status == + PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qpresults.info.polish_status == PolishStatus::POLISH_FAILED); + + if (resume_second) { + // Resume with ADMM at OSQP settings precision + scaled_eps = qpsettings.eps_abs; + scaled_eps_rel = qpsettings.eps_rel; + admm(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel, + qpresults.info.iter_ext); + } + } + } else { // Resume with ADMM at OSQP settings precision + scaled_eps = qpsettings.eps_abs; + scaled_eps_rel = qpsettings.eps_rel; + admm(qpsettings, + qpmodel, + qpresults, + qpwork, + box_constraints, + n_constraints, + dense_backend, + hessian_type, + ruiz, + primal_feasibility_eq_rhs_0, + primal_feasibility_in_rhs_0, + primal_feasibility_eq_lhs, + primal_feasibility_in_lhs, + primal_feasibility_lhs, + dual_feasibility_lhs, + dual_feasibility_rhs_0, + dual_feasibility_rhs_1, + dual_feasibility_rhs_3, + rhs_duality_gap, + duality_gap, + scaled_eps, + scaled_eps_rel, + qpresults.info.iter_ext); + } } } } diff --git a/include/proxsuite/proxqp/dense/workspace.hpp b/include/proxsuite/proxqp/dense/workspace.hpp index 04a0b3798..31d5ebe4b 100644 --- a/include/proxsuite/proxqp/dense/workspace.hpp +++ b/include/proxsuite/proxqp/dense/workspace.hpp @@ -69,8 +69,6 @@ struct Workspace VecBool active_set_low_eq; VecBool active_set_up_eq; - bool admm_solved_at_init; - //// First order residuals for line search Vec Hdx; @@ -146,7 +144,6 @@ struct Workspace , nu_eq(n_eq) , active_set_low_eq(n_eq) , active_set_up_eq(n_eq) - , admm_solved_at_init(false) , Hdx(dim) , Adx(n_eq) , dual_residual_scaled(dim) @@ -422,8 +419,6 @@ struct Workspace active_inequalities(i) = false; } - admm_solved_at_init = false; - constraints_changed = false; dirty = false; refactorize = false; diff --git a/include/proxsuite/proxqp/results.hpp b/include/proxsuite/proxqp/results.hpp index 68b2236b9..0b711dc17 100644 --- a/include/proxsuite/proxqp/results.hpp +++ b/include/proxsuite/proxqp/results.hpp @@ -45,6 +45,12 @@ struct Info ///// polishing osqp PolishStatus polish_status; + bool admm_solved_at_init; + bool resumed_admm; + bool tried_high_accuracy; + + isize polish_calls; + //// timings T setup_time; T solve_time; @@ -143,6 +149,10 @@ struct Results info.iterative_residual = 0.; info.status = QPSolverOutput::PROXQP_NOT_RUN; info.polish_status = PolishStatus::POLISH_NOT_RUN; + info.admm_solved_at_init = false; + info.resumed_admm = false; + info.tried_high_accuracy = false; + info.polish_calls = 0; info.sparse_backend = SparseBackend::Automatic; info.minimal_H_eigenvalue_estimate = 0.; } @@ -176,6 +186,10 @@ struct Results info.status = QPSolverOutput::PROXQP_MAX_ITER_REACHED; // TODO: PROXQP_NOT_RUN instead ? info.polish_status = PolishStatus::POLISH_NOT_RUN; + info.admm_solved_at_init = false; + info.resumed_admm = false; + info.tried_high_accuracy = false; + info.polish_calls = 0; info.sparse_backend = SparseBackend::Automatic; } void cold_start(optional> settings = nullopt) @@ -214,19 +228,24 @@ bool operator==(const Info& info1, const Info& info2) { bool value = - info1.mu_eq == info2.mu_eq && info1.mu_eq_inv == info2.mu_eq_inv && - info1.mu_in == info2.mu_in && info1.mu_in_inv == info2.mu_in_inv && - info1.rho == info2.rho && info1.nu == info2.nu && - info1.iter == info2.iter && info1.iter_ext == info2.iter_ext && - info1.mu_updates == info2.mu_updates && - info1.rho_updates == info2.rho_updates && info1.status == info2.status && - info1.polish_status == info2.polish_status && - info1.setup_time == info2.setup_time && - info1.solve_time == info2.solve_time && info1.run_time == info2.run_time && - info1.objValue == info2.objValue && info1.pri_res == info2.pri_res && - info1.dua_res == info2.dua_res && info1.duality_gap == info2.duality_gap && - info1.duality_gap == info2.duality_gap && - info1.minimal_H_eigenvalue_estimate == info2.minimal_H_eigenvalue_estimate; + info1.mu_eq == info2.mu_eq&& info1.mu_eq_inv == + info2.mu_eq_inv&& info1.mu_in == info2.mu_in&& info1.mu_in_inv == + info2.mu_in_inv&& info1.rho == info2.rho&& info1.nu == + info2.nu&& info1.iter == info2.iter&& info1.iter_ext == + info2.iter_ext&& info1.mu_updates == info2.mu_updates&& info1.rho_updates == + info2.rho_updates&& info1.status == info2.status&& info1.polish_status == + info2.polish_status&& info1.admm_solved_at_init == + info2.admm_solved_at_init&& info1.resumed_admm == + info2.resumed_admm&& info1.tried_high_accuracy = + info2.tried_high_accuracy && info1.setup_time == info2.setup_time && + info1.solve_time && info1.polish_calls == info2.polish_calls && + info2.solve_time && info1.run_time == info2.run_time && + info1.objValue == info2.objValue && info1.pri_res == info2.pri_res && + info1.dua_res == info2.dua_res && + info1.duality_gap == info2.duality_gap && + info1.duality_gap == info2.duality_gap && + info1.minimal_H_eigenvalue_estimate == + info2.minimal_H_eigenvalue_estimate; return value; } diff --git a/include/proxsuite/proxqp/settings.hpp b/include/proxsuite/proxqp/settings.hpp index 59e23d8c9..73dd4a438 100644 --- a/include/proxsuite/proxqp/settings.hpp +++ b/include/proxsuite/proxqp/settings.hpp @@ -164,6 +164,7 @@ struct Settings isize polish_refine_iter; T delta; bool resume_admm; + bool try_high_accuracy; isize preconditioner_max_iter; T preconditioner_accuracy; @@ -263,6 +264,8 @@ struct Settings * @param delta regularization parameter in solution polishing in OSQP * @param resume_admm if set to true, resumes to ADMM iterations after polish * failed to found active sets or provide a better solution. + * @param try_high_accuracy if set to true, resume ADMM after polishing failed + * at precision 1e-3, then try at ADMM precision 1e-5. * @param preconditioner_max_iter maximal number of authorized iterations for * the preconditioner. * @param preconditioner_accuracy accuracy level of the preconditioner. @@ -309,11 +312,10 @@ struct Settings T cold_reset_mu_in = 1. / 1.1, T cold_reset_mu_eq_inv = 1.1, T cold_reset_mu_in_inv = 1.1, - T cold_reset_mu_eq_osqp = - 1.E-2, // TODO: tune (given scenari, algo, experiments) - T cold_reset_mu_in_osqp = 1.E1, // idem - T cold_reset_mu_eq_inv_osqp = 1.E2, // idem - T cold_reset_mu_in_inv_osqp = 1.E-1, // idem + T cold_reset_mu_eq_osqp = 1.E-2, // default + T cold_reset_mu_in_osqp = 1.E1, // default + T cold_reset_mu_eq_inv_osqp = 1.E2, // default + T cold_reset_mu_in_inv_osqp = 1.E-1, // default T eps_abs = 1.e-5, T eps_rel = 0, bool high_accuracy = false, @@ -322,7 +324,7 @@ struct Settings isize safe_guard = 1.E4, isize nb_iterative_refinement = 10, T eps_refact = 1.e-6, // before eps_refact_=1.e-6 - bool verbose = true, + bool verbose = false, InitialGuessStatus initial_guess = InitialGuessStatus:: EQUALITY_CONSTRAINED_INITIAL_GUESS, // default to // EQUALITY_CONSTRAINED_INITIAL_GUESS, @@ -345,6 +347,7 @@ struct Settings isize polish_refine_iter = 3, T delta = 1.e-6, bool resume_admm = true, + bool try_high_accuracy = true, isize preconditioner_max_iter = 10, T preconditioner_accuracy = 1.e-3, T eps_primal_inf = 1.E-4, @@ -410,6 +413,7 @@ struct Settings , polish_refine_iter(polish_refine_iter) , delta(delta) , resume_admm(resume_admm) + , try_high_accuracy(try_high_accuracy) , preconditioner_max_iter(preconditioner_max_iter) , preconditioner_accuracy(preconditioner_accuracy) , eps_primal_inf(eps_primal_inf) @@ -505,6 +509,7 @@ operator==(const Settings& settings1, const Settings& settings2) settings1.polish_refine_iter == settings2.polish_refine_iter && settings1.delta == settings2.delta && settings1.resume_admm == settings2.resume_admm && + settings1.try_high_accuracy == settings2.try_high_accuracy && settings1.preconditioner_max_iter == settings2.preconditioner_max_iter && settings1.preconditioner_accuracy == settings2.preconditioner_accuracy && settings1.eps_primal_inf == settings2.eps_primal_inf && diff --git a/include/proxsuite/solvers/common/utils.hpp b/include/proxsuite/solvers/common/utils.hpp index 44c4961f1..f0e66d826 100644 --- a/include/proxsuite/solvers/common/utils.hpp +++ b/include/proxsuite/solvers/common/utils.hpp @@ -185,8 +185,9 @@ print_solver_statistics(const pp::Settings& qpsettings, break; } case common::QPSolver::OSQP: { - std::cout << "outer iter: " << qpresults.info.iter_ext << std::endl; - std::cout << "total iter: " << qpresults.info.iter_ext << std::endl; + std::cout << "admm iter: " << qpresults.info.iter_ext << std::endl; + std::cout << "polish calls: " << qpresults.info.polish_calls + << std::endl; std::cout << "mu updates: " << qpresults.info.mu_updates << std::endl; std::cout << "objective: " << qpresults.info.objValue << std::endl; break; @@ -227,30 +228,46 @@ print_solver_statistics(const pp::Settings& qpsettings, } if (qp_solver == QPSolver::OSQP) { - switch (qpresults.info.polish_status) { - case pp::PolishStatus::POLISH_SUCCEED: { - std::cout << "polishing: " - << "Succeed" << std::endl; - break; - } - case pp::PolishStatus::POLISH_FAILED: { - std::cout << "polishing: " - << "Failed" << std::endl; - std::cout << "Resumed ADMM algorithm option: " - << (qpsettings.resume_admm ? "ON" : "OFF") << std::endl; - break; + if (qpresults.info.polish_status == pp::PolishStatus::POLISH_NOT_RUN) { + std::cout << "polishing: " + << "Not run" << std::endl; + } else { + switch (qpresults.info.polish_status) { + case pp::PolishStatus::POLISH_SUCCEED: { + std::cout << "polishing: " + << "Succeed" << std::endl; + break; + } + case pp::PolishStatus::POLISH_FAILED: { + std::cout << "polishing: " + << "Failed" << std::endl; + break; + } + case pp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { + std::cout << "polishing: " + << "No active set found" << std::endl; + break; + } + case pp::PolishStatus::POLISH_NOT_RUN: { + break; + } } - case pp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { - std::cout << "polishing: " - << "Not run because no active set found" << std::endl; - std::cout << "Resumed ADMM algorithm option: " - << (qpsettings.resume_admm ? "ON" : "OFF") << std::endl; - break; + std::cout << "polishing options:" << std::endl; + std::cout << " resume_admm: " + << (qpsettings.resume_admm ? "ON" : "OFF") << std::endl; + std::cout << " high_accuracy: " + << (qpsettings.high_accuracy ? "ON" : "OFF") << std::endl; + if (!qpsettings.high_accuracy) { + std::cout << " try_high_accuracy: " + << (qpsettings.try_high_accuracy ? "ON" : "OFF") << std::endl; } - case pp::PolishStatus::POLISH_NOT_RUN: { - std::cout << "polishing: " - << "Not run" << std::endl; - break; + std::cout << "polishing behaviour:" << std::endl; + std::cout << " resumed admm: " + << (qpresults.info.resumed_admm ? "Yes" : "No") << std::endl; + if (qpresults.info.resumed_admm) { + std::cout << " tried high accuracy: " + << (qpresults.info.tried_high_accuracy ? "Yes" : "No") + << std::endl; } } } diff --git a/test/src/osqp_dense_maros_meszaros.cpp b/test/src/osqp_dense_maros_meszaros.cpp index 31f2c8165..e69de29bb 100644 --- a/test/src/osqp_dense_maros_meszaros.cpp +++ b/test/src/osqp_dense_maros_meszaros.cpp @@ -1,170 +0,0 @@ -// -// Copyright (c) 2022 INRIA -// -#include -#include -#include -#include - -using namespace proxsuite; -namespace pod = proxsuite::osqp::dense; - -#define MAROS_MESZAROS_DIR PROBLEM_PATH "/data/maros_meszaros_data/" - -char const* files[] = { - MAROS_MESZAROS_DIR "AUG2D.mat", MAROS_MESZAROS_DIR "AUG2DC.mat", - MAROS_MESZAROS_DIR "AUG2DCQP.mat", MAROS_MESZAROS_DIR "AUG2DQP.mat", - MAROS_MESZAROS_DIR "AUG3D.mat", MAROS_MESZAROS_DIR "AUG3DC.mat", - MAROS_MESZAROS_DIR "AUG3DCQP.mat", MAROS_MESZAROS_DIR "AUG3DQP.mat", - MAROS_MESZAROS_DIR "BOYD1.mat", MAROS_MESZAROS_DIR "BOYD2.mat", - MAROS_MESZAROS_DIR "CONT-050.mat", MAROS_MESZAROS_DIR "CONT-100.mat", - MAROS_MESZAROS_DIR "CONT-101.mat", MAROS_MESZAROS_DIR "CONT-200.mat", - MAROS_MESZAROS_DIR "CONT-201.mat", MAROS_MESZAROS_DIR "CONT-300.mat", - MAROS_MESZAROS_DIR "CVXQP1_L.mat", MAROS_MESZAROS_DIR "CVXQP1_M.mat", - MAROS_MESZAROS_DIR "CVXQP1_S.mat", MAROS_MESZAROS_DIR "CVXQP2_L.mat", - MAROS_MESZAROS_DIR "CVXQP2_M.mat", MAROS_MESZAROS_DIR "CVXQP2_S.mat", - MAROS_MESZAROS_DIR "CVXQP3_L.mat", MAROS_MESZAROS_DIR "CVXQP3_M.mat", - MAROS_MESZAROS_DIR "CVXQP3_S.mat", MAROS_MESZAROS_DIR "DPKLO1.mat", - MAROS_MESZAROS_DIR "DTOC3.mat", MAROS_MESZAROS_DIR "DUAL1.mat", - MAROS_MESZAROS_DIR "DUAL2.mat", MAROS_MESZAROS_DIR "DUAL3.mat", - MAROS_MESZAROS_DIR "DUAL4.mat", MAROS_MESZAROS_DIR "DUALC1.mat", - MAROS_MESZAROS_DIR "DUALC2.mat", MAROS_MESZAROS_DIR "DUALC5.mat", - MAROS_MESZAROS_DIR "DUALC8.mat", MAROS_MESZAROS_DIR "EXDATA.mat", - MAROS_MESZAROS_DIR "GENHS28.mat", MAROS_MESZAROS_DIR "GOULDQP2.mat", - MAROS_MESZAROS_DIR "GOULDQP3.mat", MAROS_MESZAROS_DIR "HS118.mat", - MAROS_MESZAROS_DIR "HS21.mat", MAROS_MESZAROS_DIR "HS268.mat", - MAROS_MESZAROS_DIR "HS35.mat", MAROS_MESZAROS_DIR "HS35MOD.mat", - MAROS_MESZAROS_DIR "HS51.mat", MAROS_MESZAROS_DIR "HS52.mat", - MAROS_MESZAROS_DIR "HS53.mat", MAROS_MESZAROS_DIR "HS76.mat", - MAROS_MESZAROS_DIR "HUES-MOD.mat", MAROS_MESZAROS_DIR "HUESTIS.mat", - MAROS_MESZAROS_DIR "KSIP.mat", MAROS_MESZAROS_DIR "LASER.mat", - MAROS_MESZAROS_DIR "LISWET1.mat", MAROS_MESZAROS_DIR "LISWET10.mat", - MAROS_MESZAROS_DIR "LISWET11.mat", MAROS_MESZAROS_DIR "LISWET12.mat", - MAROS_MESZAROS_DIR "LISWET2.mat", MAROS_MESZAROS_DIR "LISWET3.mat", - MAROS_MESZAROS_DIR "LISWET4.mat", MAROS_MESZAROS_DIR "LISWET5.mat", - MAROS_MESZAROS_DIR "LISWET6.mat", MAROS_MESZAROS_DIR "LISWET7.mat", - MAROS_MESZAROS_DIR "LISWET8.mat", MAROS_MESZAROS_DIR "LISWET9.mat", - MAROS_MESZAROS_DIR "LOTSCHD.mat", MAROS_MESZAROS_DIR "MOSARQP1.mat", - MAROS_MESZAROS_DIR "MOSARQP2.mat", MAROS_MESZAROS_DIR "POWELL20.mat", - MAROS_MESZAROS_DIR "PRIMAL1.mat", MAROS_MESZAROS_DIR "PRIMAL2.mat", - MAROS_MESZAROS_DIR "PRIMAL3.mat", MAROS_MESZAROS_DIR "PRIMAL4.mat", - MAROS_MESZAROS_DIR "PRIMALC1.mat", MAROS_MESZAROS_DIR "PRIMALC2.mat", - MAROS_MESZAROS_DIR "PRIMALC5.mat", MAROS_MESZAROS_DIR "PRIMALC8.mat", - MAROS_MESZAROS_DIR "Q25FV47.mat", MAROS_MESZAROS_DIR "QADLITTL.mat", - MAROS_MESZAROS_DIR "QAFIRO.mat", MAROS_MESZAROS_DIR "QBANDM.mat", - MAROS_MESZAROS_DIR "QBEACONF.mat", MAROS_MESZAROS_DIR "QBORE3D.mat", - MAROS_MESZAROS_DIR "QBRANDY.mat", MAROS_MESZAROS_DIR "QCAPRI.mat", - MAROS_MESZAROS_DIR "QE226.mat", MAROS_MESZAROS_DIR "QETAMACR.mat", - MAROS_MESZAROS_DIR "QFFFFF80.mat", MAROS_MESZAROS_DIR "QFORPLAN.mat", - MAROS_MESZAROS_DIR "QGFRDXPN.mat", MAROS_MESZAROS_DIR "QGROW15.mat", - MAROS_MESZAROS_DIR "QGROW22.mat", MAROS_MESZAROS_DIR "QGROW7.mat", - MAROS_MESZAROS_DIR "QISRAEL.mat", MAROS_MESZAROS_DIR "QPCBLEND.mat", - MAROS_MESZAROS_DIR "QPCBOEI1.mat", MAROS_MESZAROS_DIR "QPCBOEI2.mat", - MAROS_MESZAROS_DIR "QPCSTAIR.mat", MAROS_MESZAROS_DIR "QPILOTNO.mat", - MAROS_MESZAROS_DIR "QPTEST.mat", MAROS_MESZAROS_DIR "QRECIPE.mat", - MAROS_MESZAROS_DIR "QSC205.mat", MAROS_MESZAROS_DIR "QSCAGR25.mat", - MAROS_MESZAROS_DIR "QSCAGR7.mat", MAROS_MESZAROS_DIR "QSCFXM1.mat", - MAROS_MESZAROS_DIR "QSCFXM2.mat", MAROS_MESZAROS_DIR "QSCFXM3.mat", - MAROS_MESZAROS_DIR "QSCORPIO.mat", MAROS_MESZAROS_DIR "QSCRS8.mat", - MAROS_MESZAROS_DIR "QSCSD1.mat", MAROS_MESZAROS_DIR "QSCSD6.mat", - MAROS_MESZAROS_DIR "QSCSD8.mat", MAROS_MESZAROS_DIR "QSCTAP1.mat", - MAROS_MESZAROS_DIR "QSCTAP2.mat", MAROS_MESZAROS_DIR "QSCTAP3.mat", - MAROS_MESZAROS_DIR "QSEBA.mat", MAROS_MESZAROS_DIR "QSHARE1B.mat", - MAROS_MESZAROS_DIR "QSHARE2B.mat", MAROS_MESZAROS_DIR "QSHELL.mat", - MAROS_MESZAROS_DIR "QSHIP04L.mat", MAROS_MESZAROS_DIR "QSHIP04S.mat", - MAROS_MESZAROS_DIR "QSHIP08L.mat", MAROS_MESZAROS_DIR "QSHIP08S.mat", - MAROS_MESZAROS_DIR "QSHIP12L.mat", MAROS_MESZAROS_DIR "QSHIP12S.mat", - MAROS_MESZAROS_DIR "QSIERRA.mat", MAROS_MESZAROS_DIR "QSTAIR.mat", - MAROS_MESZAROS_DIR "QSTANDAT.mat", MAROS_MESZAROS_DIR "S268.mat", - MAROS_MESZAROS_DIR "STADAT1.mat", MAROS_MESZAROS_DIR "STADAT2.mat", - MAROS_MESZAROS_DIR "STADAT3.mat", MAROS_MESZAROS_DIR "STCQP1.mat", - MAROS_MESZAROS_DIR "STCQP2.mat", MAROS_MESZAROS_DIR "TAME.mat", - MAROS_MESZAROS_DIR "UBH1.mat", MAROS_MESZAROS_DIR "VALUES.mat", - MAROS_MESZAROS_DIR "YAO.mat", MAROS_MESZAROS_DIR "ZECEVIC2.mat", -}; - -TEST_CASE("dense maros meszaros using the api") -{ - using T = double; - using isize = proxqp::utils::isize; - proxsuite::proxqp::Timer timer; - T elapsed_time = 0.0; - - for (auto const* file : files) { - auto qp = load_qp(file); - isize n = qp.P.rows(); - isize n_eq_in = qp.A.rows(); - - const bool skip = n > 1000 || n_eq_in > 1000; - if (skip) { - std::cout << " path: " << qp.filename << " n: " << n - << " n_eq+n_in: " << n_eq_in << " - skipping" << std::endl; - } else { - std::cout << " path: " << qp.filename << " n: " << n - << " n_eq+n_in: " << n_eq_in << std::endl; - } - - if (!skip) { - - auto preprocessed = preprocess_qp(qp); - auto& H = preprocessed.H; - auto& A = preprocessed.A; - auto& C = preprocessed.C; - auto& g = preprocessed.g; - auto& b = preprocessed.b; - auto& u = preprocessed.u; - auto& l = preprocessed.l; - - isize dim = H.rows(); - isize n_eq = A.rows(); - isize n_in = C.rows(); - timer.stop(); - timer.start(); - pod::QP qp{ - dim, n_eq, n_in, false, proxsuite::proxqp::DenseBackend::PrimalDualLDLT - }; // creating QP object - qp.init(H, g, A, b, C, l, u); - - qp.settings.eps_abs = 2e-8; - qp.settings.eps_rel = 0; - qp.settings.eps_primal_inf = 1e-12; - qp.settings.eps_dual_inf = 1e-12; - auto& eps = qp.settings.eps_abs; - - for (size_t it = 0; it < 2; ++it) { - if (it > 0) - qp.settings.initial_guess = proxsuite::proxqp::InitialGuessStatus:: - WARM_START_WITH_PREVIOUS_RESULT; - - qp.solve(); - const auto& x = qp.results.x; - const auto& y = qp.results.y; - const auto& z = qp.results.z; - - T prim_eq = proxqp::dense::infty_norm(A * x - b); - T prim_in = - proxqp::dense::infty_norm(helpers::positive_part(C * x - u) + - helpers::negative_part(C * x - l)); - std::cout << "primal residual " << std::max(prim_eq, prim_in) - << std::endl; - std::cout << "dual residual " - << proxqp::dense::infty_norm(H * x + g + A.transpose() * y + - C.transpose() * z) - << std::endl; - std::cout << "iter " << qp.results.info.iter << std::endl; - CHECK(proxqp::dense::infty_norm(H * x + g + A.transpose() * y + - C.transpose() * z) < 2 * eps); - CHECK(proxqp::dense::infty_norm(A * x - b) > -eps); - CHECK((C * x - l).minCoeff() > -eps); - CHECK((C * x - u).maxCoeff() < eps); - - if (it > 0) { - CHECK(qp.results.info.iter == 0); - } - } - timer.stop(); - elapsed_time += timer.elapsed().user; - } - } - std::cout << "timings total : \t" << elapsed_time * 1e-3 << "ms" << std::endl; -} diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index c1b556558..5ccb94e83 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -25,7 +25,8 @@ DOCTEST_TEST_CASE( T sparsity_factor = 0.15; T eps_abs = T(1e-9); proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + for (proxqp::isize dim = 10; dim < 30; dim += 7) { proxqp::isize n_eq(dim / 4); proxqp::isize n_in(dim / 4); @@ -87,7 +88,8 @@ DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " T sparsity_factor = 0.15; T eps_abs = T(1e-9); proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + for (proxqp::isize dim = 10; dim < 30; dim += 7) { proxqp::isize n_eq(0); proxqp::isize n_in(dim); @@ -147,7 +149,8 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " T sparsity_factor = 0.15; T eps_abs = T(1e-9); proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + for (proxqp::isize dim = 10; dim < 30; dim += 7) { proxqp::isize n_in(dim / 2); proxqp::isize n_eq(0); proxqp::dense::Model qp_random = @@ -210,9 +213,10 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // T strong_convexity_factor(1e-2); // proxqp::utils::rand::set_seed(1); // // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// // for (proxqp::isize dim = 10; dim < 110; dim += 100) { -// proxqp::isize dim = 10; -// { +// // for (proxqp::isize dim = 10; dim < 110; dim += 10) { +// proxqp::isize dim = 50; +// { +// std::cout << "current dim: " << dim << std::endl; // proxqp::isize m(dim / 4); // proxqp::isize n_in(2 * m); // proxqp::isize n_eq(0); @@ -227,80 +231,8 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // qp.settings.eps_rel = 0; // qp.settings.default_mu_eq = T(1.E-2); // qp.settings.default_mu_in = T(1.E1); -// qp.init(qp_random.H, -// qp_random.g, -// qp_random.A, -// qp_random.b, -// qp_random.C, -// qp_random.l, -// qp_random.u); -// qp.solve(); // Memory error here -// // DOCTEST_CHECK(qp.results.info.status == -// // proxqp::QPSolverOutput::PROXQP_SOLVED); -// // 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()); -// // 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) -// // .lpNorm(); -// // DOCTEST_CHECK(pri_res <= eps_abs); -// // DOCTEST_CHECK(dua_res <= eps_abs); - -// // std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq -// // << " nin: " << n_in << std::endl; -// // std::cout << "primal residual: " << pri_res << std::endl; -// // std::cout << "dual residual: " << dua_res << std::endl; -// // std::cout << "number of iterations (admm): " << -// qp.results.info.iter_ext -// // << std::endl; -// // if (qp.settings.polish && -// // !(qp.results.info.polish_status == -// // proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// // qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NOT_RUN)) -// // { -// // std::cout << "number of iterations (polishing): " << -// // qp.settings.polish_refine_iter -// // << std::endl; -// // } -// } -// } - -// DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " -// "increasing dimension using the API") -// { -// srand(1); -// std::cout << "---testing linear problem with inequality constraints and " -// "increasing dimension using the API---" -// << std::endl; -// T sparsity_factor = 0.15; -// T eps_abs = T(1e-9); -// proxqp::utils::rand::set_seed(1); -// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// proxqp::isize n_in(dim / 2); -// proxqp::isize n_eq(0); -// proxqp::dense::Model qp_random = -// proxqp::utils::dense_not_strongly_convex_qp( -// dim, n_eq, n_in, sparsity_factor); -// qp_random.H.setZero(); -// auto z_sol = proxqp::utils::rand::vector_rand(n_in); -// qp_random.g = -qp_random.C.transpose() * -// z_sol; // make sure the LP is bounded within the feasible -// set -// // std::cout << "g : " << qp.g << " C " << qp.C << " u " << qp.u << " l" -// // << qp.l << std::endl; -// pod::QP qp{ dim, n_eq, n_in }; // creating QP object -// qp.settings.eps_abs = eps_abs; -// qp.settings.eps_rel = 0; -// qp.settings.default_mu_eq = T(1.E-2); -// qp.settings.default_mu_in = T(1.E1); -// if (dim == 610) { -// qp.settings.verbose = true; -// } +// qp.settings.high_accuracy = false; +// qp.settings.verbose = true; // qp.init(qp_random.H, // qp_random.g, // qp_random.A, @@ -309,6 +241,8 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // qp_random.l, // qp_random.u); // qp.solve(); +// DOCTEST_CHECK(qp.results.info.status == +// proxqp::QPSolverOutput::PROXQP_SOLVED); // 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) + @@ -328,12 +262,75 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // std::cout << "number of iterations (admm): " << qp.results.info.iter_ext // << std::endl; // if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NOT_RUN)) { -// std::cout << "number of iterations (polishing): " -// << qp.settings.polish_refine_iter << std::endl; +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) +// { +// std::cout << "number of iterations (polishing): " << +// qp.settings.polish_refine_iter +// << std::endl; // } // } // } + +DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " + "increasing dimension using the API") +{ + srand(1); + std::cout << "---testing linear problem with inequality constraints and " + "increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.15; + T eps_abs = T(1e-9); + proxqp::utils::rand::set_seed(1); + for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + proxqp::isize n_in(dim / 2); + proxqp::isize n_eq(0); + proxqp::dense::Model qp_random = + proxqp::utils::dense_not_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor); + qp_random.H.setZero(); + auto z_sol = proxqp::utils::rand::vector_rand(n_in); + qp_random.g = -qp_random.C.transpose() * + z_sol; // make sure LP is bounded within the feasible set + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq + << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "number of iterations (admm): " << qp.results.info.iter_ext + << std::endl; + if (qp.settings.polish && + !(qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || + qp.results.info.polish_status == + proxqp::PolishStatus::POLISH_NOT_RUN)) { + std::cout << "number of iterations (polishing): " + << qp.settings.polish_refine_iter << std::endl; + } + } +} diff --git a/test/src/osqp_dense_qp_wrapper.cpp b/test/src/osqp_dense_qp_wrapper.cpp index bbf69ce64..e69de29bb 100644 --- a/test/src/osqp_dense_qp_wrapper.cpp +++ b/test/src/osqp_dense_qp_wrapper.cpp @@ -1,7674 +0,0 @@ -// -// Copyright (c) 2022-2024 INRIA -// -#include -#include -#include -#include -#include -#include -#include - -using T = double; -using namespace proxsuite; -using namespace proxsuite::proxqp; -namespace pod = proxsuite::osqp::dense; - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with inequality constraints" - "and empty equality constraints") -{ - std::cout << "---testing sparse random strongly convex qp with inequality " - "constraints " - "and empty equality constraints---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - dense::isize n_eq(0); - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - - // Testing with empty but properly sized matrix A of size (0, 10) - std::cout << "Solving QP with" << std::endl; - std::cout << "dim: " << dim << std::endl; - std::cout << "n_eq: " << n_eq << std::endl; - std::cout << "n_in: " << n_in << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; - std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - qp.init(qp_random.H, - qp_random.g, - nullopt, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp.solve(); - - T pri_res = - (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(); - T dua_res = (qp_random.H * qp.results.x + qp_random.g + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - // Testing with empty matrix A of size (0, 0) - qp_random.A = Eigen::MatrixXd(); - qp_random.b = Eigen::VectorXd(); - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - - std::cout << "Solving QP with" << std::endl; - std::cout << "dim: " << dim << std::endl; - std::cout << "n_eq: " << n_eq << std::endl; - std::cout << "n_in: " << n_in << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; - std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = (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(); - dua_res = (qp_random.H * qp.results.x + qp_random.g + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - // Testing with nullopt - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - - qp3.init(qp_random.H, - qp_random.g, - nullopt, - nullopt, - qp_random.C, - qp_random.l, - qp_random.u); - qp3.solve(); - - pri_res = (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(); - dua_res = (qp_random.H * qp.results.x + qp_random.g + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - 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") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update H---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating H" << std::endl; - qp_random.H.setIdentity(); - qp.update(qp_random.H, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update A") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update A---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - - isize n_eq(dim / 4); - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating A" << std::endl; - qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( - n_eq, dim, sparsity_factor); - qp.update(nullopt, nullopt, qp_random.A, nullopt, nullopt, nullopt, nullopt); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update C") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update C---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - - isize n_eq(dim / 4); - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating C" << std::endl; - qp_random.C = utils::rand::sparse_matrix_rand_not_compressed( - n_in, dim, sparsity_factor); - qp.update(nullopt, nullopt, nullopt, nullopt, qp_random.C, nullopt, nullopt); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update b") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update b---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - - isize n_eq(dim / 4); - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating b" << std::endl; - auto x_sol = utils::rand::vector_rand(dim); - qp_random.b = qp_random.A * x_sol; - qp.update(nullopt, nullopt, nullopt, qp_random.b, nullopt, nullopt, nullopt); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update u") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update u---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating b" << std::endl; - auto x_sol = utils::rand::vector_rand(dim); - auto delta = utils::Vec(n_in); - for (isize i = 0; i < n_in; ++i) { - delta(i) = utils::rand::uniform_rand(); - } - - qp_random.u = qp_random.C * x_sol + delta; - qp.update(nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, qp_random.u); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update g") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update g---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating g" << std::endl; - auto g = utils::rand::vector_rand(dim); - - qp_random.g = g; - qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and inequality " - "constraints: test update H and A and b and u and l") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update H and A and b and u and l---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "H : " << qp_random.H << std::endl; - std::cout << "g : " << qp_random.g << std::endl; - std::cout << "A : " << qp_random.A << std::endl; - std::cout << "b : " << qp_random.b << std::endl; - std::cout << "C : " << qp_random.C << std::endl; - std::cout << "u : " << qp_random.u << std::endl; - std::cout << "l : " << qp_random.l << std::endl; - - std::cout << "testing updating b" << std::endl; - qp_random.H = utils::rand::sparse_positive_definite_rand_not_compressed( - dim, strong_convexity_factor, sparsity_factor); - qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( - n_eq, dim, sparsity_factor); - auto x_sol = utils::rand::vector_rand(dim); - auto delta = utils::Vec(n_in); - for (proxqp::isize i = 0; i < n_in; ++i) { - delta(i) = utils::rand::uniform_rand(); - } - qp_random.b = qp_random.A * x_sol; - qp_random.u = qp_random.C * x_sol + delta; - qp_random.l = qp_random.C * x_sol - delta; - qp.update(qp_random.H, - nullopt, - qp_random.A, - qp_random.b, - nullopt, - qp_random.l, - qp_random.u); - - std::cout << "after upating" << std::endl; - std::cout << "H : " << qp.model.H << std::endl; - std::cout << "g : " << qp.model.g << std::endl; - std::cout << "A : " << qp.model.A << std::endl; - std::cout << "b : " << qp.model.b << std::endl; - std::cout << "C : " << qp.model.C << std::endl; - std::cout << "u : " << qp.model.u << std::endl; - std::cout << "l : " << qp.model.l << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update rho") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update rho---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "rho : " << qp.results.info.rho << std::endl; - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.e-7), - nullopt, - nullopt); // restart the problem with default options - std::cout << "after upating" << std::endl; - std::cout << "rho : " << qp.results.info.rho << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.e-7), - nullopt, - nullopt); - std::cout << "rho : " << qp2.results.info.rho << std::endl; - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update mu_eq and mu_in") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test update mu_eq and mu_in---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "before upating" << std::endl; - std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; - std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - nullopt, - T(1.e-2), - T(1.e-3)); - - std::cout << "after upating" << std::endl; - std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; - std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - nullopt, - T(1.e-2), - T(1.e-3)); - qp2.solve(); - std::cout << "mu_in : " << qp2.results.info.mu_in << std::endl; - std::cout << "mu_eq : " << qp2.results.info.mu_eq << std::endl; - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test warm starting") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test warm starting---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - isize dim = 10; - isize n_eq(dim / 4); - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - auto x_wm = utils::rand::vector_rand(dim); - auto y_wm = utils::rand::vector_rand(n_eq); - auto z_wm = utils::rand::vector_rand(n_in); - std::cout << "proposed warm start" << std::endl; - std::cout << "x_wm : " << x_wm << std::endl; - std::cout << "y_wm : " << y_wm << std::endl; - std::cout << "z_wm : " << z_wm << std::endl; - qp.settings.initial_guess = InitialGuessStatus::WARM_START; - qp.solve(x_wm, y_wm, z_wm); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim after updating: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = InitialGuessStatus::WARM_START; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(x_wm, y_wm, z_wm); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------ conter factual check with another QP object starting at " - "the updated model : " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test dense init") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test dense init---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init( - Eigen::Matrix( - qp_random.H), - qp_random.g, - Eigen::Matrix( - qp_random.A), - qp_random.b, - Eigen::Matrix( - qp_random.C), - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test with no initial guess") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test with no initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp2: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test with equality constrained initial guess") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test with equality constrained initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_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); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp2: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test with warm start with previous result") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test with warm start with previous result---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_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); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = InitialGuessStatus::WARM_START; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true); - - auto x = qp.results.x; - auto y = qp.results.y; - auto z = qp.results.z; - // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x - // << std::endl; - qp2.ruiz.scale_primal_in_place({ from_eigen, x }); - qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); - qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); - // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x - // << std::endl; - qp2.solve(x, y, z); - - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp.update( - nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, false); - 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()); - 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) - .lpNorm(); - std::cout << "------using API solving qp with dim with qp after warm start " - "with previous result: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp2: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test with cold start option") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test with cold start option---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_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); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = InitialGuessStatus::WARM_START; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true); - - auto x = qp.results.x; - auto y = qp.results.y; - auto z = qp.results.z; - // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x - // << std::endl; - qp2.ruiz.scale_primal_in_place({ from_eigen, x }); - qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); - qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); - // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x - // << std::endl; - qp2.solve(x, y, z); - - qp.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - qp.update( - nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, 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()); - 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) - .lpNorm(); - std::cout << "------using API solving qp with dim with qp after warm start " - "with cold start option: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with cold start option: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test equilibration options at initialization") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test equilibration options at initialization---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_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, - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp with " - "preconditioner derived: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "ruiz vector : " << qp.ruiz.delta << " ruiz scalar factor " - << qp.ruiz.c << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - false); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp without preconditioner derivation: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " - << qp2.ruiz.c << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test equilibration options at update") -{ - - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test equilibration options at update---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_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, - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with dim with qp with " - "preconditioner derived: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true); // rederive preconditioner with previous options, i.e., redo - // exact same derivations - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp with preconditioner re derived " - "after an update (should get exact same results): " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - std::cout << "------using API solving qp with preconditioner derivation and " - "another object QP: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; - - qp2.update( - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - false); // use previous preconditioner: should get same result as well - qp2.solve(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------using API solving qp without preconditioner derivation: " - << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " - << qp2.ruiz.c << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -///// TESTS ALL INITIAL GUESS OPTIONS FOR MULTIPLE SOLVES AT ONCE -TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with no initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with no initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with equality " - "constrained initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with equality constrained initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with equality " - "constrained initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with warm start with previous result and first solve with " - "equality constrained initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with no initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with warm start with previous result and first solve with " - "no initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with cold start " - "initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with cold start with previous result and first solve with " - "equality constrained initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with warm start") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with warm start and first solve with no initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = InitialGuessStatus::WARM_START; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp.solve(qp.results.x, qp.results.y, qp.results.z); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp.solve(qp.results.x, qp.results.y, qp.results.z); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp.solve(qp.results.x, qp.results.y, qp.results.z); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: warm start test from init") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with warm start and first solve with no initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp2.settings.initial_guess = InitialGuessStatus::WARM_START; - std::cout << "dirty workspace for qp2 : " << qp2.work.dirty << std::endl; - qp2.solve(qp.results.x, qp.results.y, qp.results.z); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve with new QP object" << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -/// TESTS WITH UPDATE + INITIAL GUESS OPTIONS - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update and multiple solve at once with " - "no initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with no initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - bool update_preconditioner = true; - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update + multiple solve at once with " - "equality constrained initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with equality constrained initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - bool update_preconditioner = true; - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test update + multiple solve at once with equality " - "constrained initial guess and then warm start with previous results") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with warm start with previous result and first solve with " - "equality constrained initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - bool update_preconditioner = true; - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test multiple solve at once with no initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with warm start with previous result and first solve with " - "no initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - bool update_preconditioner = true; - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update + multiple solve at once with " - "cold start initial guess and then cold start option") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - - std::cout << "Test with cold start with previous result and first solve with " - "equality constrained initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - bool update_preconditioner = true; - qp.update(qp_random.H, - qp_random.g, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - update_preconditioner); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test update + multiple solve at once with " - "warm start") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test with warm start and first solve with no initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - qp.settings.initial_guess = InitialGuessStatus::WARM_START; - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - auto x_wm = qp.results.x; // keep previous result - auto y_wm = qp.results.y; - auto z_wm = qp.results.z; - bool update_preconditioner = true; - // test with a false update (the warm start should give the exact solution) - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; - qp.solve(x_wm, y_wm, z_wm); - 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()); - 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) - .lpNorm(); - std::cout << "Second solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - x_wm = qp.results.x; // keep previous result - y_wm = qp.results.y; - z_wm = qp.results.z; - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - // try now with a real update - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - update_preconditioner); - std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; - qp.solve(x_wm, y_wm, z_wm); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Third solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp.solve(qp.results.x, qp.results.y, qp.results.z); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fourth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - std::cout << "dirty workspace : " << qp.work.dirty << std::endl; - qp.solve(qp.results.x, qp.results.y, qp.results.z); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "Fifth solve " << std::endl; - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} - -TEST_CASE( - "ProxQP::dense: Test initializaton with rho for different initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test initializaton with rho for different initial guess" - << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.E-7)); - qp.solve(); - CHECK(qp.results.info.rho == T(1.E-7)); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.E-7)); - qp2.solve(); - CHECK(qp2.results.info.rho == T(1.E-7)); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; - - pod::QP qp3(dim, n_eq, n_in); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.E-7)); - qp3.solve(); - CHECK(qp3.results.info.rho == T(1.E-7)); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp3.results.info.iter - << std::endl; - std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " - << qp3.results.info.solve_time << std::endl; - - pod::QP qp4(dim, n_eq, n_in); - qp4.settings.eps_abs = eps_abs; - qp4.settings.eps_rel = 0; - qp4.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - qp4.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.E-7)); - qp4.solve(); - CHECK(qp4.results.info.rho == T(1.E-7)); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp4.results.info.iter - << std::endl; - std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " - << qp4.results.info.solve_time << std::endl; - - pod::QP qp5(dim, n_eq, n_in); - qp5.settings.eps_abs = eps_abs; - qp5.settings.eps_rel = 0; - qp5.settings.initial_guess = InitialGuessStatus::WARM_START; - qp5.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - true, - T(1.E-7)); - qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); - CHECK(qp5.results.info.rho == T(1.E-7)); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp5.results.info.iter - << std::endl; - std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " - << qp5.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: Test g update for different initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test g update for different initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - auto old_g = qp_random.g; - qp_random.g = utils::rand::vector_rand(dim); - qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - 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()); - 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) - .lpNorm(); - CHECK((qp.model.g - qp_random.g).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp2.init(qp_random.H, - old_g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + old_g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp2.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK((qp2.model.g - qp_random.g).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; - - pod::QP qp3(dim, n_eq, n_in); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp3.init(qp_random.H, - old_g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + old_g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp3.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK((qp3.model.g - qp_random.g).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp3.results.info.iter - << std::endl; - std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " - << qp3.results.info.solve_time << std::endl; - - pod::QP qp4(dim, n_eq, n_in); - qp4.settings.eps_abs = eps_abs; - qp4.settings.eps_rel = 0; - qp4.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - qp4.init(qp_random.H, - old_g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp4.solve(); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + old_g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp4.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - qp4.solve(); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK((qp4.model.g - qp_random.g).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp4.results.info.iter - << std::endl; - std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " - << qp4.results.info.solve_time << std::endl; - - pod::QP qp5(dim, n_eq, n_in); - qp5.settings.eps_abs = eps_abs; - qp5.settings.eps_rel = 0; - qp5.settings.initial_guess = InitialGuessStatus::WARM_START; - qp5.init(qp_random.H, - old_g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + old_g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp5.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); - qp5.solve(); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK((qp5.model.g - qp_random.g).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp5.results.info.iter - << std::endl; - std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " - << qp5.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: Test A update for different initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test A update for different initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - auto new_A = utils::rand::sparse_matrix_rand_not_compressed( - n_eq, dim, sparsity_factor); - qp.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); - qp.solve(); - pri_res = - std::max((new_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()); - dua_res = - (qp_random.H * qp.results.x + qp_random.g + - new_A.transpose() * qp.results.y + qp_random.C.transpose() * qp.results.z) - .lpNorm(); - CHECK((qp.model.A - new_A).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp2.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); - qp2.solve(); - pri_res = std::max( - (new_A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - new_A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK((qp2.model.A - new_A).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; - - pod::QP qp3(dim, n_eq, n_in); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp3.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); - qp3.solve(); - pri_res = std::max( - (new_A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - new_A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK((qp3.model.A - new_A).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp3.results.info.iter - << std::endl; - std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " - << qp3.results.info.solve_time << std::endl; - - pod::QP qp4(dim, n_eq, n_in); - qp4.settings.eps_abs = eps_abs; - qp4.settings.eps_rel = 0; - qp4.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - qp4.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp4.solve(); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp4.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); - qp4.solve(); - pri_res = std::max( - (new_A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - new_A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK((qp4.model.A - new_A).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp4.results.info.iter - << std::endl; - std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " - << qp4.results.info.solve_time << std::endl; - - pod::QP qp5(dim, n_eq, n_in); - qp5.settings.eps_abs = eps_abs; - qp5.settings.eps_rel = 0; - qp5.settings.initial_guess = InitialGuessStatus::WARM_START; - qp5.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp5.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); - qp5.solve(); - pri_res = std::max( - (new_A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - new_A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK((qp5.model.A - new_A).lpNorm() <= eps_abs); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp5.results.info.iter - << std::endl; - std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " - << qp5.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: Test rho update for different initial guess") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - std::cout << "Test rho update for different initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.E-7)); - 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()); - 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) - .lpNorm(); - CHECK(qp.results.info.rho == T(1.E-7)); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp2.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.E-7)); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(qp2.results.info.rho == T(1.e-7)); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; - - pod::QP qp3(dim, n_eq, n_in); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.settings.initial_guess = - InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.E-7)); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - CHECK(qp3.results.info.rho == T(1.e-7)); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp3.results.info.iter - << std::endl; - std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " - << qp3.results.info.solve_time << std::endl; - - pod::QP qp4(dim, n_eq, n_in); - qp4.settings.eps_abs = eps_abs; - qp4.settings.eps_rel = 0; - qp4.settings.initial_guess = - InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - qp4.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp4.solve(); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp4.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.E-7)); - qp4.solve(); - pri_res = std::max( - (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp4.results.x + qp_random.g + - qp_random.A.transpose() * qp4.results.y + - qp_random.C.transpose() * qp4.results.z) - .lpNorm(); - CHECK(qp4.results.info.rho == T(1.e-7)); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp4.results.info.iter - << std::endl; - std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " - << qp4.results.info.solve_time << std::endl; - - pod::QP qp5(dim, n_eq, n_in); - qp5.settings.eps_abs = eps_abs; - qp5.settings.eps_rel = 0; - qp5.settings.initial_guess = InitialGuessStatus::WARM_START; - qp5.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - qp5.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - true, - T(1.E-7)); - qp5.solve(); - pri_res = std::max( - (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp5.results.x + qp_random.g + - qp_random.A.transpose() * qp5.results.y + - qp_random.C.transpose() * qp5.results.z) - .lpNorm(); - CHECK(qp5.results.info.rho == T(1.e-7)); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp5.results.info.iter - << std::endl; - std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " - << qp5.results.info.solve_time << std::endl; -} - -TEST_CASE("ProxQP::dense: Test g update for different warm start with previous " - "result option") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - - std::cout << "Test rho update for different initial guess" << std::endl; - std::cout << "dirty workspace before any solving: " << qp.work.dirty - << std::endl; - - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - // a new linear cost slightly modified - auto g = qp_random.g * 0.95; - - qp.update(nullopt, g, nullopt, nullopt, nullopt, nullopt, nullopt); - 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()); - dua_res = - (qp_random.H * qp.results.x + g + qp_random.A.transpose() * qp.results.y + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; - - pod::QP qp2(dim, n_eq, n_in); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.settings.initial_guess = - InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - qp2.init(qp_random.H, - g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp2.solve(); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = - (qp_random.H * qp2.results.x + g + qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in - << std::endl; - std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res - << std::endl; - std::cout << "total number of iteration: " << qp2.results.info.iter - << std::endl; - std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " - << qp2.results.info.solve_time << std::endl; -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after updates using warm start with previous results") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "updates using warm start with previous results---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - qp.settings.initial_guess = - proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - qp3.settings.initial_guess = - proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after updates using cold start with previous results") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "updates using cold start with previous results---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after updates using equality constrained initial guess") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "updates using equality constrained initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after updates using no initial guess") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "updates using no initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after several solves using warm start with previous results") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "several solves using warm start with previous results---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - qp.settings.initial_guess = - proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - qp2.settings.initial_guess = - proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; - for (isize iter = 0; iter < 10; ++iter) { - // warm start with previous result used, hence if the qp is small and - // simple, the parameters should not changed during first solve, and also - // after as we start at the solution - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.settings.verbose = true; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - - for (isize iter = 0; iter < 10; ++iter) { - // warm start with previous result used, hence if the qp is small and - // simple, the parameters should not changed during first solve, and also - // after as we start at the solution - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - for (isize iter = 0; iter < 10; ++iter) { - // warm start with previous result used, hence if the qp is small and - // simple, the parameters should not changed during first solve, and also - // after as we start at the solution - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after several solves using cold start with previous results") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "several solves using cold start with previous results---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } -} - -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after several solves " - "using equality constrained initial guess") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "several solves using equality constrained initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } -} - -DOCTEST_TEST_CASE( - "ProxQP::dense: sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings " - "after several solves using no initial guess") -{ - std::cout << "---testing sparse random strongly convex qp with equality and " - "inequality constraints: test changing default settings after " - "several solves using no initial guess---" - << std::endl; - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - T rho(1.e-7); - T mu_eq(1.e-4); - bool compute_preconditioner = true; - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); - - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6); - for (isize iter = 0; iter < 10; ++iter) { - qp.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp2{ dim, n_eq, n_in }; // creating QP object - qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp2.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp2.settings.eps_abs = eps_abs; - qp2.settings.eps_rel = 0; - qp2.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - nullopt, - mu_eq); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - qp2.solve(); - DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp2.results.x + qp_random.g + - qp_random.A.transpose() * qp2.results.y + - qp_random.C.transpose() * qp2.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - // conter factual check with another QP object starting at the updated model - pod::QP qp3{ dim, n_eq, n_in }; // creating QP object - qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; - DOCTEST_CHECK(qp3.settings.initial_guess == - proxqp::InitialGuessStatus::NO_INITIAL_GUESS); - qp3.settings.eps_abs = eps_abs; - qp3.settings.eps_rel = 0; - qp3.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - compute_preconditioner, - rho, - mu_eq); - - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } - - qp3.update(nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - compute_preconditioner, - 1.e-6, - 1.e-3); - for (isize iter = 0; iter < 10; ++iter) { - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - qp3.solve(); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); - DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); - pri_res = std::max( - (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), - (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + - helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) - .lpNorm()); - dua_res = (qp_random.H * qp3.results.x + qp_random.g + - qp_random.A.transpose() * qp3.results.y + - qp_random.C.transpose() * qp3.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - } -} - -TEST_CASE("ProxQP::dense: init must be called before update") -{ - - double sparsity_factor = 0.15; - T eps_abs = T(1e-9); - utils::rand::set_seed(1); - dense::isize dim = 10; - - 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); - - pod::QP qp(dim, n_eq, n_in); - - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - - // call update without init, update calls init internally - qp.update(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u, - 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()); - 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) - .lpNorm(); - CHECK(dua_res <= eps_abs); - CHECK(pri_res <= eps_abs); - - qp_random.H *= 2.; - qp_random.g = utils::rand::vector_rand(dim); - qp.update(qp_random.H, - qp_random.g, - nullopt, - nullopt, - nullopt, - nullopt, - nullopt, - 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()); - 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) - .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::pod::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; - //////////////// - - pod::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; - } - - pod::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 - pod::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 - pod::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 - pod::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); -} -TEST_CASE("ProxQP::dense: test primal infeasibility solving") -{ - double sparsity_factor = 0.15; - T eps_abs = T(1e-5); - utils::rand::set_seed(1); - dense::isize dim = 20; - - dense::isize n_eq(dim / 4); - dense::isize n_in(dim / 4); - T strong_convexity_factor(1.e-2); - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - // create infeasible problem - qp_random.b.array() += T(10.); - qp_random.u.array() -= T(100.); - qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; - qp.settings.primal_infeasibility_solving = true; - qp.settings.eps_primal_inf = T(1.E-4); - qp.settings.eps_dual_inf = T(1.E-4); - qp.settings.verbose = true; - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - qp.solve(); - - proxsuite::proxqp::utils::Vec rhs_dim(dim); - proxsuite::proxqp::utils::Vec rhs_n_eq(n_eq); - rhs_n_eq.setOnes(); - proxsuite::proxqp::utils::Vec rhs_n_in(n_in); - rhs_n_in.setOnes(); - rhs_dim.noalias() = - qp_random.A.transpose() * rhs_n_eq + qp_random.C.transpose() * rhs_n_in; - T scaled_eps = (rhs_dim).lpNorm() * eps_abs; - - T pri_res = - (qp_random.A.transpose() * (qp_random.A * qp.results.x - qp_random.b) + - qp_random.C.transpose() * - (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(); - T dua_res = (qp_random.H.selfadjointView() * qp.results.x + - qp_random.g + qp_random.A.transpose() * qp.results.y + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= scaled_eps); - DOCTEST_CHECK(dua_res <= eps_abs); - } -} - -TEST_CASE("ProxQP::dense: estimate of minimal eigenvalues using Eigen") -{ - double sparsity_factor = 1.; - T tol = T(1e-6); - utils::rand::set_seed(1); - dense::isize dim = 2; - dense::isize n_eq(dim); - dense::isize n_in(dim); - T strong_convexity_factor(1.e-2); - for (isize i = 0; i < 1; ++i) { - // trivial test - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - qp_random.H.diagonal().setOnes(); - qp_random.H.diagonal().tail(1).setConstant(-1.); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= - tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += random_diag.array(); - T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += 100 * random_diag.array(); - Eigen::SelfAdjointEigenSolver> es(qp_random.H, - Eigen::EigenvaluesOnly); - T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } -} - -TEST_CASE( - "ProxQP::dense: test estimate of minimal eigenvalue using manual choice") -{ - double sparsity_factor = 1.; - T tol = T(1e-6); - utils::rand::set_seed(1); - dense::isize dim = 2; - dense::isize n_eq(dim); - dense::isize n_in(dim); - T strong_convexity_factor(1.e-2); - for (isize i = 0; i < 1; ++i) { - // trivial test - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - qp_random.H.diagonal().setOnes(); - qp_random.H.diagonal().tail(1).setConstant(-1.); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - -1); - - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= - tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += random_diag.array(); - T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - minimal_eigenvalue); - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += 100 * random_diag.array(); - Eigen::SelfAdjointEigenSolver> es(qp_random.H, - Eigen::EigenvaluesOnly); - T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - minimal_eigenvalue); - - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } -} - -TEST_CASE( - "ProxQP::dense: test estimate of minimal eigenvalue using power iteration") -{ - double sparsity_factor = 1.; - T tol = T(1e-3); - utils::rand::set_seed(1); - dense::isize dim = 2; - dense::isize n_eq(dim); - dense::isize n_in(dim); - T strong_convexity_factor(1.e-2); - for (isize i = 0; i < 1; ++i) { - // trivial test - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - qp_random.H.diagonal().setOnes(); - qp_random.H.diagonal().tail(1).setConstant(-0.5); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, - EigenValueEstimateMethodOption::PowerIteration, - 1.E-6, - 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - - DOCTEST_CHECK( - std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 0.5) <= tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += random_diag.array(); - T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, - EigenValueEstimateMethodOption::PowerIteration, - 1.E-6, - 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } - dim = 50; - n_eq = dim; - n_in = dim; - for (isize i = 0; i < 20; ++i) { - ::proxsuite::proxqp::utils::rand::set_seed(i); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); - qp_random.H.diagonal().array() += - 100 * random_diag.array(); // add some random values to dense matrix - Eigen::SelfAdjointEigenSolver> es(qp_random.H, - Eigen::EigenvaluesOnly); - T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); - - T estimate_minimal_eigen_value = - dense::estimate_minimal_eigen_value_of_symmetric_matrix( - qp_random.H, - EigenValueEstimateMethodOption::PowerIteration, - 1.E-6, - 10000); - - pod::QP qp(dim, n_eq, n_in); - qp.settings.max_iter = 1; - qp.settings.max_iter_in = 1; - 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, - true, - nullopt, - nullopt, - nullopt, - estimate_minimal_eigen_value); - - DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - - minimal_eigenvalue) <= tol); - } -} - -DOCTEST_TEST_CASE("check that model.is_valid function for symmetric matrices " - "works for epsilon precision") -{ - Eigen::Matrix matrix = Eigen::Matrix::Random(); - Eigen::Matrix symmetric_mat = matrix + matrix.transpose(); - - symmetric_mat(0, 1) = - symmetric_mat(1, 0) + std::numeric_limits::epsilon(); - - // compare the two checks for symmetry with and without tolerance - bool is_symmetric_without_tolerance = - symmetric_mat.isApprox(symmetric_mat.transpose(), 0.0); - bool is_symmetric_with_tolerance = symmetric_mat.isApprox( - symmetric_mat.transpose(), - std::numeric_limits::epsilon()); - DOCTEST_CHECK(is_symmetric_without_tolerance == false); - DOCTEST_CHECK(is_symmetric_with_tolerance == true); - - // initialize a model with a symmetric matrix as Hessian, this runs - // model.is_valid() that performs the check above - pod::QP qp(3, 0, 0); - qp.init(symmetric_mat, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); -} - -TEST_CASE("ProxQP::dense: test memory allocation when estimating biggest " - "eigenvalue with power iteration") -{ - double sparsity_factor = 1.; - utils::rand::set_seed(1); - dense::isize dim = 2; - dense::isize n_eq(dim); - dense::isize n_in(dim); - T strong_convexity_factor(1.e-2); - Eigen::Matrix H; - Eigen::VectorXd dw(2), rhs(2), err_v(2); - // trivial test - ::proxsuite::proxqp::utils::rand::set_seed(1234); - proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - - qp_random.H.setZero(); - qp_random.H.diagonal().setOnes(); - qp_random.H.diagonal().tail(1).setConstant(-0.5); - H = qp_random.H; - PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); - dense::power_iteration(H, dw, rhs, err_v, 1.E-6, 10000); - PROXSUITE_EIGEN_MALLOC_ALLOWED(); -} - -TEST_CASE("ProxQP::dense: sparse random strongly convex qp with" - "inequality constraints: test PrimalLDLT backend mu update") -{ - - std::cout << "---testing sparse random strongly convex qp with" - "inequality constraints: test PrimalLDLT backend mu update---" - << std::endl; - double sparsity_factor = 1; - utils::rand::set_seed(1); - isize dim = 3; - isize n_eq(0); - isize n_in(9); - 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); - pod::QP qp{ - dim, - n_eq, - n_in, - false, - proxsuite::proxqp::HessianType::Dense, - proxsuite::proxqp::DenseBackend::PrimalLDLT - }; // creating QP object - T eps_abs = T(1e-7); - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.compute_timings = true; - qp.settings.verbose = true; - qp.init(qp_random.H, - qp_random.g, - nullopt, - nullopt, - qp_random.C, - nullopt, - qp_random.u); - qp.solve(); - - DOCTEST_CHECK(qp.results.info.mu_updates > 0); - - T pri_res = (helpers::negative_part(qp_random.C * qp.results.x - qp_random.l)) - .lpNorm(); - T dua_res = (qp_random.H * qp.results.x + qp_random.g + - qp_random.C.transpose() * qp.results.z) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); - - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "total number of iteration: " << qp.results.info.iter - << std::endl; - std::cout << "setup timing " << qp.results.info.setup_time << " solve time " - << qp.results.info.solve_time << std::endl; -} From 13d2e77b4c95bdc382d08c710e87991496ed3239 Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 20 May 2025 14:48:21 +0200 Subject: [PATCH 26/27] /osqp: Removed memory debug commentaries --- include/proxsuite/osqp/dense/solver.hpp | 19 - test/src/osqp_dense_qp_with_eq_and_in.cpp | 442 +++++++++++----------- 2 files changed, 221 insertions(+), 240 deletions(-) diff --git a/include/proxsuite/osqp/dense/solver.hpp b/include/proxsuite/osqp/dense/solver.hpp index c76a31391..e89cb412f 100644 --- a/include/proxsuite/osqp/dense/solver.hpp +++ b/include/proxsuite/osqp/dense/solver.hpp @@ -531,14 +531,6 @@ admm( // qpwork.active_set_low.array() = (qpresults.si.array() < 0); // {zeta_in - l + z < 0} - // std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; - // std::cout << "In admm:" << std::endl; - // std::cout << "z:" << std::endl << qpresults.z << std::endl; - // std::cout << "active_set_low:" << std::endl << qpwork.active_set_low << - // std::endl; std::cout << "active_set_up:" << std::endl << - // qpwork.active_set_up << std::endl; std::cout << - // "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; - T primal_feasibility_lhs_new(primal_feasibility_lhs); T dual_feasibility_lhs_new(dual_feasibility_lhs); proxsuite::common::update_solver_status(qpsettings, @@ -656,17 +648,6 @@ find_active_sets(const Settings& qpsettings, num_active_constraints_eq + num_active_constraints_ineq; inner_pb_dim = qpmodel.dim + num_active_constraints; - - // std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; - // std::cout << "In find_active_sets" << std::endl; - // std::cout << "y:" << std::endl << qpresults.y << std::endl; - // std::cout << "active_set_low_eq:" << std::endl << qpwork.active_set_low_eq - // << std::endl; std::cout << "active_set_up_eq:" << std::endl << - // qpwork.active_set_up_eq << std::endl; std::cout << "z:" << std::endl << - // qpresults.z << std::endl; std::cout << "active_set_low:" << std::endl << - // qpwork.active_set_low << std::endl; std::cout << "active_set_up:" << - // std::endl << qpwork.active_set_up << std::endl; std::cout << - // "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl; } /*! * Build the reduced matrices of constraints in polishing. diff --git a/test/src/osqp_dense_qp_with_eq_and_in.cpp b/test/src/osqp_dense_qp_with_eq_and_in.cpp index 5ccb94e83..0f35c802e 100644 --- a/test/src/osqp_dense_qp_with_eq_and_in.cpp +++ b/test/src/osqp_dense_qp_with_eq_and_in.cpp @@ -13,226 +13,157 @@ using T = double; using namespace proxsuite; namespace pod = proxsuite::osqp::dense; -DOCTEST_TEST_CASE( - "sparse random strongly convex qp with equality and inequality constraints" - "and increasing dimension using wrapper API") -{ - - std::cout - << "---testing sparse random strongly convex qp with equality and " - "inequality constraints and increasing dimension using wrapper API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - for (proxqp::isize dim = 10; dim < 30; dim += 7) { - - proxqp::isize n_eq(dim / 4); - proxqp::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); - - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); +// DOCTEST_TEST_CASE( +// "sparse random strongly convex qp with equality and inequality constraints" +// "and increasing dimension using wrapper API") +// { - std::cout << "------using API solving qp with dim: " << dim - << " neq: " << n_eq << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "number of iterations (admm): " << qp.results.info.iter_ext - << std::endl; - if (qp.settings.polish && - !(qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NOT_RUN)) { - std::cout << "number of iterations (polishing): " - << qp.settings.polish_refine_iter << std::endl; - } - } -} +// std::cout +// << "---testing sparse random strongly convex qp with equality and " +// "inequality constraints and increasing dimension using wrapper API---" +// << std::endl; +// T sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// proxqp::utils::rand::set_seed(1); +// // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// for (proxqp::isize dim = 10; dim < 30; dim += 7) { -DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " - "constraints and increasing dimension using the API") -{ +// proxqp::isize n_eq(dim / 4); +// proxqp::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); - std::cout - << "---testing sparse random strongly convex qp with box inequality " - "constraints and increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - for (proxqp::isize dim = 10; dim < 30; dim += 7) { +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// qp.solve(); - proxqp::isize n_eq(0); - proxqp::isize n_in(dim); - T strong_convexity_factor(1.e-2); - proxqp::dense::Model qp_random = proxqp::utils::dense_box_constrained_qp( - dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "number of iterations: " << qp.results.info.iter_ext - << std::endl; - if (qp.settings.polish && - !(qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NOT_RUN)) { - std::cout << "number of iterations (polishing): " - << qp.settings.polish_refine_iter << std::endl; - } - } -} +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) { +// std::cout << "number of iterations (polishing): " +// << qp.settings.polish_refine_iter << std::endl; +// } +// } +// } -DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " - "constraints and increasing dimension using the API") -{ +// DOCTEST_TEST_CASE("sparse random strongly convex qp with box inequality " +// "constraints and increasing dimension using the API") +// { - std::cout - << "---testing sparse random not strongly convex qp with inequality " - "constraints and increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; - T eps_abs = T(1e-9); - proxqp::utils::rand::set_seed(1); - // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - for (proxqp::isize dim = 10; dim < 30; dim += 7) { - proxqp::isize n_in(dim / 2); - proxqp::isize n_eq(0); - proxqp::dense::Model qp_random = - proxqp::utils::dense_not_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor); +// std::cout +// << "---testing sparse random strongly convex qp with box inequality " +// "constraints and increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// proxqp::utils::rand::set_seed(1); +// // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// for (proxqp::isize dim = 10; dim < 30; dim += 7) { - pod::QP qp{ dim, n_eq, n_in }; // creating QP object - qp.settings.eps_abs = eps_abs; - qp.settings.eps_rel = 0; - qp.settings.default_mu_eq = T(1.E-2); - qp.settings.default_mu_in = T(1.E1); - qp.init(qp_random.H, - qp_random.g, - qp_random.A, - qp_random.b, - qp_random.C, - qp_random.l, - qp_random.u); - 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()); - 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) - .lpNorm(); - DOCTEST_CHECK(pri_res <= eps_abs); - DOCTEST_CHECK(dua_res <= eps_abs); +// proxqp::isize n_eq(0); +// proxqp::isize n_in(dim); +// T strong_convexity_factor(1.e-2); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_box_constrained_qp( +// dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); - std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq - << " nin: " << n_in << std::endl; - std::cout << "primal residual: " << pri_res << std::endl; - std::cout << "dual residual: " << dua_res << std::endl; - std::cout << "number of iterations (admm): " << qp.results.info.iter_ext - << std::endl; - if (qp.settings.polish && - !(qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || - qp.results.info.polish_status == - proxqp::PolishStatus::POLISH_NOT_RUN)) { - std::cout << "number of iterations (polishing): " - << qp.settings.polish_refine_iter << std::endl; - } - } -} +// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations: " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) { +// std::cout << "number of iterations (polishing): " +// << qp.settings.polish_refine_iter << std::endl; +// } +// } +// } -// DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate -// inequality " +// DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // "constraints and increasing dimension using the API") // { // std::cout -// << "---testing sparse random strongly convex qp with degenerate " -// "inequality constraints and increasing dimension using the API---" +// << "---testing sparse random not strongly convex qp with inequality " +// "constraints and increasing dimension using the API---" // << std::endl; -// T sparsity_factor = 0.45; +// T sparsity_factor = 0.15; // T eps_abs = T(1e-9); -// T strong_convexity_factor(1e-2); // proxqp::utils::rand::set_seed(1); // // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { -// // for (proxqp::isize dim = 10; dim < 110; dim += 10) { -// proxqp::isize dim = 50; -// { -// std::cout << "current dim: " << dim << std::endl; -// proxqp::isize m(dim / 4); -// proxqp::isize n_in(2 * m); +// for (proxqp::isize dim = 10; dim < 30; dim += 7) { +// proxqp::isize n_in(dim / 2); // proxqp::isize n_eq(0); -// proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( -// dim, -// n_eq, -// m, // it n_in = 2 * m, it doubles the inequality constraints -// sparsity_factor, -// strong_convexity_factor); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_not_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor); + // pod::QP qp{ dim, n_eq, n_in }; // creating QP object // qp.settings.eps_abs = eps_abs; // qp.settings.eps_rel = 0; // qp.settings.default_mu_eq = T(1.E-2); // qp.settings.default_mu_in = T(1.E1); -// qp.settings.high_accuracy = false; -// qp.settings.verbose = true; // qp.init(qp_random.H, // qp_random.g, // qp_random.A, @@ -241,8 +172,6 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // qp_random.l, // qp_random.u); // qp.solve(); -// DOCTEST_CHECK(qp.results.info.status == -// proxqp::QPSolverOutput::PROXQP_SOLVED); // 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) + @@ -262,42 +191,49 @@ DOCTEST_TEST_CASE("sparse random not strongly convex qp with inequality " // std::cout << "number of iterations (admm): " << qp.results.info.iter_ext // << std::endl; // if (qp.settings.polish && -// !(qp.results.info.polish_status == -// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || -// qp.results.info.polish_status == proxqp::PolishStatus::POLISH_NOT_RUN)) -// { -// std::cout << "number of iterations (polishing): " << -// qp.settings.polish_refine_iter -// << std::endl; +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) { +// std::cout << "number of iterations (polishing): " +// << qp.settings.polish_refine_iter << std::endl; // } // } // } -DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " - "increasing dimension using the API") +DOCTEST_TEST_CASE("sparse random strongly convex qp with degenerate inequality " + "constraints and increasing dimension using the API") { - srand(1); - std::cout << "---testing linear problem with inequality constraints and " - "increasing dimension using the API---" - << std::endl; - T sparsity_factor = 0.15; + + std::cout + << "---testing sparse random strongly convex qp with degenerate " + "inequality constraints and increasing dimension using the API---" + << std::endl; + T sparsity_factor = 0.45; T eps_abs = T(1e-9); + T strong_convexity_factor(1e-2); proxqp::utils::rand::set_seed(1); - for (proxqp::isize dim = 10; dim < 1000; dim += 100) { - proxqp::isize n_in(dim / 2); + // for (proxqp::isize dim = 10; dim < 1000; dim += 100) { + // for (proxqp::isize dim = 10; dim < 110; dim += 10) { + proxqp::isize dim = 50; + { + std::cout << "current dim: " << dim << std::endl; + proxqp::isize m(dim / 4); + proxqp::isize n_in(2 * m); proxqp::isize n_eq(0); - proxqp::dense::Model qp_random = - proxqp::utils::dense_not_strongly_convex_qp( - dim, n_eq, n_in, sparsity_factor); - qp_random.H.setZero(); - auto z_sol = proxqp::utils::rand::vector_rand(n_in); - qp_random.g = -qp_random.C.transpose() * - z_sol; // make sure LP is bounded within the feasible set + proxqp::dense::Model qp_random = proxqp::utils::dense_degenerate_qp( + dim, + n_eq, + m, // it n_in = 2 * m, it doubles the inequality constraints + sparsity_factor, + strong_convexity_factor); pod::QP qp{ dim, n_eq, n_in }; // creating QP object qp.settings.eps_abs = eps_abs; qp.settings.eps_rel = 0; qp.settings.default_mu_eq = T(1.E-2); qp.settings.default_mu_in = T(1.E1); + qp.settings.high_accuracy = false; + qp.settings.verbose = true; qp.init(qp_random.H, qp_random.g, qp_random.A, @@ -306,6 +242,8 @@ DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " qp_random.l, qp_random.u); qp.solve(); + DOCTEST_CHECK(qp.results.info.status == + proxqp::QPSolverOutput::PROXQP_SOLVED); 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) + @@ -334,3 +272,65 @@ DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " } } } + +// DOCTEST_TEST_CASE("linear problem with equality inequality constraints and " +// "increasing dimension using the API") +// { +// srand(1); +// std::cout << "---testing linear problem with inequality constraints and " +// "increasing dimension using the API---" +// << std::endl; +// T sparsity_factor = 0.15; +// T eps_abs = T(1e-9); +// proxqp::utils::rand::set_seed(1); +// for (proxqp::isize dim = 10; dim < 1000; dim += 100) { +// proxqp::isize n_in(dim / 2); +// proxqp::isize n_eq(0); +// proxqp::dense::Model qp_random = +// proxqp::utils::dense_not_strongly_convex_qp( +// dim, n_eq, n_in, sparsity_factor); +// qp_random.H.setZero(); +// auto z_sol = proxqp::utils::rand::vector_rand(n_in); +// qp_random.g = -qp_random.C.transpose() * +// z_sol; // make sure LP is bounded within the feasible set +// pod::QP qp{ dim, n_eq, n_in }; // creating QP object +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.default_mu_eq = T(1.E-2); +// qp.settings.default_mu_in = T(1.E1); +// qp.init(qp_random.H, +// qp_random.g, +// qp_random.A, +// qp_random.b, +// qp_random.C, +// qp_random.l, +// qp_random.u); +// 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()); +// 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) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------solving qp with dim: " << dim << " neq: " << n_eq +// << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "number of iterations (admm): " << qp.results.info.iter_ext +// << std::endl; +// if (qp.settings.polish && +// !(qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND || +// qp.results.info.polish_status == +// proxqp::PolishStatus::POLISH_NOT_RUN)) { +// std::cout << "number of iterations (polishing): " +// << qp.settings.polish_refine_iter << std::endl; +// } +// } +// } From 404b81fe91942faa7daaeedcc40af8fe4d7cd88c Mon Sep 17 00:00:00 2001 From: Lucas Haubert Date: Tue, 20 May 2025 17:25:52 +0200 Subject: [PATCH 27/27] /osqp: Begin work on test_dense_maros_meszaros --- test/src/osqp_dense_maros_meszaros.cpp | 331 + test/src/osqp_dense_qp_wrapper.cpp | 7677 ++++++++++++++++++++++++ 2 files changed, 8008 insertions(+) diff --git a/test/src/osqp_dense_maros_meszaros.cpp b/test/src/osqp_dense_maros_meszaros.cpp index e69de29bb..87e4a4393 100644 --- a/test/src/osqp_dense_maros_meszaros.cpp +++ b/test/src/osqp_dense_maros_meszaros.cpp @@ -0,0 +1,331 @@ +// +// Copyright (c) 2022 INRIA +// +#include +#include +#include +#include + +using namespace proxsuite; +namespace pp = proxsuite::proxqp; +namespace pod = proxsuite::osqp::dense; + +#define MAROS_MESZAROS_DIR PROBLEM_PATH "/data/maros_meszaros_data/" + +char const* files[] = { + // MAROS_MESZAROS_DIR "AUG2D.mat", // skipping + // MAROS_MESZAROS_DIR "AUG2DC.mat", // skipping + // MAROS_MESZAROS_DIR "AUG2DCQP.mat", // skipping + // MAROS_MESZAROS_DIR "AUG2DQP.mat", // skipping + // MAROS_MESZAROS_DIR "AUG3D.mat", // skipping + // MAROS_MESZAROS_DIR "AUG3DC.mat", // skipping + // MAROS_MESZAROS_DIR "AUG3DCQP.mat", // skipping + // MAROS_MESZAROS_DIR "AUG3DQP.mat", // skipping + // MAROS_MESZAROS_DIR "BOYD1.mat", // skipping + // MAROS_MESZAROS_DIR "BOYD2.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-050.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-100.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-101.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-200.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-201.mat", // skipping + // MAROS_MESZAROS_DIR "CONT-300.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP1_L.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP1_M.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP1_S.mat", // Pass + // MAROS_MESZAROS_DIR "CVXQP2_L.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP2_M.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP2_S.mat", // Pass + // MAROS_MESZAROS_DIR "CVXQP3_L.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP3_M.mat", // skipping + // MAROS_MESZAROS_DIR "CVXQP3_S.mat", // Pass + // MAROS_MESZAROS_DIR "DPKLO1.mat", // Pass + // MAROS_MESZAROS_DIR "DTOC3.mat", // Skipping + // MAROS_MESZAROS_DIR "DUAL1.mat", // Pass + // MAROS_MESZAROS_DIR "DUAL2.mat", // Pass + // MAROS_MESZAROS_DIR "DUAL3.mat", // Pass + // MAROS_MESZAROS_DIR "DUAL4.mat", // Pass + // MAROS_MESZAROS_DIR "DUALC1.mat", // Pass + // MAROS_MESZAROS_DIR "DUALC2.mat", // Pass + // MAROS_MESZAROS_DIR "DUALC5.mat", // Pass + // MAROS_MESZAROS_DIR "DUALC8.mat", // Pass + // MAROS_MESZAROS_DIR "EXDATA.mat", // Skipping + // MAROS_MESZAROS_DIR "GENHS28.mat", // Pass + // MAROS_MESZAROS_DIR "GOULDQP2.mat", // Skipping + // MAROS_MESZAROS_DIR "GOULDQP3.mat", // Skipping + // MAROS_MESZAROS_DIR "HS118.mat", // Pass + // MAROS_MESZAROS_DIR "HS21.mat", // Pass + // MAROS_MESZAROS_DIR "HS268.mat", // Pass + // MAROS_MESZAROS_DIR "HS35.mat", // Pass + // MAROS_MESZAROS_DIR "HS35MOD.mat", // Pass + // MAROS_MESZAROS_DIR "HS51.mat", // Pass + // MAROS_MESZAROS_DIR "HS52.mat", // Pass + // MAROS_MESZAROS_DIR "HS53.mat", // Pass + // MAROS_MESZAROS_DIR "HS76.mat", // Pass + // MAROS_MESZAROS_DIR "HUES-MOD.mat", // Skipping + // MAROS_MESZAROS_DIR "HUESTIS.mat", // Skipping + // MAROS_MESZAROS_DIR "KSIP.mat", // Skipping + // MAROS_MESZAROS_DIR "LASER.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET1.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET10.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET11.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET12.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET2.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET3.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET4.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET5.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET6.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET7.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET8.mat", // Skipping + // MAROS_MESZAROS_DIR "LISWET9.mat", // Skipping + // MAROS_MESZAROS_DIR "LOTSCHD.mat", // Pass + // MAROS_MESZAROS_DIR "MOSARQP1.mat", // Skipping + // MAROS_MESZAROS_DIR "MOSARQP2.mat", // Skipping + // MAROS_MESZAROS_DIR "POWELL20.mat", // Skipping + // MAROS_MESZAROS_DIR "PRIMAL1.mat", // Pass + // MAROS_MESZAROS_DIR "PRIMAL2.mat", // Pass + // MAROS_MESZAROS_DIR "PRIMAL3.mat", // Pass + // MAROS_MESZAROS_DIR "PRIMAL4.mat", // Skipping + // MAROS_MESZAROS_DIR "PRIMALC1.mat", // Fail: + // Values nan (primal residual 0, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 11) n: 230 n_eq+n_in: 239 + // MAROS_MESZAROS_DIR "PRIMALC2.mat", // Fail: + // Values nan (primal residual 0, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 8) n: 231 n_eq+n_in: 238 + // MAROS_MESZAROS_DIR "PRIMALC5.mat", // Fail: + // Values nan (primal residual 0, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 9) n: 287 n_eq+n_in: 295 + // MAROS_MESZAROS_DIR "PRIMALC8.mat", // Fail: + // Values nan (primal residual 0, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 8) n: 520 n_eq+n_in: 528 + // MAROS_MESZAROS_DIR "Q25FV47.mat", // Skipping + // MAROS_MESZAROS_DIR "QADLITTL.mat", // Fail: + // Values nan (primal residual huge, dual residual nan, iter 10000, polish + // 2, polish failed, mu updates 4) n: 97 n_eq+n_in: 153 + // MAROS_MESZAROS_DIR "QAFIRO.mat", // Fail: + // Values nan (primal residual nan, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 3) n: 32 n_eq+n_in: 59 + // MAROS_MESZAROS_DIR "QBANDM.mat", // Pass + // MAROS_MESZAROS_DIR "QBEACONF.mat", // Pass + // MAROS_MESZAROS_DIR "QBORE3D.mat", // Fail: Does + // not converge enough (primal residual 1e-1, dual residual 8e-3, iter + // 10000, polish not run, mu updates 1) n: 315 n_eq+n_in: 548 + // MAROS_MESZAROS_DIR "QBRANDY.mat", // Fail: Does + // not converge enough (primal residual 7e-2, dual residual 4e-2, iter + // 10000, polish not run, mu updates 2) n: 249 n_eq+n_in: 469 + // MAROS_MESZAROS_DIR "QCAPRI.mat", // Fail: Does + // not converge well (primal residual 7e-3, dual residual 7e-1, iter 10000, + // polish not run, mu updates 12) n: 353 n_eq+n_in: 624 + // MAROS_MESZAROS_DIR "QE226.mat", // Fail: Does + // not converge enough (primal residual 2e-3, dual residual 2e-3, iter + // 10000, polish not run, mu updates 1) n: 282 n_eq+n_in: 505 + // MAROS_MESZAROS_DIR "QETAMACR.mat", // Skipping + // MAROS_MESZAROS_DIR "QFFFFF80.mat", // Skipping + // MAROS_MESZAROS_DIR "QFORPLAN.mat", // Fail: Does + // not converge enough (primal residual 1e-2, dual residual 1e-1, iter + // 10000, polish not run, mu updates 10) n: 421 n_eq+n_in: 582 + // MAROS_MESZAROS_DIR "QGFRDXPN.mat", // Skipping + // MAROS_MESZAROS_DIR "QGROW15.mat", // Fail: Does + // not converge well (primal residual 6e3, dual residual 2e-2, iter 10000, + // polish not run, mu updates 50) n: 645 n_eq+n_in: 945 + // MAROS_MESZAROS_DIR "QGROW22.mat", // Skipping + // MAROS_MESZAROS_DIR "QGROW7.mat", // Fail: Does + // not converge well (primal residual 8e3, dual residual 6e-1, iter 10000, + // polish not run, mu updates 64) n: 301 n_eq+n_in: 441 + // MAROS_MESZAROS_DIR "QISRAEL.mat", // Fail: Does + // not converge well (primal residual 4e2, dual residual 1e3, iter 10000, + // polish not run, mu updates 30) n: 142 n_eq+n_in: 316 + // MAROS_MESZAROS_DIR "QPCBLEND.mat", // Fail: + // Values nan (primal residual nan, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 3) n: 83 n_eq+n_in: 157 + // MAROS_MESZAROS_DIR "QPCBOEI1.mat", // Fail: Does + // not converge enough (primal residual 1e-1, dual residual 1e-3, iter + // 10000, polish not run, mu updates 1) n: 384 n_eq+n_in: 735 + // MAROS_MESZAROS_DIR "QPCBOEI2.mat", // Fail: Does + // not converge well (primal residual 1e-2, dual residual 1e1, iter 10000, + // polish not run, mu updates 4) n: 143 n_eq+n_in: 309 + // MAROS_MESZAROS_DIR "QPCSTAIR.mat", // Pass + // MAROS_MESZAROS_DIR "QPILOTNO.mat", // Skipping + // MAROS_MESZAROS_DIR "QPTEST.mat", // Pass + // MAROS_MESZAROS_DIR "QRECIPE.mat", // Pass + // MAROS_MESZAROS_DIR "QSC205.mat", // Fail: + // Values nan (primal residual nan, dual residual nan, iter 10000, polish 2, + // polish failed, mu updates 4) n: 203 n_eq+n_in: 408 + // MAROS_MESZAROS_DIR "QSCAGR25.mat", // Fail: Does + // not converge enough (primal residual 6e-2, dual residual 6e-2, iter + // 10000, polish not run, mu updates 3) n: 500 n_eq+n_in: 971 + // MAROS_MESZAROS_DIR "QSCAGR7.mat", // Fail: Does + // not converge enough (primal residual 9e-2, dual residual 1e0, iter 10000, + // polish not run, mu updates 12) n: 140 n_eq+n_in: 269 + // MAROS_MESZAROS_DIR "QSCFXM1.mat", // Fail: Does + // not converge enough (primal residual 3e-2, dual residual 4e-2, iter + // 10000, polish not run, mu updates 1) n: 457 n_eq+n_in: 787 + // MAROS_MESZAROS_DIR "QSCFXM2.mat", // Skipping + // MAROS_MESZAROS_DIR "QSCFXM3.mat", // Skipping + MAROS_MESZAROS_DIR + "QSCORPIO.mat", // Fail: Does not converge well and polish -> ?? (primal + // residual 2e-4, dual residual 7e-1, iter 10000, polish 2, + // polish failed, mu updates 18) n: 358 n_eq+n_in: 746 + // MAROS_MESZAROS_DIR "QSCRS8.mat", // Skipping + MAROS_MESZAROS_DIR + "QSCSD1.mat", // Fail: Does not converge well and polish -> ?? (primal + // residual 1e-2, dual residual 1e-2, iter 10000, polish 2, + // polish failed, mu updates 134) n: 760 n_eq+n_in: 837 + // MAROS_MESZAROS_DIR "QSCSD6.mat", // Skipping + // MAROS_MESZAROS_DIR "QSCSD8.mat", // Skipping + // MAROS_MESZAROS_DIR "QSCTAP1.mat", // Fail: Does + // not converge well (primal residual 5e-3, dual residual 1e0, iter 10000, + // polish not run, mu updates 16) n: 480 n_eq+n_in: 780 + // MAROS_MESZAROS_DIR "QSCTAP2.mat", // Skipping + // MAROS_MESZAROS_DIR "QSCTAP3.mat", + // MAROS_MESZAROS_DIR "QSEBA.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHARE1B.mat", // Fail: Does + // not converge well (primal residual 3e0, dual residual 3e-3, iter 10000, + // polish not run, mu updates 4) n: 225 n_eq+n_in: 342 + // MAROS_MESZAROS_DIR "QSHARE2B.mat", // Fail: Does + // not converge enough (primal residual 1e-1, dual residual 8e-1, iter + // 10000, polish not run, mu updates 4) n: 79 n_eq+n_in: 175 + // MAROS_MESZAROS_DIR "QSHELL.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP04L.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP04S.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP08L.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP08S.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP12L.mat", // Skipping + // MAROS_MESZAROS_DIR "QSHIP12S.mat", // Skipping + // MAROS_MESZAROS_DIR "QSIERRA.mat", // Skipping + // MAROS_MESZAROS_DIR "QSTAIR.mat", // Fail: Does + // not converge enough (primal residual 2e-2, dual residual 3e-1, iter + // 10000, polish not run, mu updates 13) n: 467 n_eq+n_in: 823 + // MAROS_MESZAROS_DIR "QSTANDAT.mat", // Skipping + // MAROS_MESZAROS_DIR "S268.mat", // Pass + // MAROS_MESZAROS_DIR "STADAT1.mat", // Skipping + // MAROS_MESZAROS_DIR "STADAT2.mat", // Skipping + // MAROS_MESZAROS_DIR "STADAT3.mat", // Skipping + // MAROS_MESZAROS_DIR "STCQP1.mat", // Skipping + // MAROS_MESZAROS_DIR "STCQP2.mat", // Skipping + // MAROS_MESZAROS_DIR "TAME.mat", // Pass + // MAROS_MESZAROS_DIR "UBH1.mat", // Skipping + // MAROS_MESZAROS_DIR "VALUES.mat", // Pass + // MAROS_MESZAROS_DIR "YAO.mat", // Skipping + // MAROS_MESZAROS_DIR "ZECEVIC2.mat", // Pass +}; + +TEST_CASE("dense maros meszaros using the api") +{ + using T = double; + using isize = proxqp::utils::isize; + proxsuite::proxqp::Timer timer; + T elapsed_time = 0.0; + + for (auto const* file : files) { + auto qp = load_qp(file); + isize n = qp.P.rows(); + isize n_eq_in = qp.A.rows(); + + const bool skip = n > 1000 || n_eq_in > 1000; + if (skip) { + std::cout << " path: " << qp.filename << " n: " << n + << " n_eq+n_in: " << n_eq_in << " - skipping" << std::endl; + } else { + std::cout << " path: " << qp.filename << " n: " << n + << " n_eq+n_in: " << n_eq_in << std::endl; + } + + if (!skip) { + + auto preprocessed = preprocess_qp(qp); + auto& H = preprocessed.H; + auto& A = preprocessed.A; + auto& C = preprocessed.C; + auto& g = preprocessed.g; + auto& b = preprocessed.b; + auto& u = preprocessed.u; + auto& l = preprocessed.l; + + isize dim = H.rows(); + isize n_eq = A.rows(); + isize n_in = C.rows(); + timer.stop(); + timer.start(); + pod::QP qp{ + dim, n_eq, n_in, false, proxsuite::proxqp::DenseBackend::PrimalDualLDLT + }; // creating QP object + + qp.settings.default_mu_eq = T(1.E-2); + qp.settings.default_mu_in = T(1.E1); + + qp.init(H, g, A, b, C, l, u); + + qp.settings.eps_abs = 2e-8; + qp.settings.eps_rel = 0; + qp.settings.eps_primal_inf = 1e-12; + qp.settings.eps_dual_inf = 1e-12; + + { + qp.settings.max_iter = 10000; + qp.settings.verbose = false; + } + + auto& eps = qp.settings.eps_abs; + + // for (size_t it = 0; it < 2; ++it) { + size_t it = 0; // Just test first solve for a first glance + { + if (it > 0) + qp.settings.initial_guess = proxsuite::proxqp::InitialGuessStatus:: + WARM_START_WITH_PREVIOUS_RESULT; + + qp.solve(); + const auto& x = qp.results.x; + const auto& y = qp.results.y; + const auto& z = qp.results.z; + + T prim_eq = proxqp::dense::infty_norm(A * x - b); + T prim_in = + proxqp::dense::infty_norm(helpers::positive_part(C * x - u) + + helpers::negative_part(C * x - l)); + std::cout << "primal residual " << std::max(prim_eq, prim_in) + << std::endl; + std::cout << "dual residual " + << proxqp::dense::infty_norm(H * x + g + A.transpose() * y + + C.transpose() * z) + << std::endl; + std::cout << "admm iter " << qp.results.info.iter_ext << std::endl; + { + std::cout << "polish calls " << qp.results.info.polish_calls + << std::endl; + switch (qp.results.info.polish_status) { + case pp::PolishStatus::POLISH_NOT_RUN: { + std::cout << "polish not run" << std::endl; + break; + } + case pp::PolishStatus::POLISH_SUCCEED: { + std::cout << "polish succeeded" << std::endl; + break; + } + case pp::PolishStatus::POLISH_FAILED: { + std::cout << "polish failed" << std::endl; + break; + } + case pp::PolishStatus::POLISH_NO_ACTIVE_SET_FOUND: { + std::cout << "polish no active set found" << std::endl; + break; + } + } + std::cout << "mu updates " << qp.results.info.mu_updates << std::endl; + } + CHECK(proxqp::dense::infty_norm(H * x + g + A.transpose() * y + + C.transpose() * z) < 2 * eps); + CHECK(proxqp::dense::infty_norm(A * x - b) > -eps); + CHECK((C * x - l).minCoeff() > -eps); + CHECK((C * x - u).maxCoeff() < eps); + + if (it > 0) { + CHECK(qp.results.info.iter_ext == 0); + } + } + timer.stop(); + elapsed_time += timer.elapsed().user; + } + } + std::cout << "timings total : \t" << elapsed_time * 1e-3 << "ms" << std::endl; +} diff --git a/test/src/osqp_dense_qp_wrapper.cpp b/test/src/osqp_dense_qp_wrapper.cpp index e69de29bb..5002fef9e 100644 --- a/test/src/osqp_dense_qp_wrapper.cpp +++ b/test/src/osqp_dense_qp_wrapper.cpp @@ -0,0 +1,7677 @@ +// +// Copyright (c) 2022-2024 INRIA +// +#include +#include +#include +#include +#include +#include +#include + +using T = double; +using namespace proxsuite; +using namespace proxsuite::proxqp; +namespace pod = proxsuite::osqp::dense; + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with inequality constraints" + "and empty equality constraints") +{ + std::cout << "---testing sparse random strongly convex qp with inequality " + "constraints " + "and empty equality constraints---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + dense::isize n_eq(0); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + + // Testing with empty but properly sized matrix A of size (0, 10) + std::cout << "Solving QP with" << std::endl; + std::cout << "dim: " << dim << std::endl; + std::cout << "n_eq: " << n_eq << std::endl; + std::cout << "n_in: " << n_in << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; + std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + qp.init(qp_random.H, + qp_random.g, + nullopt, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); + + T pri_res = + (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(); + T dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // Testing with empty matrix A of size (0, 0) + qp_random.A = Eigen::MatrixXd(); + qp_random.b = Eigen::VectorXd(); + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + + std::cout << "Solving QP with" << std::endl; + std::cout << "dim: " << dim << std::endl; + std::cout << "n_eq: " << n_eq << std::endl; + std::cout << "n_in: " << n_in << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "A.cols() : " << qp_random.A.cols() << std::endl; + std::cout << "A.rows() : " << qp_random.A.rows() << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = (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(); + dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // Testing with nullopt + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + + qp3.init(qp_random.H, + qp_random.g, + nullopt, + nullopt, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + + pri_res = (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(); + dua_res = (qp_random.H * qp.results.x + qp_random.g + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + 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") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update H---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating H" << std::endl; + qp_random.H.setIdentity(); + qp.update(qp_random.H, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update A") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update A---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating A" << std::endl; + qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + qp.update(nullopt, nullopt, qp_random.A, nullopt, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update C") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update C---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating C" << std::endl; + qp_random.C = utils::rand::sparse_matrix_rand_not_compressed( + n_in, dim, sparsity_factor); + qp.update(nullopt, nullopt, nullopt, nullopt, qp_random.C, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update b") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update b---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + auto x_sol = utils::rand::vector_rand(dim); + qp_random.b = qp_random.A * x_sol; + qp.update(nullopt, nullopt, nullopt, qp_random.b, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update u") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update u---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + auto x_sol = utils::rand::vector_rand(dim); + auto delta = utils::Vec(n_in); + for (isize i = 0; i < n_in; ++i) { + delta(i) = utils::rand::uniform_rand(); + } + + qp_random.u = qp_random.C * x_sol + delta; + qp.update(nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, qp_random.u); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update g") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update g---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating g" << std::endl; + auto g = utils::rand::vector_rand(dim); + + qp_random.g = g; + qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and inequality " + "constraints: test update H and A and b and u and l") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update H and A and b and u and l---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "H : " << qp_random.H << std::endl; + std::cout << "g : " << qp_random.g << std::endl; + std::cout << "A : " << qp_random.A << std::endl; + std::cout << "b : " << qp_random.b << std::endl; + std::cout << "C : " << qp_random.C << std::endl; + std::cout << "u : " << qp_random.u << std::endl; + std::cout << "l : " << qp_random.l << std::endl; + + std::cout << "testing updating b" << std::endl; + qp_random.H = utils::rand::sparse_positive_definite_rand_not_compressed( + dim, strong_convexity_factor, sparsity_factor); + qp_random.A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + auto x_sol = utils::rand::vector_rand(dim); + auto delta = utils::Vec(n_in); + for (proxqp::isize i = 0; i < n_in; ++i) { + delta(i) = utils::rand::uniform_rand(); + } + qp_random.b = qp_random.A * x_sol; + qp_random.u = qp_random.C * x_sol + delta; + qp_random.l = qp_random.C * x_sol - delta; + qp.update(qp_random.H, + nullopt, + qp_random.A, + qp_random.b, + nullopt, + qp_random.l, + qp_random.u); + + std::cout << "after upating" << std::endl; + std::cout << "H : " << qp.model.H << std::endl; + std::cout << "g : " << qp.model.g << std::endl; + std::cout << "A : " << qp.model.A << std::endl; + std::cout << "b : " << qp.model.b << std::endl; + std::cout << "C : " << qp.model.C << std::endl; + std::cout << "u : " << qp.model.u << std::endl; + std::cout << "l : " << qp.model.l << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update rho") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update rho---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "rho : " << qp.results.info.rho << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.e-7), + nullopt, + nullopt); // restart the problem with default options + std::cout << "after upating" << std::endl; + std::cout << "rho : " << qp.results.info.rho << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.e-7), + nullopt, + nullopt); + std::cout << "rho : " << qp2.results.info.rho << std::endl; + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update mu_eq and mu_in") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test update mu_eq and mu_in---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "before upating" << std::endl; + std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + nullopt, + T(1.e-2), + T(1.e-3)); + + std::cout << "after upating" << std::endl; + std::cout << "mu_in : " << qp.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp.results.info.mu_eq << std::endl; + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + nullopt, + T(1.e-2), + T(1.e-3)); + qp2.solve(); + std::cout << "mu_in : " << qp2.results.info.mu_in << std::endl; + std::cout << "mu_eq : " << qp2.results.info.mu_eq << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test warm starting---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + isize dim = 10; + isize n_eq(dim / 4); + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + auto x_wm = utils::rand::vector_rand(dim); + auto y_wm = utils::rand::vector_rand(n_eq); + auto z_wm = utils::rand::vector_rand(n_in); + std::cout << "proposed warm start" << std::endl; + std::cout << "x_wm : " << x_wm << std::endl; + std::cout << "y_wm : " << y_wm << std::endl; + std::cout << "z_wm : " << z_wm << std::endl; + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + qp.solve(x_wm, y_wm, z_wm); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------using API solving qp with dim after updating: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(x_wm, y_wm, z_wm); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + std::cout << "------ conter factual check with another QP object starting at " + "the updated model : " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test dense init") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test dense init---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init( + Eigen::Matrix( + qp_random.H), + qp_random.g, + Eigen::Matrix( + qp_random.A), + qp_random.b, + Eigen::Matrix( + qp_random.C), + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test with no initial guess") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test with equality constrained initial guess") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test with warm start with previous result") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with warm start with previous result---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + + auto x = qp.results.x; + auto y = qp.results.y; + auto z = qp.results.z; + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.ruiz.scale_primal_in_place({ from_eigen, x }); + qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); + qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.solve(x, y, z); + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp.update( + nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, false); + 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()); + 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) + .lpNorm(); + std::cout << "------using API solving qp with dim with qp after warm start " + "with previous result: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp2: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test with cold start option") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test with cold start option---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp: " << dim + << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + + auto x = qp.results.x; + auto y = qp.results.y; + auto z = qp.results.z; + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.ruiz.scale_primal_in_place({ from_eigen, x }); + qp2.ruiz.scale_dual_in_place_eq({ from_eigen, y }); + qp2.ruiz.scale_dual_in_place_in({ from_eigen, z }); + // std::cout << "after scaling x " << x << " qp.results.x " << qp.results.x + // << std::endl; + qp2.solve(x, y, z); + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp.update( + nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt, 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()); + 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) + .lpNorm(); + std::cout << "------using API solving qp with dim with qp after warm start " + "with cold start option: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with cold start option: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at initialization") +{ + + std::cout + << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at initialization---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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, + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp with " + "preconditioner derived: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp.ruiz.delta << " ruiz scalar factor " + << qp.ruiz.c << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + false); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp without preconditioner derivation: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " + << qp2.ruiz.c << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at update") +{ + + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test equilibration options at update---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_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, + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with dim with qp with " + "preconditioner derived: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true); // rederive preconditioner with previous options, i.e., redo + // exact same derivations + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp with preconditioner re derived " + "after an update (should get exact same results): " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + std::cout << "------using API solving qp with preconditioner derivation and " + "another object QP: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + qp2.update( + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + false); // use previous preconditioner: should get same result as well + qp2.solve(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + std::cout << "------using API solving qp without preconditioner derivation: " + << dim << " neq: " << n_eq << " nin: " << n_in << std::endl; + std::cout << "primal residual: " << pri_res << std::endl; + std::cout << "dual residual: " << dua_res << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "ruiz vector : " << qp2.ruiz.delta << " ruiz scalar factor " + << qp2.ruiz.c << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +///// TESTS ALL INITIAL GUESS OPTIONS FOR MULTIPLE SOLVES AT ONCE +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with no initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with equality " + "constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with equality constrained initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with equality " + "constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with cold start " + "initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with cold start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with warm start") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: warm start test from init") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp2.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace for qp2 : " << qp2.work.dirty << std::endl; + qp2.solve(qp.results.x, qp.results.y, qp.results.z); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve with new QP object" << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +/// TESTS WITH UPDATE + INITIAL GUESS OPTIONS + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update and multiple solve at once with " + "no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with no initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "equality constrained initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with equality constrained initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with equality " + "constrained initial guess and then warm start with previous results") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test multiple solve at once with no initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start with previous result and first solve with " + "no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "cold start initial guess and then cold start option") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + + std::cout << "Test with cold start with previous result and first solve with " + "equality constrained initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + bool update_preconditioner = true; + qp.update(qp_random.H, + qp_random.g, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + update_preconditioner); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test update + multiple solve at once with " + "warm start") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test with warm start and first solve with no initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + qp.settings.initial_guess = InitialGuessStatus::WARM_START; + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + auto x_wm = qp.results.x; // keep previous result + auto y_wm = qp.results.y; + auto z_wm = qp.results.z; + bool update_preconditioner = true; + // test with a false update (the warm start should give the exact solution) + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; + qp.solve(x_wm, y_wm, z_wm); + 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()); + 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) + .lpNorm(); + std::cout << "Second solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + x_wm = qp.results.x; // keep previous result + y_wm = qp.results.y; + z_wm = qp.results.z; + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + // try now with a real update + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + update_preconditioner); + std::cout << "dirty workspace after update: " << qp.work.dirty << std::endl; + qp.solve(x_wm, y_wm, z_wm); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Third solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fourth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + std::cout << "dirty workspace : " << qp.work.dirty << std::endl; + qp.solve(qp.results.x, qp.results.y, qp.results.z); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "Fifth solve " << std::endl; + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; +} + +TEST_CASE( + "ProxQP::dense: Test initializaton with rho for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test initializaton with rho for different initial guess" + << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp.solve(); + CHECK(qp.results.info.rho == T(1.E-7)); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp2.solve(); + CHECK(qp2.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp3.solve(); + CHECK(qp3.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp4.solve(); + CHECK(qp4.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + true, + T(1.E-7)); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + CHECK(qp5.results.info.rho == T(1.E-7)); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test g update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test g update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + auto old_g = qp_random.g; + qp_random.g = utils::rand::vector_rand(dim); + qp.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + 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()); + 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) + .lpNorm(); + CHECK((qp.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + old_g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK((qp2.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + old_g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK((qp3.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + old_g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK((qp4.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + old_g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + old_g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, qp_random.g, nullopt, nullopt, nullopt, nullopt, nullopt); + qp5.solve(); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK((qp5.model.g - qp_random.g).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test A update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test A update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + auto new_A = utils::rand::sparse_matrix_rand_not_compressed( + n_eq, dim, sparsity_factor); + qp.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp.solve(); + pri_res = + std::max((new_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()); + dua_res = + (qp_random.H * qp.results.x + qp_random.g + + new_A.transpose() * qp.results.y + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + CHECK((qp.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp2.solve(); + pri_res = std::max( + (new_A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + new_A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK((qp2.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp3.solve(); + pri_res = std::max( + (new_A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + new_A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK((qp3.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp4.solve(); + pri_res = std::max( + (new_A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + new_A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK((qp4.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, nullopt, new_A, nullopt, nullopt, nullopt, nullopt); + qp5.solve(); + pri_res = std::max( + (new_A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + new_A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK((qp5.model.A - new_A).lpNorm() <= eps_abs); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test rho update for different initial guess") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + std::cout << "Test rho update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + 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()); + 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) + .lpNorm(); + CHECK(qp.results.info.rho == T(1.E-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp2.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(qp2.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; + + pod::QP qp3(dim, n_eq, n_in); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.initial_guess = + InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + CHECK(qp3.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp3.results.info.iter + << std::endl; + std::cout << "setup timing " << qp3.results.info.setup_time << " solve time " + << qp3.results.info.solve_time << std::endl; + + pod::QP qp4(dim, n_eq, n_in); + qp4.settings.eps_abs = eps_abs; + qp4.settings.eps_rel = 0; + qp4.settings.initial_guess = + InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + qp4.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp4.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp4.solve(); + pri_res = std::max( + (qp_random.A * qp4.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp4.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp4.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp4.results.x + qp_random.g + + qp_random.A.transpose() * qp4.results.y + + qp_random.C.transpose() * qp4.results.z) + .lpNorm(); + CHECK(qp4.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp4.results.info.iter + << std::endl; + std::cout << "setup timing " << qp4.results.info.setup_time << " solve time " + << qp4.results.info.solve_time << std::endl; + + pod::QP qp5(dim, n_eq, n_in); + qp5.settings.eps_abs = eps_abs; + qp5.settings.eps_rel = 0; + qp5.settings.initial_guess = InitialGuessStatus::WARM_START; + qp5.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp5.solve(qp3.results.x, qp3.results.y, qp3.results.z); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + qp5.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + true, + T(1.E-7)); + qp5.solve(); + pri_res = std::max( + (qp_random.A * qp5.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp5.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp5.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp5.results.x + qp_random.g + + qp_random.A.transpose() * qp5.results.y + + qp_random.C.transpose() * qp5.results.z) + .lpNorm(); + CHECK(qp5.results.info.rho == T(1.e-7)); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp5.results.info.iter + << std::endl; + std::cout << "setup timing " << qp5.results.info.setup_time << " solve time " + << qp5.results.info.solve_time << std::endl; +} + +TEST_CASE("ProxQP::dense: Test g update for different warm start with previous " + "result option") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + + std::cout << "Test rho update for different initial guess" << std::endl; + std::cout << "dirty workspace before any solving: " << qp.work.dirty + << std::endl; + + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + // a new linear cost slightly modified + auto g = qp_random.g * 0.95; + + qp.update(nullopt, g, nullopt, nullopt, nullopt, nullopt, nullopt); + 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()); + dua_res = + (qp_random.H * qp.results.x + g + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp.results.info.iter + << std::endl; + std::cout << "setup timing " << qp.results.info.setup_time << " solve time " + << qp.results.info.solve_time << std::endl; + + pod::QP qp2(dim, n_eq, n_in); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.settings.initial_guess = + InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + qp2.init(qp_random.H, + g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp2.solve(); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = + (qp_random.H * qp2.results.x + g + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + std::cout << "--n = " << dim << " n_eq " << n_eq << " n_in " << n_in + << std::endl; + std::cout << "; dual residual " << dua_res << "; primal residual " << pri_res + << std::endl; + std::cout << "total number of iteration: " << qp2.results.info.iter + << std::endl; + std::cout << "setup timing " << qp2.results.info.setup_time << " solve time " + << qp2.results.info.solve_time << std::endl; +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using warm start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using warm start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + qp.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + qp3.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using cold start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using cold start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using equality constrained initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after updates using no initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "updates using no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using warm start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using warm start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + qp.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + qp2.settings.initial_guess = + proxsuite::proxqp::InitialGuessStatus::WARM_START_WITH_PREVIOUS_RESULT; + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.settings.verbose = true; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + // warm start with previous result used, hence if the qp is small and + // simple, the parameters should not changed during first solve, and also + // after as we start at the solution + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using cold start with previous results") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using cold start with previous results---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::COLD_START_WITH_PREVIOUS_RESULT); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after several solves " + "using equality constrained initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using equality constrained initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::EQUALITY_CONSTRAINED_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +DOCTEST_TEST_CASE( + "ProxQP::dense: sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings " + "after several solves using no initial guess") +{ + std::cout << "---testing sparse random strongly convex qp with equality and " + "inequality constraints: test changing default settings after " + "several solves using no initial guess---" + << std::endl; + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + T rho(1.e-7); + T mu_eq(1.e-4); + bool compute_preconditioner = true; + + pod::QP qp{ dim, n_eq, n_in }; // creating QP object + qp.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) <= 1.E-9); + + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(rho - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(rho - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6); + for (isize iter = 0; iter < 10; ++iter) { + qp.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp.settings.default_rho) < 1.e-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp.results.info.rho) < 1.e-9); + 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()); + 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) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp2{ dim, n_eq, n_in }; // creating QP object + qp2.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp2.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp2.settings.eps_abs = eps_abs; + qp2.settings.eps_rel = 0; + qp2.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + nullopt, + mu_eq); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + qp2.solve(); + DOCTEST_CHECK(std::abs(mu_eq - qp2.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp2.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp2.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp2.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp2.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp2.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp2.results.x + qp_random.g + + qp_random.A.transpose() * qp2.results.y + + qp_random.C.transpose() * qp2.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + // conter factual check with another QP object starting at the updated model + pod::QP qp3{ dim, n_eq, n_in }; // creating QP object + qp3.settings.initial_guess = proxqp::InitialGuessStatus::NO_INITIAL_GUESS; + DOCTEST_CHECK(qp3.settings.initial_guess == + proxqp::InitialGuessStatus::NO_INITIAL_GUESS); + qp3.settings.eps_abs = eps_abs; + qp3.settings.eps_rel = 0; + qp3.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + compute_preconditioner, + rho, + mu_eq); + + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(rho - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(rho - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(mu_eq - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(T(1) / mu_eq - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } + + qp3.update(nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + compute_preconditioner, + 1.e-6, + 1.e-3); + for (isize iter = 0; iter < 10; ++iter) { + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + qp3.solve(); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.settings.default_rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-6 - qp3.results.info.rho) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.settings.default_mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e-3 - qp3.results.info.mu_eq) <= 1.E-9); + DOCTEST_CHECK(std::abs(1.e3 - qp3.results.info.mu_eq_inv) <= 1.E-9); + pri_res = std::max( + (qp_random.A * qp3.results.x - qp_random.b).lpNorm(), + (helpers::positive_part(qp_random.C * qp3.results.x - qp_random.u) + + helpers::negative_part(qp_random.C * qp3.results.x - qp_random.l)) + .lpNorm()); + dua_res = (qp_random.H * qp3.results.x + qp_random.g + + qp_random.A.transpose() * qp3.results.y + + qp_random.C.transpose() * qp3.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= eps_abs); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +TEST_CASE("ProxQP::dense: init must be called before update") +{ + + double sparsity_factor = 0.15; + T eps_abs = T(1e-9); + utils::rand::set_seed(1); + dense::isize dim = 10; + + 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); + + pod::QP qp(dim, n_eq, n_in); + + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + + // call update without init, update calls init internally + qp.update(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u, + 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()); + 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) + .lpNorm(); + CHECK(dua_res <= eps_abs); + CHECK(pri_res <= eps_abs); + + qp_random.H *= 2.; + qp_random.g = utils::rand::vector_rand(dim); + qp.update(qp_random.H, + qp_random.g, + nullopt, + nullopt, + nullopt, + nullopt, + nullopt, + 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()); + 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) + .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; + // pod::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; + //////////////// + + pod::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; + } + + pod::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 + pod::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 + pod::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 + pod::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); +} +TEST_CASE("ProxQP::dense: test primal infeasibility solving") +{ + double sparsity_factor = 0.15; + T eps_abs = T(1e-5); + utils::rand::set_seed(1); + dense::isize dim = 20; + + dense::isize n_eq(dim / 4); + dense::isize n_in(dim / 4); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.eps_abs = eps_abs; + qp.settings.eps_rel = 0; + // create infeasible problem + qp_random.b.array() += T(10.); + qp_random.u.array() -= T(100.); + qp.settings.initial_guess = InitialGuessStatus::NO_INITIAL_GUESS; + qp.settings.primal_infeasibility_solving = true; + qp.settings.eps_primal_inf = T(1.E-4); + qp.settings.eps_dual_inf = T(1.E-4); + qp.settings.verbose = true; + qp.init(qp_random.H, + qp_random.g, + qp_random.A, + qp_random.b, + qp_random.C, + qp_random.l, + qp_random.u); + qp.solve(); + + proxsuite::proxqp::utils::Vec rhs_dim(dim); + proxsuite::proxqp::utils::Vec rhs_n_eq(n_eq); + rhs_n_eq.setOnes(); + proxsuite::proxqp::utils::Vec rhs_n_in(n_in); + rhs_n_in.setOnes(); + rhs_dim.noalias() = + qp_random.A.transpose() * rhs_n_eq + qp_random.C.transpose() * rhs_n_in; + T scaled_eps = (rhs_dim).lpNorm() * eps_abs; + + T pri_res = + (qp_random.A.transpose() * (qp_random.A * qp.results.x - qp_random.b) + + qp_random.C.transpose() * + (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(); + T dua_res = (qp_random.H.selfadjointView() * qp.results.x + + qp_random.g + qp_random.A.transpose() * qp.results.y + + qp_random.C.transpose() * qp.results.z) + .lpNorm(); + DOCTEST_CHECK(pri_res <= scaled_eps); + DOCTEST_CHECK(dua_res <= eps_abs); + } +} + +TEST_CASE("ProxQP::dense: estimate of minimal eigenvalues using Eigen") +{ + double sparsity_factor = 1.; + T tol = T(1e-6); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-1.); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= + tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += 100 * random_diag.array(); + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, EigenValueEstimateMethodOption::ExactMethod, 1.E-6, 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +TEST_CASE( + "ProxQP::dense: test estimate of minimal eigenvalue using manual choice") +{ + double sparsity_factor = 1.; + T tol = T(1e-6); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-1.); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + -1); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 1) <= + tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + minimal_eigenvalue); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += 100 * random_diag.array(); + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + minimal_eigenvalue); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +TEST_CASE( + "ProxQP::dense: test estimate of minimal eigenvalue using power iteration") +{ + double sparsity_factor = 1.; + T tol = T(1e-3); + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + for (isize i = 0; i < 1; ++i) { + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-0.5); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK( + std::abs(qp.results.info.minimal_H_eigenvalue_estimate + 0.5) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += random_diag.array(); + T minimal_eigenvalue = qp_random.H.diagonal().minCoeff(); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } + dim = 50; + n_eq = dim; + n_in = dim; + for (isize i = 0; i < 20; ++i) { + ::proxsuite::proxqp::utils::rand::set_seed(i); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + dense::Vec random_diag = proxqp::utils::rand::vector_rand(dim); + qp_random.H.diagonal().array() += + 100 * random_diag.array(); // add some random values to dense matrix + Eigen::SelfAdjointEigenSolver> es(qp_random.H, + Eigen::EigenvaluesOnly); + T minimal_eigenvalue = T(es.eigenvalues().minCoeff()); + + T estimate_minimal_eigen_value = + dense::estimate_minimal_eigen_value_of_symmetric_matrix( + qp_random.H, + EigenValueEstimateMethodOption::PowerIteration, + 1.E-6, + 10000); + + pod::QP qp(dim, n_eq, n_in); + qp.settings.max_iter = 1; + qp.settings.max_iter_in = 1; + 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, + true, + nullopt, + nullopt, + nullopt, + estimate_minimal_eigen_value); + + DOCTEST_CHECK(std::abs(qp.results.info.minimal_H_eigenvalue_estimate - + minimal_eigenvalue) <= tol); + } +} + +DOCTEST_TEST_CASE("check that model.is_valid function for symmetric matrices " + "works for epsilon precision") +{ + Eigen::Matrix matrix = Eigen::Matrix::Random(); + Eigen::Matrix symmetric_mat = matrix + matrix.transpose(); + + symmetric_mat(0, 1) = + symmetric_mat(1, 0) + std::numeric_limits::epsilon(); + + // compare the two checks for symmetry with and without tolerance + bool is_symmetric_without_tolerance = + symmetric_mat.isApprox(symmetric_mat.transpose(), 0.0); + bool is_symmetric_with_tolerance = symmetric_mat.isApprox( + symmetric_mat.transpose(), + std::numeric_limits::epsilon()); + DOCTEST_CHECK(is_symmetric_without_tolerance == false); + DOCTEST_CHECK(is_symmetric_with_tolerance == true); + + // initialize a model with a symmetric matrix as Hessian, this runs + // model.is_valid() that performs the check above + pod::QP qp(3, 0, 0); + qp.init(symmetric_mat, nullopt, nullopt, nullopt, nullopt, nullopt, nullopt); +} + +TEST_CASE("ProxQP::dense: test memory allocation when estimating biggest " + "eigenvalue with power iteration") +{ + double sparsity_factor = 1.; + utils::rand::set_seed(1); + dense::isize dim = 2; + dense::isize n_eq(dim); + dense::isize n_in(dim); + T strong_convexity_factor(1.e-2); + Eigen::Matrix H; + Eigen::VectorXd dw(2), rhs(2), err_v(2); + // trivial test + ::proxsuite::proxqp::utils::rand::set_seed(1234); + proxqp::dense::Model qp_random = proxqp::utils::dense_strongly_convex_qp( + dim, n_eq, n_in, sparsity_factor, strong_convexity_factor); + + qp_random.H.setZero(); + qp_random.H.diagonal().setOnes(); + qp_random.H.diagonal().tail(1).setConstant(-0.5); + H = qp_random.H; + PROXSUITE_EIGEN_MALLOC_NOT_ALLOWED(); + dense::power_iteration(H, dw, rhs, err_v, 1.E-6, 10000); + PROXSUITE_EIGEN_MALLOC_ALLOWED(); +} + +// TEST_CASE("ProxQP::dense: sparse random strongly convex qp with" +// "inequality constraints: test PrimalLDLT backend mu update") +// { + +// std::cout << "---testing sparse random strongly convex qp with" +// "inequality constraints: test PrimalLDLT backend mu update---" +// << std::endl; +// double sparsity_factor = 1; +// utils::rand::set_seed(1); +// isize dim = 3; +// isize n_eq(0); +// isize n_in(9); +// 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); +// pod::QP qp{ +// dim, +// n_eq, +// n_in, +// false, +// proxsuite::proxqp::HessianType::Dense, +// proxsuite::proxqp::DenseBackend::PrimalLDLT +// }; // creating QP object +// T eps_abs = T(1e-7); +// qp.settings.eps_abs = eps_abs; +// qp.settings.eps_rel = 0; +// qp.settings.compute_timings = true; +// qp.settings.verbose = true; +// qp.init(qp_random.H, +// qp_random.g, +// nullopt, +// nullopt, +// qp_random.C, +// nullopt, +// qp_random.u); +// qp.solve(); + +// DOCTEST_CHECK(qp.results.info.mu_updates > 0); + +// T pri_res = (helpers::negative_part(qp_random.C * qp.results.x - +// qp_random.l)) +// .lpNorm(); +// T dua_res = (qp_random.H * qp.results.x + qp_random.g + +// qp_random.C.transpose() * qp.results.z) +// .lpNorm(); +// DOCTEST_CHECK(pri_res <= eps_abs); +// DOCTEST_CHECK(dua_res <= eps_abs); + +// std::cout << "------using API solving qp with dim: " << dim +// << " neq: " << n_eq << " nin: " << n_in << std::endl; +// std::cout << "primal residual: " << pri_res << std::endl; +// std::cout << "dual residual: " << dua_res << std::endl; +// std::cout << "total number of iteration: " << qp.results.info.iter +// << std::endl; +// std::cout << "setup timing " << qp.results.info.setup_time << " solve time +// " +// << qp.results.info.solve_time << std::endl; +// } \ No newline at end of file