EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Methods.h
Go to the documentation of this file.
1#pragma once
2
12
13#include <array>
14#include <cmath>
15#include <concepts>
16#include <cstddef>
17#include <limits>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21
22#include <Eigen/Eigenvalues>
23
29
30namespace Emrys
31{
32
44template <typename Value, typename Jacobian>
46{
47 Value value;
48 Jacobian jacobian;
49};
50
51namespace internal
52{
53
57template <typename T>
58struct PlainResultOf
59{
61 using type = T;
62};
63
65template <MatrixExpression T>
66struct PlainResultOf<T>
67{
68 using type = typename T::PlainObject;
69};
70
71template <typename... Blocks>
72struct PlainResultOf<std::tuple<Blocks...>>
73{
74 using type = std::tuple<typename PlainResultOf<std::remove_cvref_t<Blocks>>::type...>;
75};
76
78
80template <typename T>
81using PlainResult = typename PlainResultOf<T>::type;
82
89template <typename F, typename... Args>
90[[nodiscard]] decltype(auto)
91callEvaluated(F & f, Args &&... args)
92{
93 using Returned = std::remove_cvref_t<decltype(f(std::forward<Args>(args)...))>;
94 if constexpr (MatrixExpression<Returned> && !std::same_as<PlainResult<Returned>, Returned>)
95 {
96 return PlainResult<Returned>{f(std::forward<Args>(args)...)};
97 }
98 else
99 {
100 return f(std::forward<Args>(args)...);
101 }
102}
103
109template <typename F, typename Scalar, typename... Args>
110concept TakesScalar = requires(F & f, Args &&... args) { f.template operator()<Scalar>(std::forward<Args>(args)...); };
111
117template <typename Scalar, typename F>
118struct WithScalar
119{
120 F & f;
121
123 template <typename... Args>
124 requires(TakesScalar<F, Scalar, Args...> || std::invocable<F &, Args...>)
125 [[nodiscard]] decltype(auto)
126 operator()(Args &&... args) const
127 {
128 if constexpr (TakesScalar<F, Scalar, Args...>)
129 {
130 return f.template operator()<Scalar>(std::forward<Args>(args)...);
131 }
132 else
133 {
134 return f(std::forward<Args>(args)...);
135 }
136 }
137};
138
142template <typename Scalar, typename F, typename... Args>
143[[nodiscard]] decltype(auto)
144callWithScalar(F & f, Args &&... args)
145{
146 auto const supplied = WithScalar<Scalar, F>{f};
147 return callEvaluated(supplied, std::forward<Args>(args)...);
148}
149
158template <typename F, typename In, typename Out, typename X, typename... Rest>
159inline constexpr int functionForm = [] {
160 using G = WithScalar<typename X::Scalar, F const> const &;
161 return std::invocable<G, In const &, Out const &, X const &, Rest...> ? 3
162 : std::invocable<G, In const &, X const &, Rest...> ? 2
163 : std::invocable<G, X const &, Rest...> ? 1
164 : 0;
165}();
166
172template <typename F, typename In, typename Out, typename X, typename... Rest>
173[[nodiscard]] decltype(auto)
174callForm(F const & f, In const & in, [[maybe_unused]] Out const & out, X const & x, Rest &&... rest)
175{
176 constexpr int Form = functionForm<F, In, Out, X, Rest &&...>;
177 static_assert(
178 Form != 0,
179 "a model's function takes h(x), h(in, x), or h(in, out, x), and a process model's f(x, dt), f(in, x, dt), or "
180 "f(in, out, x, dt), with in the input space, out the output space, and x a vector in the input space; a "
181 "batched function takes its output Y last; and each may take x's scalar as its one template parameter, "
182 "[]<typename T>(auto const & x)"
183 );
184 using Scalar = typename X::Scalar;
185 if constexpr (Form == 3)
186 {
187 return callWithScalar<Scalar>(f, in, out, x, std::forward<Rest>(rest)...);
188 }
189 else if constexpr (Form == 2)
190 {
191 return callWithScalar<Scalar>(f, in, x, std::forward<Rest>(rest)...);
192 }
193 else
194 {
195 return callWithScalar<Scalar>(f, x, std::forward<Rest>(rest)...);
196 }
197}
198
199} // namespace internal
200
218template <typename Value, typename Jacobian>
219[[nodiscard]] WithJacobian<
220 internal::PlainResult<std::remove_cvref_t<Value>>,
221 internal::PlainResult<std::remove_cvref_t<Jacobian>>>
222withJacobian(Value && value, Jacobian && jacobian)
223{
224 return {std::forward<Value>(value), std::forward<Jacobian>(jacobian)};
225}
226
231inline constexpr int MaxPointCount = 4096;
232
233namespace internal
234{
235
246template <typename Scalar>
247inline constexpr bool takesDoubleConstants = std::same_as<Scalar, float> || std::same_as<Scalar, double>;
248
254template <typename Scalar, int Order>
255[[nodiscard]] std::pair<Eigen::Matrix<Scalar, Order, 1>, Eigen::Matrix<Scalar, Order, 1>>
256hermiteRule()
257{
258 using Vector = Eigen::Matrix<Scalar, Order, 1>;
259 using Matrix = Eigen::Matrix<Scalar, Order, Order>;
260 Matrix recursion = Matrix::Zero();
261 for (int k = 1; k < Order; ++k)
262 {
263 recursion(k - 1, k) = Eigen::numext::sqrt(Scalar(k));
264 recursion(k, k - 1) = recursion(k - 1, k);
265 }
266 auto const solver = Eigen::SelfAdjointEigenSolver<Matrix>{recursion, Eigen::EigenvaluesOnly};
267 Vector const nodes = solver.eigenvalues();
268 Scalar factorial = 1;
269 for (int k = 2; k <= Order; ++k)
270 {
271 factorial *= Scalar(k);
272 }
273 auto weights = Vector{};
274 for (int i = 0; i < Order; ++i)
275 {
276 // H_{p-1}(x) by H_{k+1} = x H_k - k H_{k-1}, from H_0 = 1 and H_1 = x.
277 Scalar previous = 1;
278 Scalar current = nodes(i);
279 for (int k = 1; k < Order - 1; ++k)
280 {
281 Scalar const next = nodes(i) * current - Scalar(k) * previous;
282 previous = current;
283 current = next;
284 }
285 Scalar const value = Order == 1 ? Scalar(1) : current;
286 weights(i) = factorial / (Scalar(Order) * Scalar(Order) * value * value);
287 }
288 return {nodes, weights};
289}
290
291} // namespace internal
292
297template <typename Scalar>
299{
300 Scalar alpha;
301 Scalar beta;
302 Scalar kappa;
303};
304
311namespace methods
312{
313
327{};
328
332[[nodiscard]] constexpr Linearized
334{
335 return {};
336}
337
365template <double Alpha = 1.0, double Beta = 2.0, double Kappa = 0.0>
367{
368 static_assert(Alpha > 0, "the unscented transform's alpha must be positive");
369
370 static constexpr double alpha = Alpha;
371 static constexpr double beta = Beta;
372 static constexpr double kappa = Kappa;
373
375 template <typename Scalar>
376 [[nodiscard]] static constexpr UnscentedParams<Scalar>
377 params(Eigen::Index /*n*/)
378 {
379 return {Scalar(Alpha), Scalar(Beta), Scalar(Kappa)};
380 }
381};
382
387template <double Alpha = 1.0, double Beta = 2.0, double Kappa = 0.0>
388[[nodiscard]] constexpr Unscented<Alpha, Beta, Kappa>
390{
391 return {};
392}
393
410{
412 template <typename Scalar>
413 [[nodiscard]] static constexpr UnscentedParams<Scalar>
414 params(Eigen::Index n)
415 {
416 return {Scalar(1), Scalar(0), Scalar(3) - Scalar(n)};
417 }
418};
419
423[[nodiscard]] constexpr FourthMomentUnscented
425{
426 return {};
427}
428
442{
444 [[nodiscard]] static constexpr Eigen::Index
445 pointCount(Eigen::Index n)
446 {
447 return 2 * n;
448 }
449
456 template <typename Points, typename Weights>
457 static Eigen::Index
458 unitPoints(Eigen::Index n, Points & points, Weights & weights)
459 {
460 using Scalar = typename Points::Scalar;
461 constexpr int N = Points::RowsAtCompileTime;
462 points.setZero();
463 if constexpr (N != Eigen::Dynamic && N > 0 && internal::takesDoubleConstants<Scalar>)
464 {
465 constexpr Scalar radius = Scalar(internal::compileTimeSqrt(double(N)));
466 points.diagonal().setConstant(radius);
467 points.rightCols(N).diagonal().setConstant(-radius);
468 weights.setConstant(Scalar(1.0 / (2.0 * N)));
469 }
470 else
471 {
472 Scalar const radius = Eigen::numext::sqrt(Scalar(n));
473 for (Eigen::Index i = 0; i < n; ++i)
474 {
475 points(i, i) = radius;
476 points(i, n + i) = -radius;
477 }
478 weights.setConstant(Scalar(1) / Scalar(2 * n));
479 }
480 return 0;
481 }
482};
483
487[[nodiscard]] constexpr Cubature
489{
490 return {};
491}
492
510template <double CenterWeight = 0.0>
512{
513 static_assert(CenterWeight >= 0 && CenterWeight < 1, "a spherical simplex's center weight must be in [0, 1)");
514
515 static constexpr double centerWeight = CenterWeight;
516
518 [[nodiscard]] static constexpr Eigen::Index
519 pointCount(Eigen::Index n)
520 {
521 return n + 2;
522 }
523
532 template <typename Points, typename Weights>
533 static Eigen::Index
534 unitPoints(Eigen::Index n, Points & points, Weights & weights)
535 {
536 using Scalar = typename Points::Scalar;
537 points.setZero();
538 Scalar const center = Scalar(CenterWeight);
539 Scalar const pointWeight = (Scalar(1) - center) / Scalar(n + 1);
540 weights(0) = center;
541 weights.tail(n + 1).setConstant(pointWeight);
542 for (Eigen::Index j = 1; j <= n; ++j)
543 {
544 Scalar const step = Scalar(1) / Eigen::numext::sqrt(Scalar(j * (j + 1)) * pointWeight);
545 points.row(j - 1).segment(1, j).setConstant(-step);
546 points(j - 1, j + 1) = Scalar(j) * step;
547 }
548 return 0;
549 }
550};
551
556template <double CenterWeight = 0.0>
557[[nodiscard]] constexpr SphericalSimplex<CenterWeight>
559{
560 return {};
561}
562
583template <int Order = 3>
585{
586 static_assert(Order >= 1, "a Gauss-Hermite rule has at least one point per coordinate");
587 static constexpr int PointsPerCoordinate = Order;
588
592 [[nodiscard]] static constexpr Eigen::Index
593 pointCount(Eigen::Index n)
594 {
595 Eigen::Index count = 1;
596 for (Eigen::Index i = 0; i < n && count <= MaxPointCount; ++i)
597 {
598 count *= Order;
599 }
600 return std::min<Eigen::Index>(count, MaxPointCount + 1);
601 }
602
610 template <typename Points, typename Weights>
611 static Eigen::Index
612 unitPoints(Eigen::Index n, Points & points, Weights & weights)
613 {
614 using Scalar = typename Points::Scalar;
615 Eigen::Index const count = weights.size();
616 EMRYS_FILTER_ASSERT(
617 count <= MaxPointCount && "a Gauss-Hermite rule over this many coordinates has too many points"
618 );
619 auto const [nodes, nodeWeights] = internal::hermiteRule<Scalar, Order>();
620 for (Eigen::Index k = 0; k < count; ++k)
621 {
622 Eigen::Index digits = k;
623 Scalar weight = 1;
624 for (Eigen::Index i = 0; i < n; ++i)
625 {
626 Eigen::Index const digit = digits % Order;
627 digits /= Order;
628 points(i, k) = nodes(digit);
629 weight *= nodeWeights(digit);
630 }
631 weights(k) = weight;
632 }
633 Eigen::Index reference = 0;
634 weights.maxCoeff(&reference);
635 return reference;
636 }
637};
638
642template <int Order = 3>
643[[nodiscard]] constexpr GaussHermite<Order>
645{
646 return {};
647}
648
649} // namespace methods
650
673template <typename T>
674concept UnscentedRule = requires(Eigen::Index n) {
675 { T::template params<double>(n) } -> std::same_as<UnscentedParams<double>>;
676};
677
702template <typename T>
703concept PointSet = requires(Eigen::Index n, Eigen::MatrixXd & points, Eigen::VectorXd & weights) {
704 { T::pointCount(n) } -> std::convertible_to<Eigen::Index>;
705 { T::unitPoints(n, points, weights) } -> std::same_as<Eigen::Index>;
706};
707
708namespace methods
709{
710
729template <typename Rule, typename A>
730struct Spread
731{
732 static_assert(
734 "spread() takes a sigma-point rule, an UnscentedRule or a PointSet, not linearized(), iterated(), or a "
735 "rule spread already"
736 );
737 static_assert(Axes<A>, "spread() takes axes, such as roots::principal(), after its rule");
738
739 Rule rule{};
740 A axes{};
741};
742
748template <typename Rule, typename A>
749[[nodiscard]] constexpr Spread<Rule, A>
750spread(Rule rule, A axes)
751{
752 return {std::move(rule), std::move(axes)};
753}
754
755} // namespace methods
756
757namespace internal
758{
759
763template <typename T>
764inline constexpr bool isPointSet = PointSet<T>;
765
769template <typename T>
770inline constexpr bool isUnscented = UnscentedRule<T>;
771
773template <typename T>
774inline constexpr bool isSpread = false;
775
777template <typename Rule, typename A>
778inline constexpr bool isSpread<methods::Spread<Rule, A>>{true};
779
781
786template <typename T>
787inline constexpr bool isSigmaPointMethod = isUnscented<T> || isPointSet<T> || isSpread<T>;
788
792template <typename Method>
793[[nodiscard]] constexpr decltype(auto)
794ruleOf(Method const & method)
795{
796 if constexpr (isSpread<Method>)
797 {
798 return (method.rule);
799 }
800 else
801 {
802 return (method);
803 }
804}
805
809template <typename Method>
810[[nodiscard]] constexpr decltype(auto)
811axesOf(Method const & method)
812{
813 if constexpr (isSpread<Method>)
814 {
815 return (method.axes);
816 }
817 else
818 {
819 return roots::DefaultAxes{};
820 }
821}
822
824template <typename Method>
825using RuleOf = std::remove_cvref_t<decltype(ruleOf(std::declval<Method const &>()))>;
826
828template <typename Method>
829using AxesOf = std::remove_cvref_t<decltype(axesOf(std::declval<Method const &>()))>;
830
834template <typename A>
835consteval void
836refuseFailingAxes()
837{
838 static_assert(
839 !A::canFail,
840 "a transform returns no std::expected, so its axes cannot fail: append roots::pivoted() to the list, or take "
841 "the root with roots::squareRoot(), which returns one"
842 );
843}
844
848template <typename Result>
849[[nodiscard]] decltype(auto)
850rootValue(Result && result)
851{
852 if constexpr (requires { result.has_value(); })
853 {
854 EMRYS_FILTER_ASSERT(result.has_value() && "a transform's axes cannot fail");
855 return *std::forward<Result>(result);
856 }
857 else
858 {
859 return std::forward<Result>(result);
860 }
861}
862
866template <typename T>
867inline constexpr bool isLinearMethod = false;
868
870template <>
871inline constexpr bool isLinearMethod<methods::Linearized> = true;
872
874
875} // namespace internal
876
884template <RealScalar Scalar = double>
886{
887 Scalar tolerance = Scalar(1e-3);
888 std::size_t maxIterations = 10;
889};
890
891namespace methods
892{
893
960template <typename Method = Linearized, RealScalar Scalar = double>
962{
963 static_assert(
964 internal::isLinearMethod<Method> || internal::isSigmaPointMethod<Method>,
965 "Iterated relinearizes Linearized or a sigma-point method (an UnscentedRule such as Unscented, or a PointSet "
966 "such as Cubature, SphericalSimplex, GaussHermite, or the user's own) (decisions.md [iterated-update], "
967 "[sigma-point-sets])"
968 );
969
970 Method method{};
971 Scalar tolerance = Scalar(1e-3);
972 std::size_t maxIterations = 10;
973};
974
983template <RealScalar Scalar = double, typename Method = Linearized>
984[[nodiscard]] Iterated<Method, Scalar>
985iterated(Method method = {}, IteratedOptions<Scalar> options = {})
986{
987 EMRYS_FILTER_ASSERT(options.tolerance >= Scalar(0) && "an iteration's tolerance must not be negative");
988 EMRYS_FILTER_ASSERT(options.maxIterations >= 1 && "an Iterated method makes at least one linearization");
989 return {std::move(method), options.tolerance, options.maxIterations};
990}
991
992} // namespace methods
993
994namespace internal
995{
996
998template <typename T>
999inline constexpr bool isIterated = false;
1000
1002template <typename Method, typename Scalar>
1003inline constexpr bool isIterated<methods::Iterated<Method, Scalar>>{true};
1004
1006
1007} // namespace internal
1008
1018template <typename M>
1020 internal::isLinearMethod<std::remove_cvref_t<M>> || internal::isSigmaPointMethod<std::remove_cvref_t<M>>
1021 || internal::isIterated<std::remove_cvref_t<M>>;
1022
1023} // namespace Emrys
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
constexpr int MaxPointCount
The most points a PointSet may have over a fixed number of coordinates, which bounds the compile-time...
Definition Methods.h:231
Square roots of a covariance (decisions.md [sigma-point-axes]): the Axes and Repair concepts that a r...
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
Axes: a way of taking a root of a covariance , , whose columns carry a sigma-point rule's unit point...
Definition Roots.h:300
A set of sigma points other than Unscented's: a method that evaluates a function at weighted points g...
Definition Methods.h:703
A method observe() can take: Linearized, an UnscentedRule (Unscented, FourthMomentUnscented,...
Definition Methods.h:1019
A rule for the scaled unscented transform's parameters by the number of coordinates it spreads its po...
Definition Methods.h:674
WithFallbacks< Cholesky, Pivoted > DefaultAxes
The axes every root the library takes uses unless told otherwise, cholesky(pivoted()): the Cholesky f...
Definition Roots.h:1254
constexpr Spread< Rule, A > spread(Rule rule, A axes)
Definition Methods.h:750
constexpr Linearized linearized()
Definition Methods.h:333
constexpr GaussHermite< Order > gaussHermite()
Definition Methods.h:644
constexpr Unscented< Alpha, Beta, Kappa > unscented()
Definition Methods.h:389
Iterated< Method, Scalar > iterated(Method method={}, IteratedOptions< Scalar > options={})
Definition Methods.h:985
WithJacobian< internal::PlainResult< std::remove_cvref_t< Value > >, internal::PlainResult< std::remove_cvref_t< Jacobian > > > withJacobian(Value &&value, Jacobian &&jacobian)
Definition Methods.h:222
constexpr Cubature cubature()
Definition Methods.h:488
constexpr SphericalSimplex< CenterWeight > sphericalSimplex()
Definition Methods.h:558
constexpr FourthMomentUnscented fourthMomentUnscented()
Definition Methods.h:424
The transform methods and the sigma-point rules, each made by its factory (decisions....
Definition Methods.h:312
The run-time settings of an Iterated method, given to methods::iterated() as iterated(method,...
Definition Methods.h:886
std::size_t maxIterations
the most linearizations it makes, at least 1
Definition Methods.h:888
Scalar tolerance
the largest step that stops the iteration, in prior standard deviations
Definition Methods.h:887
The parameters of the scaled unscented transform over a number of coordinates, in Scalar,...
Definition Methods.h:299
Scalar kappa
the secondary scaling
Definition Methods.h:302
Scalar beta
the center point's extra covariance weight
Definition Methods.h:301
Scalar alpha
the spread of the points about the mean
Definition Methods.h:300
What a function returns when it knows its Jacobian: its value, and the Jacobian of its output differe...
Definition Methods.h:46
Jacobian jacobian
its Jacobian, one matrix or a tuple of blocks
Definition Methods.h:48
Value value
the function's value
Definition Methods.h:47
Cubature method: the points over the joint input space of dimension , for each column of a root of...
Definition Methods.h:442
static constexpr Eigen::Index pointCount(Eigen::Index n)
Definition Methods.h:445
static Eigen::Index unitPoints(Eigen::Index n, Points &points, Weights &weights)
Fills points, , with , the plus points first, and weights with each, over coordinates; over a fixed...
Definition Methods.h:458
Unscented method with , , and over coordinates, so that : the points sit at standard deviations al...
Definition Methods.h:410
static constexpr UnscentedParams< Scalar > params(Eigen::Index n)
Definition Methods.h:414
Gauss-Hermite method: the product rule of Order points per coordinate over the joint input space of d...
Definition Methods.h:585
static constexpr Eigen::Index pointCount(Eigen::Index n)
Definition Methods.h:593
static Eigen::Index unitPoints(Eigen::Index n, Points &points, Weights &weights)
Fills points, , with every combination of the one-dimensional points of internal::hermiteRule(),...
Definition Methods.h:612
static constexpr int PointsPerCoordinate
Order
Definition Methods.h:587
Iterated method, for a measurement update: the update of Method relinearized with respect to its own ...
Definition Methods.h:962
std::size_t maxIterations
the most linearizations it makes, at least 1
Definition Methods.h:972
Method method
the method relinearized at each iterate
Definition Methods.h:970
Scalar tolerance
the largest step that stops it, in prior standard deviations
Definition Methods.h:971
Linearized method: and , with the Jacobians of f at the input means.
Definition Methods.h:327
Spherical simplex method: points over the joint input space of dimension , the mean with weight cent...
Definition Methods.h:512
static constexpr Eigen::Index pointCount(Eigen::Index n)
Definition Methods.h:519
static Eigen::Index unitPoints(Eigen::Index n, Points &points, Weights &weights)
Fills points, , with the center, then the points of the recursion of Julier and Uhlmann (2004),...
Definition Methods.h:534
static constexpr double centerWeight
CenterWeight
Definition Methods.h:515
A sigma-point rule spread by axes of its own (decisions.md [sigma-point-axes]): Rule's unit points an...
Definition Methods.h:731
A axes
the axes they are spread along
Definition Methods.h:740
Rule rule
the unit points and weights
Definition Methods.h:739
Unscented method: sigma points over the joint input space of dimension , spread by .
Definition Methods.h:367
static constexpr double kappa
Kappa
Definition Methods.h:372
static constexpr UnscentedParams< Scalar > params(Eigen::Index)
Definition Methods.h:377
static constexpr double alpha
Alpha
Definition Methods.h:370
static constexpr double beta
Beta
Definition Methods.h:371