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