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