smooth_feedback
Control and estimation on Lie groups
Loading...
Searching...
No Matches
mesh.hpp
Go to the documentation of this file.
1// Copyright (C) 2022 Petter Nilsson. MIT License.
2
3#pragma once
4
10#include <ranges>
11#include <span>
12#include <vector>
13
14#include <Eigen/Core>
15#include <Eigen/LU>
16#include <smooth/detail/traits.hpp>
17#include <smooth/detail/utils.hpp>
18#include <smooth/polynomial/quadrature.hpp>
19
20#include "smooth/feedback/traits.hpp"
22
23namespace smooth::feedback {
24
25using smooth::utils::zip;
26
27using std::views::iota, std::views::drop, std::views::reverse, std::views::take, std::views::transform,
28 std::views::join;
29
30namespace detail {
31
35template<std::size_t K, std::size_t I = 8>
36constexpr std::pair<std::array<double, K + 1>, std::array<double, K + 1>> lgr_plus_one()
37{
38 auto lgr_norm = ::smooth::lgr_nodes<K, I>();
39
40 std::array<double, K + 1> ns, ws;
41 for (auto i = 0u; i < K; ++i) {
42 ns[i] = lgr_norm.first[i];
43 ws[i] = lgr_norm.second[i];
44 }
45 ns[K] = 1;
46 ws[K] = 0;
47 return {ns, ws};
48}
49
50} // namespace detail
51
60template<std::size_t _Kmin = 5, std::size_t _Kmax = 10>
61 requires(_Kmin <= _Kmax)
62class Mesh
63{
64 using MatMap = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>;
65
66public:
68 static constexpr auto Kmin = _Kmin;
70 static constexpr auto Kmax = _Kmax;
71
83 inline Mesh() : intervals_(1, Interval{.K = Kmin, .tau0 = 0.}) {}
84
93 inline Mesh(const std::size_t n, const std::size_t k = Kmin)
94 {
95 assert(Kmin <= k && k <= Kmax + 1);
96
97 if (n < 2) {
98 intervals_.emplace_back(k, 0.);
99 } else {
100 const double dx = 1. / static_cast<double>(n);
101 intervals_.reserve(n);
102 for (std::size_t i = 0; i < n; ++i) { intervals_.emplace_back(k, static_cast<double>(i) * dx); }
103 }
104 }
105
109 inline std::size_t N_ivals() const { return intervals_.size(); }
110
114 inline std::size_t N_colloc() const
115 {
116 return std::accumulate(
117 intervals_.begin(), intervals_.end(), 0u, [](std::size_t curr, const auto & x) { return curr + x.K; });
118 }
119
126 inline std::size_t N_colloc_ival(std::size_t i) const
127 {
128 assert(i < intervals_.size());
129 return intervals_[i].K;
130 }
131
145 inline void refine_ph(std::size_t i, std::size_t D)
146 {
147 assert(i < intervals_.size());
148 if (D > Kmax || intervals_[i].K > Kmax) {
149 // refine by splitting interval into n intervals, each with degree Kmin_
150 std::size_t n = std::max<std::size_t>(2u, (D + Kmin - 1) / Kmin);
151
152 const double tau0 = intervals_[i].tau0;
153 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
154 const double taum = (tauf - tau0) / static_cast<double>(n);
155
156 while (n-- > 1) {
157 intervals_.insert(
158 std::next(intervals_.begin(), static_cast<intptr_t>(i + 1)),
159 Interval{.K = Kmin, .tau0 = tau0 + static_cast<double>(n) * taum});
160 }
161 } else if (D < intervals_[i].K) {
162 return;
163 } else if (D <= Kmax) {
164 // refine by increasing degree in interval
165 intervals_[i].K = D;
166 }
167 }
168
174 inline void refine_errors(std::ranges::sized_range auto && errs, double target_err)
175 {
176 const auto N = N_ivals();
177
178 assert(N == std::size_t(std::ranges::size(errs)));
179
180 for (const auto & [i, e] : zip(iota(0u, N) | reverse, errs | reverse)) {
181
182 const auto Ki = N_colloc_ival(i);
183
184 if (e > target_err) {
185 const auto Ktarget = Ki + std::lround(std::log(e / target_err) / std::log(Ki) + 1);
186 refine_ph(i, Ktarget);
187 }
188 }
189 }
190
196 inline void set_N_colloc_ival(std::size_t i, std::size_t K)
197 {
198 assert(Kmin <= K);
199 assert(K <= Kmax + 1);
200 intervals_[i].K = K;
201 }
202
208 inline auto interval_nodes(std::size_t i) const
209 {
210 const std::size_t k = intervals_[i].K;
211
212 assert(Kmin <= k && k <= Kmax + 1);
213
214 std::span<const double> sp;
215
216 utils::static_for<Kmax + 2 - Kmin>([&](auto ivar) {
217 static constexpr auto K = Kmin + ivar;
218 if (K == k) {
219 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
220
221 sp = std::span<const double>(nw_ext_s.first.data(), k + 1);
222 }
223 });
224
225 const double tau0 = intervals_[i].tau0;
226 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
227 const double al = (tauf - tau0) / 2;
228
229 return transform(std::move(sp), [tau0, al](double d) -> double { return tau0 + al * (d + 1); });
230 }
231
239 inline auto all_nodes() const
240 {
241 const auto n_ivals = N_ivals();
242 auto all_views = iota(0u, n_ivals) | transform([this, n_ivals = n_ivals](auto i) {
243 const auto n_ival = N_colloc_ival(i);
244 const auto n_take = static_cast<int64_t>(i + 1 < n_ivals ? n_ival : n_ival + 1);
245 return interval_nodes(i) | take(n_take);
246 });
247
248 return join(std::move(all_views));
249 }
250
256 inline auto interval_weights(std::size_t i) const
257 {
258 const std::size_t k = intervals_[i].K;
259
260 assert(Kmin <= k && k <= Kmax + 1);
261
262 std::span<const double> sp;
263
264 utils::static_for<Kmax + 2 - Kmin>([&](auto ivar) {
265 static constexpr auto K = Kmin + ivar;
266 if (K == k) {
267 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
268
269 sp = std::span<const double>(nw_ext_s.second.data(), k + 1);
270 }
271 });
272
273 const double tau0 = intervals_[i].tau0;
274 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
275 const double al = (tauf - tau0) / 2;
276
277 return transform(std::move(sp), [al](double d) -> double { return al * d; });
278 }
279
287 inline auto all_weights() const
288 {
289 const auto n_ivals = N_ivals();
290 auto all_views = iota(0u, n_ivals) | transform([this, n_ivals = n_ivals](auto i) {
291 const auto n_ival = N_colloc_ival(i);
292 const int64_t n_take = i + 1 < n_ivals ? n_ival : n_ival + 1;
293 return interval_weights(i) | take(n_take);
294 });
295
296 return join(std::move(all_views));
297 }
298
312 inline Eigen::MatrixXd interval_diffmat(std::size_t i) const
313 {
314 const std::size_t k = intervals_[i].K;
315
316 const double tau0 = intervals_[i].tau0;
317 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
318
319 Eigen::MatrixXd ret(k + 1, k);
320
321 utils::static_for<Kmax + 2 - Kmin>([&](auto ivar) {
322 static constexpr auto K = Kmin + ivar;
323 if (K == k) {
324 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
325 static constexpr auto B_ext_s = lagrange_basis<K>(nw_ext_s.first);
326 static constexpr auto D_ext_s =
327 polynomial_basis_derivatives<K, K + 1>(B_ext_s, nw_ext_s.first).template block<K + 1, K>(0, 0);
328 ret = MatMap(D_ext_s[0].data(), k + 1, k);
329 }
330 });
331
332 ret *= 2. / (tauf - tau0);
333 return ret;
334 }
335
343 inline std::pair<double, MatMap> interval_diffmat_unscaled(std::size_t i) const
344 {
345 const std::size_t k = intervals_[i].K;
346
347 const double tau0 = intervals_[i].tau0;
348 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
349
350 MatMap ret(nullptr, 0, 0);
351
352 utils::static_for<Kmax + 2 - Kmin>([&](auto ivar) {
353 static constexpr auto K = Kmin + ivar;
354 if (K == k) {
355 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
356 static constexpr auto B_ext_s = lagrange_basis<K>(nw_ext_s.first);
357 static constexpr auto D_ext_s =
358 polynomial_basis_derivatives<K, K + 1>(B_ext_s, nw_ext_s.first).template block<K + 1, K>(0, 0);
359 // Eigen maps don't have copy constructors so use placement new
360 new (&ret) MatMap(D_ext_s[0].data(), k + 1, k);
361 }
362 });
363
364 return {2. / (tauf - tau0), ret};
365 }
366
387 inline Eigen::MatrixXd interval_intmat(std::size_t i) const
388 {
389 const std::size_t k = intervals_[i].K;
390 return interval_diffmat(i).block(1, 0, k, k).inverse();
391 }
392
396 inline std::size_t interval_find(double t) const
397 {
398 if (t < 0) { return 0; }
399 if (t > 1) { return intervals_.size() - 1; }
400 auto it =
401 utils::binary_interval_search(intervals_, t, [](const auto & ival, double _t) { return ival.tau0 <=> _t; });
402 if (it != intervals_.end()) { return std::distance(intervals_.begin(), it); }
403 return 0;
404 }
405
410 {
411 for (auto & ival : intervals_) { ival.K = std::min(ival.K + 1, Kmax + 1); }
412 }
413
418 {
419 for (auto & ival : intervals_) { ival.K = std::max(ival.K - 1, Kmin); }
420 }
421
432 template<smooth::RnType RetT>
433 RetT eval(double t, std::ranges::range auto && r, std::size_t p = 0, bool extend = true) const
434 {
435 const std::size_t ival = interval_find(t);
436 const std::size_t k = intervals_[ival].K;
437
438 const double tau0 = intervals_[ival].tau0;
439 const double tauf = ival + 1 < intervals_.size() ? intervals_[ival + 1].tau0 : 1.;
440
441 const double u = 2 * (t - tau0) / (tauf - tau0) - 1;
442
443 int64_t N_before = 0;
444 for (auto i = 0u; i < ival; ++i) { N_before += intervals_[i].K; }
445
446 // initialize output variable
447 RetT ret = RetT::Zero(std::ranges::begin(r)->size());
448
449 utils::static_for<Kmax + 2 - Kmin>([&](auto i) {
450 static constexpr auto K = Kmin + i;
451 if (K == k) {
452 if (extend || ival + 1 < intervals_.size()) {
453 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
454 static constexpr auto B_ext_s = lagrange_basis<K>(nw_ext_s.first); // K+1 x K+1
455 const auto U = monomial_derivative<K>(u, p); // 1 x K+1
456 const auto W = U * B_ext_s; // 1 x K+1
457
458 for (const auto & [w, v] : zip(std::span(W[0].data(), k + 1), r | drop(N_before))) { ret += w * v; }
459 } else {
460 static constexpr auto nw_s = lgr_nodes<K>();
461 static constexpr auto B_s = lagrange_basis<K - 1>(nw_s.first); // K x K
462 const auto U = monomial_derivative<K - 1>(u, p); // 1 x K
463 const auto W = U * B_s; // 1 x K
464
465 for (const auto & [w, v] : zip(std::span(W[0].data(), k), r | drop(N_before))) { ret += w * v; }
466 }
467 }
468 });
469
470 return ret;
471 }
472
473private:
474 struct Interval
475 {
477 std::size_t K;
479 double tau0;
480 };
481
483 std::vector<Interval> intervals_;
484};
485
487template<typename T>
488concept MeshType = traits::is_specialization_of_sizet_v<std::decay_t<T>, Mesh>;
489
490} // namespace smooth::feedback
Collocation mesh of interval [0, 1].
Definition: mesh.hpp:63
std::size_t N_colloc() const
Number of collocation points in mesh.
Definition: mesh.hpp:114
auto interval_nodes(std::size_t i) const
Interval nodes (as range of doubles).
Definition: mesh.hpp:208
void decrease_degrees()
Decrease the degree of all intervals by one (down to minimal degree Kmin)
Definition: mesh.hpp:417
auto all_weights() const
Weights (as range of doubles)
Definition: mesh.hpp:287
std::pair< double, MatMap > interval_diffmat_unscaled(std::size_t i) const
Interval differentiation matrix (unscaled).
Definition: mesh.hpp:343
void set_N_colloc_ival(std::size_t i, std::size_t K)
Set the number of collocation points in interval i to K.
Definition: mesh.hpp:196
auto all_nodes() const
Nodes (as range of doubles).
Definition: mesh.hpp:239
std::size_t N_ivals() const
Number of intervals in mesh.
Definition: mesh.hpp:109
Eigen::MatrixXd interval_diffmat(std::size_t i) const
Interval differentiation matrix w.r.t. [0, 1] timescale.
Definition: mesh.hpp:312
std::size_t N_colloc_ival(std::size_t i) const
Number of collocation points in interval i.
Definition: mesh.hpp:126
RetT eval(double t, std::ranges::range auto &&r, std::size_t p=0, bool extend=true) const
Evaluate a function.
Definition: mesh.hpp:433
void increase_degrees()
Increase the degree of all intervals by one (up to maximal degree Kmax + 1)
Definition: mesh.hpp:409
Eigen::MatrixXd interval_intmat(std::size_t i) const
Interval integration matrix w.r.t. [0, 1] timescale.
Definition: mesh.hpp:387
auto interval_weights(std::size_t i) const
Interval weights (as range of doubles)
Definition: mesh.hpp:256
void refine_ph(std::size_t i, std::size_t D)
Refine interval using the ph strategy.
Definition: mesh.hpp:145
Mesh()
Create a mesh consisting of a single interval [0, 1].
Definition: mesh.hpp:83
Mesh(const std::size_t n, const std::size_t k=Kmin)
Create a mesh consisting of a n intervals of equal size over [0, 1].
Definition: mesh.hpp:93
void refine_errors(std::ranges::sized_range auto &&errs, double target_err)
Refine intervals in mesh to satisfy a target error criterion.
Definition: mesh.hpp:174
std::size_t interval_find(double t) const
Find interval index that contains t.
Definition: mesh.hpp:396
MeshType is a specialization of Mesh.
Definition: mesh.hpp:488
constexpr std::pair< std::array< double, K+1 >, std::array< double, K+1 > > lgr_plus_one()
Legendre-Gauss-Radau nodes including an extra node at +1.
Definition: mesh.hpp:36
Sparse matrix utilities.