// Copyright (c) 2020-2021 GeometryFactory SARL (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org) // // $URL$ // $Id$ // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Dmitry Anisimov // Mael Rouxel-Labbé // #ifndef CGAL_OSQP_QUADRATIC_PROGRAM_TRAITS_H #define CGAL_OSQP_QUADRATIC_PROGRAM_TRAITS_H #if defined(CGAL_USE_OSQP) || defined(DOXYGEN_RUNNING) // STL includes. #include #include #include #include #include // CGAL includes. #include // OSQP includes. #include namespace CGAL { /*! \ingroup PkgSolverInterfaceNLP \brief wraps the external OSQP solver. This class provides an interface for formulating and solving constrained or unconstrained quadratic programs using the \ref thirdpartyOSQP "OSQP" library, which must be available on the system. \tparam FT number type that `FieldNumberType` \note The `FT` type is provided for convenience. Internally, this FT type is converted to `OSQPFloat` type that can be set either to `float` or `double`. By default, the `double` type is used. After the optimization is complete, the `OSQPFloat` type is converted back to `FT`. See more about `OSQPFloat` here. \cgalModels{QuadraticProgramTraits} */ template class OSQP_quadratic_program_traits { using Triplet = std::tuple; // row, col, value private: std::size_t n; // number of variables std::size_t m; // number of constraints std::vector P_vec, A_vec; std::vector q_vec, l_vec, u_vec; public: /// %Default constructor OSQP_quadratic_program_traits() : n(0), m(0) { } /// Constructor /// \param n the number of variables OSQP_quadratic_program_traits(const std::size_t n) : n(n), m(0) { // 6*n, just guessing that this is some kind of sparse problem on a nice 2D mesh P_vec.reserve(6 * n); q_vec.reserve(n); } /// Constructor /// \param n the number of variables /// \param m the number of constraints OSQP_quadratic_program_traits(const std::size_t n, const std::size_t m) : n(n), m(m) { P_vec.reserve(6 * n); q_vec.reserve(n); A_vec.reserve(m); l_vec.reserve(m); u_vec.reserve(m); } public: /// Resets the problem, removing all existing entries and setting sizes to `0`. void clear() { n = m = 0; P_vec.clear(); A_vec.clear(); q_vec.clear(); l_vec.clear(); u_vec.clear(); } /// Changes the number of variables and the number of constraints of the problem. /// /// \warning Calling this function also clears all previous entries. void resize(const std::size_t new_n, const std::size_t new_m = 0) { clear(); n = new_n; m = new_m; P_vec.reserve(6 * n); q_vec.reserve(n); if(m > 0) { A_vec.reserve(m); l_vec.reserve(m); u_vec.reserve(m); } } /// \cond SKIP_IN_MANUAL void set_P(const std::size_t i, const std::size_t j, const FT value) { if(j < i) return; if(j >= n) // no need to test i since j >= i n = j+1; P_vec.emplace_back(i, j, value); } void set_q(const std::size_t i, const FT value) { if(i >= n) n = i+1; if(i >= q_vec.size()) q_vec.resize(n); q_vec[i] = value; } void set_r(const FT) { // is not used here } void set_A(const std::size_t i, const std::size_t j, const FT value) { if(i >= m) m = i+1; if(j >= n) n = j+1; A_vec.emplace_back(i, j, value); } void set_l(const std::size_t i, const FT value) { if(i >= m) m = i+1; if(i >= l_vec.size()) l_vec.resize(m); l_vec[i] = value; } void set_u(const std::size_t i, const FT value) { if(i >= m) m = i+1; if(i >= u_vec.size()) u_vec.resize(m); u_vec[i] = value; } template bool solve(OutIterator solution, const double eps_abs = 1e-10, const double eps_rel = 1e-15, const bool verbose = false) { if(verbose) { std::cout << "num variables = " << n << std::endl; std::cout << "num constraints = " << m << std::endl; } CGAL_precondition(n >= 1); // m >= 0 CGAL_precondition(q_vec.size() == n); CGAL_precondition(l_vec.size() == m && l_vec.size() == m); const OSQPInt P_nnz = static_cast(P_vec.size()); auto P_x = std::make_unique(P_nnz); auto P_i = std::make_unique(P_nnz); auto P_p = std::make_unique(n + 1); set_matrix_from_triplets("P", P_vec, P_x.get(), P_i.get(), P_p.get()); if(verbose) std::cout << "P_nnz: " << P_nnz << std::endl; const OSQPInt A_nnz = static_cast(A_vec.size()); auto A_x = std::make_unique(A_nnz); auto A_i = std::make_unique(A_nnz); auto A_p = std::make_unique(n + 1); set_matrix_from_triplets("A", A_vec, A_x.get(), A_i.get(), A_p.get()); if(verbose) std::cout << "A_nnz: " << A_nnz << std::endl; const OSQPInt q_size = static_cast(q_vec.size()); const OSQPInt l_size = static_cast(l_vec.size()); const OSQPInt u_size = static_cast(u_vec.size()); auto q_x = std::make_unique(q_size); auto l_x = std::make_unique(l_size); auto u_x = std::make_unique(u_size); set_qlu_data(q_x.get(), l_x.get(), u_x.get()); // Problem settings. OSQPSettings *settings = OSQPSettings_new(); CGAL_assertion(settings); // Structures. OSQPCscMatrix* A = OSQPCscMatrix_new(m, n, A_nnz, A_x.get(), A_i.get(), A_p.get()); OSQPCscMatrix* P = OSQPCscMatrix_new(n, n, P_nnz, P_x.get(), P_i.get(), P_p.get()); // Set solver settings. osqp_set_default_settings(settings); settings->eps_abs = eps_abs; settings->eps_rel = eps_rel; settings->verbose = verbose; OSQPSolver* solver = NULL; OSQPInt exitflag = osqp_setup(&solver, P, q_x.get(), A, l_x.get(), u_x.get(), m, n, settings); try { if (!exitflag) exitflag = osqp_solve(solver); const OSQPFloat* x = solver->solution->x; for (std::size_t i = 0; i < n; ++i) { const FT value{ x[i] }; *(++solution) = value; } } catch (std::exception& e) { std::cerr << "ERROR: OSQP solver has thrown an exception!" << std::endl; std::cerr << e.what() << std::endl; } osqp_cleanup(solver); if (A) OSQPCscMatrix_free(A); if (P) OSQPCscMatrix_free(P); if (settings) OSQPSettings_free(settings); return (exitflag == 0); } /// \endcond private: // Based on the code in scipy, function coo_tocsr() void set_matrix_from_triplets(const std::string /* name */, const std::vector& triplets, OSQPFloat *M_x, OSQPInt *M_i, OSQPInt *M_p) const { const std::size_t nnz = triplets.size(); // Compute the number of non-zero entries in each column of the sparse matrix A std::fill(M_p, M_p + n, 0); for(std::size_t k=0; k(triplets[k])]++; } // Fill M_p OSQPInt cumsum = 0; for(std::size_t j=0; j(std::get<1>(triplets[k])); const OSQPInt dest = M_p[col]; M_i[dest] = static_cast(std::get<0>(triplets[k])); M_x[dest] = OSQPFloat(CGAL::to_double(std::get<2>(triplets[k]))); M_p[col]++; } OSQPInt last = 0; for(std::size_t j=0; j<=n; ++j) { const OSQPInt tmp = M_p[j]; M_p[j] = last; last = tmp; } // std::cout << name + "_x: "; // for(std::size_t i=0; i