16#include <smooth/detail/traits.hpp>
17#include <smooth/detail/utils.hpp>
18#include <smooth/polynomial/quadrature.hpp>
20#include "smooth/feedback/traits.hpp"
23namespace smooth::feedback {
25using smooth::utils::zip;
27using std::views::iota, std::views::drop, std::views::reverse, std::views::take, std::views::transform,
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()
38 auto lgr_norm = ::smooth::lgr_nodes<K, I>();
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];
60template<std::
size_t _Kmin = 5, std::
size_t _Kmax = 10>
61 requires(_Kmin <= _Kmax)
64 using MatMap = Eigen::Map<
const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>;
68 static constexpr auto Kmin = _Kmin;
70 static constexpr auto Kmax = _Kmax;
83 inline Mesh() : intervals_(1, Interval{.K = Kmin, .tau0 = 0.}) {}
93 inline Mesh(
const std::size_t n,
const std::size_t k = Kmin)
95 assert(Kmin <= k && k <= Kmax + 1);
98 intervals_.emplace_back(k, 0.);
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); }
109 inline std::size_t
N_ivals()
const {
return intervals_.size(); }
116 return std::accumulate(
117 intervals_.begin(), intervals_.end(), 0u, [](std::size_t curr,
const auto & x) { return curr + x.K; });
128 assert(i < intervals_.size());
129 return intervals_[i].K;
147 assert(i < intervals_.size());
148 if (D > Kmax || intervals_[i].K > Kmax) {
150 std::size_t n = std::max<std::size_t>(2u, (D + Kmin - 1) / Kmin);
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);
158 std::next(intervals_.begin(),
static_cast<intptr_t
>(i + 1)),
159 Interval{.K = Kmin, .tau0 = tau0 +
static_cast<double>(n) * taum});
161 }
else if (D < intervals_[i].K) {
163 }
else if (D <= Kmax) {
174 inline void refine_errors(std::ranges::sized_range
auto && errs,
double target_err)
176 const auto N = N_ivals();
178 assert(N == std::size_t(std::ranges::size(errs)));
180 for (
const auto & [i, e] : zip(iota(0u, N) | reverse, errs | reverse)) {
182 const auto Ki = N_colloc_ival(i);
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);
199 assert(K <= Kmax + 1);
210 const std::size_t k = intervals_[i].K;
212 assert(Kmin <= k && k <= Kmax + 1);
214 std::span<const double> sp;
216 utils::static_for<Kmax + 2 - Kmin>([&](
auto ivar) {
217 static constexpr auto K = Kmin + ivar;
219 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
221 sp = std::span<const double>(nw_ext_s.first.data(), k + 1);
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;
229 return transform(std::move(sp), [tau0, al](
double d) ->
double {
return tau0 + al * (d + 1); });
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);
248 return join(std::move(all_views));
258 const std::size_t k = intervals_[i].K;
260 assert(Kmin <= k && k <= Kmax + 1);
262 std::span<const double> sp;
264 utils::static_for<Kmax + 2 - Kmin>([&](
auto ivar) {
265 static constexpr auto K = Kmin + ivar;
267 static constexpr auto nw_ext_s = detail::lgr_plus_one<K>();
269 sp = std::span<const double>(nw_ext_s.second.data(), k + 1);
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;
277 return transform(std::move(sp), [al](
double d) ->
double {
return al * d; });
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);
296 return join(std::move(all_views));
314 const std::size_t k = intervals_[i].K;
316 const double tau0 = intervals_[i].tau0;
317 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
319 Eigen::MatrixXd ret(k + 1, k);
321 utils::static_for<Kmax + 2 - Kmin>([&](
auto ivar) {
322 static constexpr auto K = Kmin + ivar;
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);
332 ret *= 2. / (tauf - tau0);
345 const std::size_t k = intervals_[i].K;
347 const double tau0 = intervals_[i].tau0;
348 const double tauf = i + 1 < intervals_.size() ? intervals_[i + 1].tau0 : 1.;
350 MatMap ret(
nullptr, 0, 0);
352 utils::static_for<Kmax + 2 - Kmin>([&](
auto ivar) {
353 static constexpr auto K = Kmin + ivar;
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);
360 new (&ret) MatMap(D_ext_s[0].data(), k + 1, k);
364 return {2. / (tauf - tau0), ret};
389 const std::size_t k = intervals_[i].K;
390 return interval_diffmat(i).block(1, 0, k, k).inverse();
398 if (t < 0) {
return 0; }
399 if (t > 1) {
return intervals_.size() - 1; }
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); }
411 for (
auto & ival : intervals_) { ival.K = std::min(ival.K + 1, Kmax + 1); }
419 for (
auto & ival : intervals_) { ival.K = std::max(ival.K - 1, Kmin); }
432 template<smooth::RnType RetT>
433 RetT
eval(
double t, std::ranges::range
auto && r, std::size_t p = 0,
bool extend =
true)
const
435 const std::size_t ival = interval_find(t);
436 const std::size_t k = intervals_[ival].K;
438 const double tau0 = intervals_[ival].tau0;
439 const double tauf = ival + 1 < intervals_.size() ? intervals_[ival + 1].tau0 : 1.;
441 const double u = 2 * (t - tau0) / (tauf - tau0) - 1;
443 int64_t N_before = 0;
444 for (
auto i = 0u; i < ival; ++i) { N_before += intervals_[i].K; }
447 RetT ret = RetT::Zero(std::ranges::begin(r)->size());
449 utils::static_for<Kmax + 2 - Kmin>([&](
auto i) {
450 static constexpr auto K = Kmin + i;
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);
455 const auto U = monomial_derivative<K>(u, p);
456 const auto W = U * B_ext_s;
458 for (
const auto & [w, v] : zip(std::span(W[0].data(), k + 1), r | drop(N_before))) { ret += w * v; }
460 static constexpr auto nw_s = lgr_nodes<K>();
461 static constexpr auto B_s = lagrange_basis<K - 1>(nw_s.first);
462 const auto U = monomial_derivative<K - 1>(u, p);
463 const auto W = U * B_s;
465 for (
const auto & [w, v] : zip(std::span(W[0].data(), k), r | drop(N_before))) { ret += w * v; }
483 std::vector<Interval> intervals_;
488concept MeshType = traits::is_specialization_of_sizet_v<std::decay_t<T>, Mesh>;
Collocation mesh of interval [0, 1].
std::size_t N_colloc() const
Number of collocation points in mesh.
auto interval_nodes(std::size_t i) const
Interval nodes (as range of doubles).
void decrease_degrees()
Decrease the degree of all intervals by one (down to minimal degree Kmin)
auto all_weights() const
Weights (as range of doubles)
std::pair< double, MatMap > interval_diffmat_unscaled(std::size_t i) const
Interval differentiation matrix (unscaled).
void set_N_colloc_ival(std::size_t i, std::size_t K)
Set the number of collocation points in interval i to K.
auto all_nodes() const
Nodes (as range of doubles).
std::size_t N_ivals() const
Number of intervals in mesh.
Eigen::MatrixXd interval_diffmat(std::size_t i) const
Interval differentiation matrix w.r.t. [0, 1] timescale.
std::size_t N_colloc_ival(std::size_t i) const
Number of collocation points in interval i.
RetT eval(double t, std::ranges::range auto &&r, std::size_t p=0, bool extend=true) const
Evaluate a function.
void increase_degrees()
Increase the degree of all intervals by one (up to maximal degree Kmax + 1)
Eigen::MatrixXd interval_intmat(std::size_t i) const
Interval integration matrix w.r.t. [0, 1] timescale.
auto interval_weights(std::size_t i) const
Interval weights (as range of doubles)
void refine_ph(std::size_t i, std::size_t D)
Refine interval using the ph strategy.
Mesh()
Create a mesh consisting of a single interval [0, 1].
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].
void refine_errors(std::ranges::sized_range auto &&errs, double target_err)
Refine intervals in mesh to satisfy a target error criterion.
std::size_t interval_find(double t) const
Find interval index that contains t.
MeshType is a specialization of Mesh.
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.