alpaqa dll
Nonconvex constrained optimization
Loading...
Searching...
No Matches
lin-constr-converter.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <algorithm>
8#include <cstddef>
9#include <iterator>
10#include <span>
11
12namespace alpaqa {
13
14template <Config Conf, class IndexT, class StorageIndexT>
16 using config_t = Conf;
17 using real_t = typename config_t::real_t;
18 using index_t = IndexT;
19 using storage_index_t = StorageIndexT;
20
21 struct SparseView {
23 std::span<index_t> inner_idx;
24 std::span<storage_index_t> outer_ptr;
25 std::span<real_t> values;
26 };
27
28 /// Check if the variable with the given index has bound constraints, i.e.
29 /// if not lower == -inf and upper == +inf.
30 static bool is_bound(std::span<const real_t> lbx,
31 std::span<const real_t> ubx, size_t i) {
32 using std::isnan; // Assuming no NaN inputs
33 return isnan(lbx[i] + ubx[i]) == 0;
34 }
35 static bool is_bound(const sets::Box<config_t> &C,
36 typename config_t::index_t i) {
37 return is_bound(as_span(C.lower), as_span(C.upper),
38 static_cast<size_t>(i));
39 }
40
41 static index_t count_bounds(std::span<const real_t> lbx,
42 std::span<const real_t> ubx) {
43 auto n = static_cast<index_t>(lbx.size());
44 assert(static_cast<index_t>(ubx.size()) == n);
45 index_t shift = 0;
46 for (index_t col = 0; col < n; ++col)
47 shift += is_bound(lbx, ubx, col) ? 1 : 0;
48 return shift;
49 }
50
52 return count_bounds(as_span(C.lower), as_span(C.upper));
53 }
54
56 std::span<const real_t> lbx,
57 std::span<const real_t> ubx) {
58 auto n = A.cols(), m = A.rows();
59 index_t shift = count_bounds(lbx, ubx);
60 mat<config_t> B(shift + m, n);
61 B << mat<config_t>::Zero(shift, n), A;
62 shift = 0;
63 for (index_t col = 0; col < n; ++col)
64 if (is_bound(lbx, ubx, col))
65 B(shift++, col) = 1;
66 using std::swap;
67 swap(A, B);
68 }
74
75 static void
77 std::span<const real_t> lbx,
78 std::span<const real_t> ubx) {
79 index_t n = A.cols(), shift = A.rows() - n_row;
80 // Shift down the entire matrix in-place
81 for (index_t col = n; col-- > 0;)
82 std::ranges::reverse_copy(
83 A.col(col).topRows(n_row),
84 std::reverse_iterator{A.data() + (col + 1) * A.rows()});
85 // Add ones in the top block
86 A.topRows(shift).setZero();
87 shift = 0;
88 for (index_t col = 0; col < n; ++col)
89 if (is_bound(lbx, ubx, col))
90 A(shift++, col) = 1;
91 assert(shift == A.rows() - n_row);
92 }
93 static void
99
101 index_t n_row, index_t n_col, rvec<config_t> A,
102 std::span<const real_t> lbx, std::span<const real_t> ubx) {
103 assert(A.size() % n_col == 0);
104 index_t tot_rows = A.size() / n_col;
105 index_t shift = tot_rows - n_row;
106 // Shift down the entire matrix in-place
107 auto A_old = A.topRows(n_row * n_col).reshaped(n_row, n_col);
108 auto A_new = A.reshaped(tot_rows, n_col);
109 for (index_t col = n_col; col-- > 0;)
110 std::ranges::reverse_copy(
111 A_old.col(col).topRows(n_row),
112 std::reverse_iterator{A.data() + (col + 1) * A_new.rows()});
113 // Add ones in the top block
114 A_new.topRows(shift).setZero();
115 shift = 0;
116 for (index_t col = 0; col < n_col; ++col)
117 if (is_bound(lbx, ubx, col))
118 A_new(shift++, col) = 1;
119 assert(shift == A_new.rows() - n_row);
120 }
121 static void
128
129 /// Update the constraint matrix A, such that for each constraint C(i) with
130 /// finite bounds, a row is inserted into A with a one in the i-th column.
131 /// The newly added rows are added above the original rows of A.
132 /// For example, if all constraints have finite bounds, the resulting matrix
133 /// is @f$ \begin{pmatrix} I \\\hline A \end{pmatrix} @f$.
134 ///
135 /// @pre Assumes that the user preallocated enough space for inserting
136 /// these nonzero elements into A, and that A is compressed.
137 static void add_box_constr_to_constr_matrix(SparseView &A,
138 std::span<const real_t> lbx,
139 std::span<const real_t> ubx);
145
146 /// For each constraint lbx(i)/ubx(i) with finite bounds, insert these
147 /// bounds into new_lbg(i)/new_ubg(i), followed by all bounds lbg(i)/ubg(i),
148 /// shifted by the constant vector -g₀.
149 static void combine_bound_constr(std::span<const real_t> lbx,
150 std::span<const real_t> ubx,
151 std::span<const real_t> lbg,
152 std::span<const real_t> ubg,
153 std::span<real_t> new_lbg,
154 std::span<real_t> new_ubg,
155 std::span<const real_t> g0);
157 const sets::Box<config_t> &D,
158 sets::Box<config_t> &new_D,
159 typename config_t::crvec g0) {
161 as_span(D.lower), as_span(D.upper),
162 as_span(new_D.lower), as_span(new_D.upper),
163 as_span(g0));
164 }
166 const sets::Box<config_t> &D,
167 std::span<real_t> new_lbg,
168 std::span<real_t> new_ubg,
169 typename config_t::crvec g0) {
171 as_span(D.lower), as_span(D.upper), new_lbg,
172 new_ubg, as_span(g0));
173 }
174};
175
176template <Config Conf, class IndexT, class StorageIndexT>
178 add_box_constr_to_constr_matrix(SparseView &A, std::span<const real_t> lbx,
179 std::span<const real_t> ubx) {
180 auto n = static_cast<size_t>(A.ncol);
181 assert(A.outer_ptr.size() == static_cast<size_t>(n) + 1);
182 auto old_nnz = A.outer_ptr[n];
183
184 // Start by updating the outer pointer: for each active bound constraint,
185 // one nonzero has to be inserted at the beginning of the current column.
186 // To make space for this nonzero, all row indices and values of the current
187 // column and all following columns have to be shifted. In this loop, we
188 // already update the outer pointers to point to these shifted locations,
189 // without actually shifting the row indices and values yet.
190 // (This breaks the SparseMatrix invariants!)
191 storage_index_t shift = 0;
192 // Essentially perform outer_ptrs[1:n+1] += partial_sum(is_bound(C, 0:n))
193 for (size_t col = 0; col < n; ++col) {
194 shift += is_bound(lbx, ubx, col) ? 1 : 0;
195 A.outer_ptr[col + 1] += shift;
196 }
197 assert(A.inner_idx.size() >= static_cast<size_t>(old_nnz + shift));
198 // We now know how many variables were constrained, so we know the new
199 // number of nonzeros in the matrix, and we know how many rows to add.
200 auto num_bound_constr = static_cast<index_t>(shift);
201 // Shift down the entire matrix by changing the old row indices.
202 // (This breaks the SparseMatrix invariants!)
203 for (index_t &i : A.inner_idx.first(static_cast<size_t>(old_nnz)))
204 i += num_bound_constr;
205 // Now we need to make space in the row indices and value arrays, so we can
206 // actually insert the nonzero elements of the rows we are adding.
207 // Start with the last column, so we don't overwrite any data when shifting.
208 // Throughout the iteration, the `shift` variable keeps track of how many
209 // nonzeros need to be added to the current column and all previous columns.
210 // The `prev_shift` variable keeps track of how many nonzeros need to be
211 // added to all previous columns (excluding the current column). Note that
212 // we already destroyed the original outer pointers, which we need now to
213 // iterate over the original matrix. Luckily, we can recover them using
214 // simple arithmetic, reversing the forward loop above.
215 for (size_t col = n; col-- > 0;) {
216 // Check if we need to add a nonzero in this column.
217 storage_index_t insert_nz = is_bound(lbx, ubx, col) ? 1 : 0;
218 // First recover the original outer pointer by undoing the shift.
219 storage_index_t prev_shift = shift - insert_nz;
220 storage_index_t next_outer = A.outer_ptr[col + 1] - shift;
221 storage_index_t curr_outer = A.outer_ptr[col] - prev_shift;
222 // Then we can use the outer pointer to get the row indices and values.
223 auto inners_ptr = A.inner_idx.begin() + curr_outer,
224 inners_end = A.inner_idx.begin() + next_outer;
225 auto values_ptr = A.values.begin() + curr_outer,
226 values_end = A.values.begin() + next_outer;
227 // Shift over all row indices and values to make space to insert new
228 // `shift` rows at the beginning of this column.
229 std::shift_right(inners_ptr, inners_end + shift, shift);
230 std::shift_right(values_ptr, values_end + shift, shift);
231 // Set the row index and value of the row we just inserted.
232 if (insert_nz) {
233 inners_ptr[shift - 1] = static_cast<index_t>(shift) - 1;
234 values_ptr[shift - 1] = 1;
235 }
236 // Keep track of how much we should shift the previous column.
237 shift = prev_shift;
238 }
239 // Finally, update the number of rows of the matrix.
240 A.nrow += num_bound_constr;
241}
242
243template <Config Conf, class IndexT, class StorageIndexT>
245 std::span<const real_t> lbx, std::span<const real_t> ubx,
246 std::span<const real_t> lbg, std::span<const real_t> ubg,
247 std::span<real_t> new_lbg, std::span<real_t> new_ubg,
248 std::span<const real_t> g0) {
249 const auto n = lbx.size(), m [[maybe_unused]] = lbg.size();
250 assert(ubx.size() == n);
251 assert(ubg.size() == m);
252 assert(g0.size() == m);
253 size_t c = 0;
254 for (size_t i = 0; i < n; ++i) {
255 if (is_bound(lbx, ubx, i)) {
256 new_lbg[c] = lbx[i];
257 new_ubg[c] = ubx[i];
258 ++c;
259 }
260 }
261 assert(c + m == new_lbg.size());
262 std::ranges::transform(lbg, g0, &new_lbg[c], std::minus{});
263 std::ranges::transform(ubg, g0, &new_ubg[c], std::minus{});
264}
265
266} // namespace alpaqa
typename Conf::mat mat
Definition config.hpp:93
typename Conf::rmat rmat
Definition config.hpp:96
auto as_span(Eigen::DenseBase< Derived > &v)
Convert an Eigen vector view to a std::span.
Definition span.hpp:21
typename Conf::rvec rvec
Definition config.hpp:91
static void combine_bound_constr(const sets::Box< config_t > &C, const sets::Box< config_t > &D, std::span< real_t > new_lbg, std::span< real_t > new_ubg, typename config_t::crvec g0)
static void add_box_constr_to_constr_matrix(mat< config_t > &A, const sets::Box< config_t > &C)
typename config_t::real_t real_t
static index_t count_bounds(const sets::Box< config_t > &C)
static void add_box_constr_to_constr_matrix_inplace(index_t n_row, rmat< config_t > A, std::span< const real_t > lbx, std::span< const real_t > ubx)
static void add_box_constr_to_constr_matrix_inplace_vec(index_t n_row, index_t n_col, rvec< config_t > A, const sets::Box< config_t > &C)
static void add_box_constr_to_constr_matrix(SparseView &A, const sets::Box< config_t > &C)
static index_t count_bounds(std::span< const real_t > lbx, std::span< const real_t > ubx)
static bool is_bound(std::span< const real_t > lbx, std::span< const real_t > ubx, size_t i)
Check if the variable with the given index has bound constraints, i.e.
static void add_box_constr_to_constr_matrix_inplace_vec(index_t n_row, index_t n_col, rvec< config_t > A, std::span< const real_t > lbx, std::span< const real_t > ubx)
static void add_box_constr_to_constr_matrix_inplace(index_t n_row, rmat< config_t > A, const sets::Box< config_t > &C)
static void combine_bound_constr(std::span< const real_t > lbx, std::span< const real_t > ubx, std::span< const real_t > lbg, std::span< const real_t > ubg, std::span< real_t > new_lbg, std::span< real_t > new_ubg, std::span< const real_t > g0)
For each constraint lbx(i)/ubx(i) with finite bounds, insert these bounds into new_lbg(i)/new_ubg(i),...
static bool is_bound(const sets::Box< config_t > &C, typename config_t::index_t i)
static void combine_bound_constr(const sets::Box< config_t > &C, const sets::Box< config_t > &D, sets::Box< config_t > &new_D, typename config_t::crvec g0)
static void add_box_constr_to_constr_matrix(mat< config_t > &A, std::span< const real_t > lbx, std::span< const real_t > ubx)