alpaqa 1.0.0a17
Nonconvex constrained optimization
Loading...
Searching...
No Matches
qpalm-adapter.cpp
Go to the documentation of this file.
4
5#include <qpalm/sparse.hpp>
6
7#include <cmath>
8#include <stdexcept>
9
10namespace alpaqa {
11
12namespace {
13
15
16// Check if the variable with the given index has bound constraints, i.e.
17// if not lowerbound == -inf and upperbound == +inf.
18bool is_bound(const Box<config_t> &C, index_t i) {
19 using std::isnan; // Assuming no NaN inputs
20 return isnan(C.lowerbound(i) + C.upperbound(i)) == 0;
21};
22
23/// Update the constraint matrix A, such that for each constraint C(i) with
24/// finite bounds, a row is inserted into A with a one in the i-th column.
25/// The newly added rows are added above the original rows of A.
26/// For example, if all constraints have finite bounds, the resulting matrix
27/// is @f$ \begin{pmatrix} I \\\hline A \end{pmatrix} @f$.
28///
29/// @pre Assumes that the user preallocated enough space for inserting these
30/// nonzero elements into A, and that A is compressed.
32 const Box<config_t> &C) {
33 using mindexvec = Eigen::Map<Eigen::VectorX<qpalm::sp_index_t>>;
34
35 auto m = static_cast<index_t>(A.nrow), n = static_cast<index_t>(A.ncol);
36 auto old_nnz = static_cast<index_t>(A.p[n]);
37
38 // Start by updating the outer pointer: for each active bound constraint,
39 // one nonzero has to be inserted at the beginning of the current column.
40 // To make space for this nonzero, all row indices and values of the current
41 // column and all following columns have to be shifted. In this loop, we
42 // already update the outer pointers to point to these shifted locations,
43 // without actually shifting the row indices and values yet.
44 // (This breaks the SparseMatrix invariants!)
45 mindexvec outer_ptrs{A.p, n + 1};
46 // Essentially perform outer_ptrs[1:n+1] += partial_sum(is_bound(C, 0:n))
47 index_t shift = 0;
48 for (index_t col = 0; col < n; ++col) {
49 shift += is_bound(C, col) ? 1 : 0;
50 outer_ptrs(col + 1) += shift;
51 }
52 // We now know how many variables were constrained, so we know the new
53 // number of nonzeros in the matrix, and we know how many rows to add.
55 // Shift down the entire matrix by changing the old row indices.
56 // (This breaks the SparseMatrix invariants!)
57 mindexvec{A.i, old_nnz}.array() += num_bound_constr;
58 // Now we need to make space in the row indices and value arrays, so we can
59 // actually insert the nonzero elements of the rows we are adding.
60 // Start with the last column, so we don't overwrite any data when shifting.
61 // Throughout the iteration, the `shift` variable keeps track of how many
62 // nonzeros need to be added to the current column and all previous columns.
63 // The `prev_shift` variable keeps track of how many nonzeros need to be
64 // added to all previous columns (excluding the current column). Note that
65 // we already destroyed the original outer pointers, which we need now to
66 // iterate over the original matrix. Luckily, we can recover them using
67 // simple arithmetic, reversing the forward loop above.
68 for (index_t col = n; col-- > 0;) {
69 // Check if we need to add a nonzero in this column.
70 index_t insert_nz = is_bound(C, col) ? 1 : 0;
71 // First recover the original outer pointer by undoing the shift.
75 // Then we can use the outer pointer to get the row indices and values.
78 // Shift over all row indices and values to make space to insert new
79 // `shift` rows at the beginning of this column.
80 std::shift_right(inners_ptr, inners_end + shift, shift);
81 std::shift_right(values_ptr, values_end + shift, shift);
82 // Set the row index and value of the row we just inserted.
83 if (insert_nz) {
84 inners_ptr[shift - 1] = shift - 1;
85 values_ptr[shift - 1] = 1;
86 }
87 // Keep track of how much we should shift the previous column.
89 }
90 // Finally, update the number of rows and nonzeros of the matrix.
91 A.nrow = m + num_bound_constr;
92 A.nzmax = old_nnz + num_bound_constr;
93}
94
95/// For each constraint C(i) with finite bounds, insert these bounds into b(i),
96/// followed by all bounds D, shifted by g.
98 const Box<config_t> &D, crvec g) {
99 const auto n = C.lowerbound.size(), m = D.lowerbound.size();
100 index_t c = 0;
101 for (index_t i = 0; i < n; ++i) {
102 if (is_bound(C, i)) {
103 b.lowerbound(c) = C.lowerbound(i);
104 b.upperbound(c) = C.upperbound(i);
105 ++c;
106 }
107 }
108 assert(c == static_cast<length_t>(b.lowerbound.size() - m));
109 b.lowerbound.segment(c, m) = D.lowerbound - g;
110 b.upperbound.segment(c, m) = D.upperbound - g;
111}
112
114 switch (symmetry) {
116 case sparsity::Symmetry::Upper: return UPPER;
117 case sparsity::Symmetry::Lower: return LOWER;
118 default: throw std::invalid_argument("Invalid symmetry");
119 }
120}
121
122} // namespace
123
124OwningQPALMData
127
128 // Get the dimensions of the problem matrices
129 const auto n = problem.get_n(), m = problem.get_m();
130
131 // Dummy data to evaluate Hessian and Jacobian
132 vec x = vec::Zero(n), y = vec::Zero(m), g(m);
133
134 // Construct QPALM problem
136
140 { // Evaluate cost Hessian
142 SparsityConv sp_Q{sp_Q_orig, {.order = SparseCSC::SortedRows}};
143 auto nnz_Q = static_cast<qpalm::sp_index_t>(sp_Q.get_sparsity().nnz());
144 auto symm = convert_symmetry(sp_Q.get_sparsity().symmetry);
145 qp.sto->Q = qpalm::ladel_sparse_create(n, n, nnz_Q, symm);
146 qp.Q = qp.sto->Q.get();
147 // Copy sparsity pattern
148 std::ranges::copy(sp_Q.get_sparsity().inner_idx, qp.Q->i);
149 std::ranges::copy(sp_Q.get_sparsity().outer_ptr, qp.Q->p);
150 // Get actual values
151 mvec H_values{qp.Q->x, nnz_Q};
152 auto eval_h = [&](rvec v) { problem.eval_hess_L(x, y, 1, v); };
153 sp_Q.convert_values(eval_h, H_values);
154 }
155 { // Evaluate constraints Jacobian
157 SparsityConv sp_A{sp_A_orig, {.order = SparseCSC::SortedRows}};
158 auto nnz_A = static_cast<qpalm::sp_index_t>(sp_A.get_sparsity().nnz());
159 auto symm = convert_symmetry(sp_A.get_sparsity().symmetry);
160 qp.sto->A = qpalm::ladel_sparse_create(m, n, nnz_A + n, symm);
161 qp.A = qp.sto->A.get();
162 // Copy sparsity pattern
163 std::ranges::copy(sp_A.get_sparsity().inner_idx, qp.A->i);
164 std::ranges::copy(sp_A.get_sparsity().outer_ptr, qp.A->p);
165 // Get actual values
166 mvec J_values{qp.A->x, nnz_A};
167 auto eval_j = [&](rvec v) { problem.eval_jac_g(x, v); };
168 sp_A.convert_values(eval_j, J_values);
169 // Add the bound constraints
170 add_bound_constr_to_constr_matrix(*qp.A, problem.get_box_C());
171 }
172 { // Evaluate constraints
173 problem.eval_g(x, g);
174 }
175 { // Evaluate cost and cost gradient
176 qp.sto->q.resize(n);
177 qp.q = qp.sto->q.data();
178 qp.c = problem.eval_f_grad_f(x, qp.sto->q);
179 }
180 { // Combine bound constraints
181 qp.sto->b.lowerbound.resize(qp.A->nrow);
182 qp.sto->b.upperbound.resize(qp.A->nrow);
183 qp.bmin = qp.sto->b.lowerbound.data();
184 qp.bmax = qp.sto->b.upperbound.data();
185 // Combine bound constraints and linear constraints
186 auto &&C = problem.get_box_C(), &&D = problem.get_box_D();
187 combine_bound_constr(qp.sto->b, C, D, g);
188 }
189 qp.m = static_cast<size_t>(qp.A->nrow);
190 qp.n = static_cast<size_t>(qp.Q->nrow);
191 return qp;
192}
193
194} // namespace alpaqa
The main polymorphic minimization problem interface.
const Box & get_box_D() const
[Optional] Get the rectangular constraint set of the general constraint function, .
void eval_jac_g(crvec x, rvec J_values) const
[Optional] Function that evaluates the nonzero values of the Jacobian matrix of the constraints,
Sparsity get_jac_g_sparsity() const
[Optional] Function that returns (a view of) the sparsity pattern of the Jacobian of the constraints.
length_t get_n() const
[Required] Number of decision variables.
Sparsity get_hess_L_sparsity() const
[Optional] Function that returns (a view of) the sparsity pattern of the Hessian of the Lagrangian.
length_t get_m() const
[Required] Number of constraints.
real_t eval_f_grad_f(crvec x, rvec grad_fx) const
[Optional] Evaluate both and its gradient, .
void eval_g(crvec x, rvec gx) const
[Required] Function that evaluates the constraints,
void eval_hess_L(crvec x, crvec y, real_t scale, rvec H_values) const
[Optional] Function that evaluates the nonzero values of the Hessian of the Lagrangian,
const Box & get_box_C() const
[Optional] Get the rectangular constraint set of the decision variables, .
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:77
void combine_bound_constr(Box< config_t > &b, const Box< config_t > &C, const Box< config_t > &D, crvec g)
For each constraint C(i) with finite bounds, insert these bounds into b(i), followed by all bounds D,...
void add_bound_constr_to_constr_matrix(ladel_sparse_matrix &A, const Box< config_t > &C)
Update the constraint matrix A, such that for each constraint C(i) with finite bounds,...
int convert_symmetry(sparsity::Symmetry symmetry)
bool is_bound(const Box< config_t > &C, index_t i)
Symmetry
Describes the symmetry of matrices.
Definition sparsity.hpp:12
@ Upper
Symmetric, upper-triangular part is stored.
@ Lower
Symmetric, lower-triangular part is stored.
Converts one matrix storage format to another.
typename Conf::mvec mvec
Definition config.hpp:89
typename Conf::mindexvec mindexvec
Definition config.hpp:108
OwningQPALMData build_qpalm_problem(const TypeErasedProblem< EigenConfigd > &problem)
typename Conf::real_t real_t
Definition config.hpp:86
typename Conf::index_t index_t
Definition config.hpp:104
typename Conf::length_t length_t
Definition config.hpp:103
constexpr const auto inf
Definition config.hpp:112
typename Conf::rvec rvec
Definition config.hpp:91
typename Conf::crvec crvec
Definition config.hpp:92
typename Conf::vec vec
Definition config.hpp:88
Double-precision double configuration.
Definition config.hpp:174
Sparse compressed-column structure (CCS or CSC).
Definition sparsity.hpp:29
Stores any of the supported sparsity patterns.
Definition sparsity.hpp:106