EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Roots.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <concepts>
13#include <cstddef>
14#include <expected>
15#include <optional>
16#include <tuple>
17#include <type_traits>
18#include <utility>
19#include <variant>
20
21#include <Eigen/Cholesky>
22#include <Eigen/Eigenvalues>
23#include <Eigen/SVD>
24
29
30namespace Emrys
31{
32
33namespace internal
34{
35
39template <typename Derived>
40using SquareMatrixOf = Eigen::Matrix<
41 typename Derived::Scalar,
42 Derived::RowsAtCompileTime,
43 Derived::RowsAtCompileTime,
44 Eigen::ColMajor,
45 Derived::MaxRowsAtCompileTime,
46 Derived::MaxRowsAtCompileTime>;
47
49template <typename Matrix>
50using RootColumnOf = Eigen::
51 Matrix<typename Matrix::Scalar, Matrix::RowsAtCompileTime, 1, Eigen::ColMajor, Matrix::MaxRowsAtCompileTime, 1>;
52
54template <bool CanFail, typename Matrix>
55using RootResult = std::conditional_t<CanFail, std::expected<Matrix, GaussianError>, Matrix>;
56
60struct DiscardReport
61{
63 template <typename Report>
64 constexpr void
65 operator()(Report const & /*report*/) const noexcept
66 {}
67};
68
72template <typename Scalar, double Value>
73[[nodiscard]] Scalar
74scalarFromConstant()
75{
76 using Literal = typename Eigen::NumTraits<Scalar>::Literal;
77 return Scalar(static_cast<Literal>(Value));
78}
79
81template <typename Derived>
82[[nodiscard]] bool
83lowerFinite(Eigen::MatrixBase<Derived> const & a)
84{
85 for (Eigen::Index j = 0; j < a.cols(); ++j)
86 {
87 if (!allFinite(a.col(j).tail(a.rows() - j)))
88 {
89 return false;
90 }
91 }
92 return true;
93}
94
111template <double Relative = 1.0, double Absolute = 0.0, typename Derived>
112[[nodiscard]] std::expected<typename Derived::PlainObject, GaussianError>
113ridgedCovariance(Eigen::MatrixBase<Derived> const & cov, typename Derived::Scalar * delta = nullptr)
114{
115 using Scalar = typename Derived::Scalar;
116 EMRYS_FILTER_ASSERT(cov.rows() == cov.cols() && "a covariance must be square");
117 Eigen::Index const n = cov.rows();
118
119 // Row i's off-diagonal elements are row i left of the diagonal and, by symmetry, column i
120 // below it. NaN or infinity anywhere in the lower triangle reaches some row's bound.
121 auto const diagonal = cov.diagonal();
122 Scalar bound = Eigen::NumTraits<Scalar>::infinity();
123 for (Eigen::Index i = 0; i < n; ++i)
124 {
125 Scalar const radius = cov.row(i).head(i).cwiseAbs().sum() + cov.col(i).tail(n - i - 1).cwiseAbs().sum();
126 Scalar const rowBound = diagonal(i) - radius;
127 if (!isFinite(rowBound))
128 {
129 return std::unexpected{GaussianError::NonFinite};
130 }
131 bound = Eigen::numext::mini(bound, rowBound);
132 }
133
134 Scalar const shift = Eigen::numext::maxi(-bound, Scalar(0));
135 Scalar const scale = Eigen::numext::maxi(n > 0 ? diagonal.cwiseAbs().maxCoeff() : Scalar(0), shift);
136 Scalar const margin =
137 scalarFromConstant<Scalar, Relative>() * Eigen::numext::sqrt(Eigen::NumTraits<Scalar>::epsilon()) * scale
138 + scalarFromConstant<Scalar, Absolute>();
139 typename Derived::PlainObject ridged = cov;
140 ridged.diagonal().array() += shift + margin;
141 if (delta != nullptr)
142 {
143 *delta = shift + margin;
144 }
145 return ridged;
146}
147
151template <typename Matrix>
152void
153fixColumnSigns(Matrix & root)
154{
155 for (Eigen::Index j = 0; j < root.cols(); ++j)
156 {
157 Eigen::Index largest = 0;
158 root.col(j).cwiseAbs().maxCoeff(&largest);
159 if (root(largest, j) < typename Matrix::Scalar(0))
160 {
161 root.col(j) *= typename Matrix::Scalar(-1);
162 }
163 }
164}
165
169template <typename Derived>
170[[nodiscard]] bool
171isTriangularRoot(Eigen::MatrixBase<Derived> const & a)
172{
173 using Scalar = typename Derived::Scalar;
174 if (a.rows() != a.cols())
175 {
176 return false;
177 }
178 for (Eigen::Index j = 0; j < a.cols(); ++j)
179 {
180 if (a(j, j) < Scalar(0))
181 {
182 return false;
183 }
184 for (Eigen::Index i = 0; i < j; ++i)
185 {
186 if (a(i, j) != Scalar(0))
187 {
188 return false;
189 }
190 }
191 }
192 return true;
193}
194
199template <typename Derived>
200[[nodiscard]] SquareMatrixOf<Derived>
201triangularRootOf(Eigen::MatrixBase<Derived> const & factor)
202{
203 if constexpr (Derived::ColsAtCompileTime == Eigen::Dynamic
204 || Derived::ColsAtCompileTime == Derived::RowsAtCompileTime)
205 {
206 if (isTriangularRoot(factor))
207 {
208 return SquareMatrixOf<Derived>{factor};
209 }
210 }
211 return lowerTriangularRoot(factor);
212}
213
219template <typename Derived>
220struct PrincipalAxes
221{
222 SquareMatrixOf<Derived> vectors;
223 RootColumnOf<SquareMatrixOf<Derived>> roots;
224 Eigen::Index clamped = 0;
225};
226
228template <typename Derived>
229[[nodiscard]] PrincipalAxes<Derived>
230principalAxesOfCovariance(Eigen::MatrixBase<Derived> const & cov)
231{
232 using Matrix = SquareMatrixOf<Derived>;
233 using Scalar = typename Matrix::Scalar;
234 auto const solver = Eigen::SelfAdjointEigenSolver<Matrix>{Matrix{cov}, Eigen::ComputeEigenvectors};
235 Eigen::Index const n = cov.rows();
236 auto axes = PrincipalAxes<Derived>{};
237 axes.vectors.resize(n, n);
238 axes.roots.resize(n);
239 for (Eigen::Index j = 0; j < n; ++j)
240 {
241 // The solver sorts its eigenvalues smallest first.
242 Scalar const value = solver.eigenvalues()(n - 1 - j);
243 if (value < Scalar(0))
244 {
245 ++axes.clamped;
246 }
247 axes.vectors.col(j) = solver.eigenvectors().col(n - 1 - j);
248 axes.roots(j) = Eigen::numext::sqrt(Eigen::numext::maxi(value, Scalar(0)));
249 }
250 return axes;
251}
252
257template <typename Derived>
258[[nodiscard]] PrincipalAxes<Derived>
259principalAxesOfFactor(Eigen::MatrixBase<Derived> const & factor)
260{
261 using Matrix = SquareMatrixOf<Derived>;
262 Matrix const lower = triangularRootOf(factor);
263 auto const svd = Eigen::JacobiSVD<Matrix, Eigen::ComputeFullU>{lower};
264 return PrincipalAxes<Derived>{svd.matrixU(), svd.singularValues(), 0};
265}
266
268template <typename T>
269inline constexpr bool isFallbackList = false;
270
271} // namespace internal
272
299template <typename T>
300concept Axes = requires {
301 { std::bool_constant<T::canFail>{} } -> std::convertible_to<bool>;
302} && requires(T const & axes, Eigen::Matrix3d const & cov) {
303 { axes.root(cov) } -> std::same_as<internal::RootResult<T::canFail, Eigen::Matrix3d>>;
304};
305
320template <typename T>
321concept Repair = requires(T const & repair, Eigen::Matrix3d const & cov) {
322 { repair.repaired(cov) } -> std::same_as<std::expected<Eigen::Matrix3d, GaussianError>>;
323};
324
325namespace internal
326{
327
331struct NoRepair
332{};
333
334} // namespace internal
335
341namespace roots
342{
343
349template <typename Derived>
350using RootMatrix = internal::SquareMatrixOf<Derived>;
351
357{};
358
364{
365 Eigen::Index clamped = 0;
366};
367
373template <typename Scalar>
375{
376 Scalar ridge = 0;
377};
378
397{
398 static constexpr bool canFail = true;
399
401 template <typename Derived>
402 [[nodiscard]] std::expected<internal::SquareMatrixOf<Derived>, GaussianError>
403 root(Eigen::MatrixBase<Derived> const & cov) const
404 {
405 using Matrix = internal::SquareMatrixOf<Derived>;
406 if (!internal::lowerFinite(cov))
407 {
408 return std::unexpected{GaussianError::NonFinite};
409 }
410 auto const llt = Eigen::LLT<Matrix>{Matrix{cov}};
411 if (llt.info() != Eigen::Success)
412 {
413 return std::unexpected{GaussianError::NotPositiveDefinite};
414 }
415 return Matrix{llt.matrixL()};
416 }
417
419 template <typename Derived>
420 [[nodiscard]] std::expected<internal::SquareMatrixOf<Derived>, GaussianError>
421 rootFromFactor(Eigen::MatrixBase<Derived> const & factor) const
422 {
423 if (!allFinite(factor))
424 {
425 return std::unexpected{GaussianError::NonFinite};
426 }
427 return internal::triangularRootOf(factor);
428 }
429};
430
446{
447 static constexpr bool canFail = false;
448
450 template <typename Scalar>
452
454 template <typename Derived>
455 [[nodiscard]] internal::SquareMatrixOf<Derived>
456 root(Eigen::MatrixBase<Derived> const & cov) const
457 {
458 return root(cov, internal::DiscardReport{});
459 }
460
462 template <typename Derived, typename Sink>
463 [[nodiscard]] internal::SquareMatrixOf<Derived>
464 root(Eigen::MatrixBase<Derived> const & cov, Sink && sink) const
465 {
466 using Matrix = internal::SquareMatrixOf<Derived>;
467 using Scalar = typename Matrix::Scalar;
468 auto const ldlt = Eigen::LDLT<Matrix>{Matrix{cov}};
469 auto const pivots = ldlt.vectorD();
470 sink(ClampReport{(pivots.array() < Scalar(0)).count()});
471 auto const d = pivots.cwiseMax(Scalar(0)).cwiseSqrt().eval();
472 Matrix lowerD = ldlt.matrixL();
473 lowerD *= d.asDiagonal();
474 return ldlt.transpositionsP().transpose() * lowerD;
475 }
476};
477
493{
494 static constexpr bool canFail = false;
495
497 template <typename Scalar>
499
501 template <typename Derived>
502 [[nodiscard]] internal::SquareMatrixOf<Derived>
503 root(Eigen::MatrixBase<Derived> const & cov) const
504 {
505 return root(cov, internal::DiscardReport{});
506 }
507
509 template <typename Derived, typename Sink>
510 [[nodiscard]] internal::SquareMatrixOf<Derived>
511 root(Eigen::MatrixBase<Derived> const & cov, Sink && sink) const
512 {
513 auto const axes = internal::principalAxesOfCovariance(cov);
514 sink(ClampReport{axes.clamped});
515 return fromAxes<Derived>(axes);
516 }
517
519 template <typename Derived>
520 [[nodiscard]] internal::SquareMatrixOf<Derived>
521 rootFromFactor(Eigen::MatrixBase<Derived> const & factor) const
522 {
523 return fromAxes<Derived>(internal::principalAxesOfFactor(factor));
524 }
525
526private:
527
529 template <typename Derived>
530 [[nodiscard]] static internal::SquareMatrixOf<Derived>
531 fromAxes(internal::PrincipalAxes<Derived> const & axes)
532 {
533 internal::SquareMatrixOf<Derived> root = axes.vectors * axes.roots.asDiagonal();
534 internal::fixColumnSigns(root);
535 return root;
536 }
537};
538
552{
553 static constexpr bool canFail = false;
554
556 template <typename Scalar>
558
560 template <typename Derived>
561 [[nodiscard]] internal::SquareMatrixOf<Derived>
562 root(Eigen::MatrixBase<Derived> const & cov) const
563 {
564 return root(cov, internal::DiscardReport{});
565 }
566
568 template <typename Derived, typename Sink>
569 [[nodiscard]] internal::SquareMatrixOf<Derived>
570 root(Eigen::MatrixBase<Derived> const & cov, Sink && sink) const
571 {
572 auto const axes = internal::principalAxesOfCovariance(cov);
573 sink(ClampReport{axes.clamped});
574 return fromAxes<Derived>(axes);
575 }
576
580 template <typename Derived>
581 [[nodiscard]] internal::SquareMatrixOf<Derived>
582 rootFromFactor(Eigen::MatrixBase<Derived> const & factor) const
583 {
584 return fromAxes<Derived>(internal::principalAxesOfFactor(factor));
585 }
586
587private:
588
590 template <typename Derived>
591 [[nodiscard]] static internal::SquareMatrixOf<Derived>
592 fromAxes(internal::PrincipalAxes<Derived> const & axes)
593 {
594 return axes.vectors * axes.roots.asDiagonal() * axes.vectors.transpose();
595 }
596};
597
617template <typename Vector>
618struct Along
619{
620 static constexpr bool canFail = true;
621
622 Vector direction;
623
627 template <typename Derived>
628 [[nodiscard]] std::expected<internal::SquareMatrixOf<Derived>, GaussianError>
629 root(Eigen::MatrixBase<Derived> const & cov) const
630 {
631 auto const lower = Cholesky{}.root(cov);
632 if (!lower)
633 {
634 return std::unexpected{lower.error()};
635 }
636 return rotated(*lower);
637 }
638
640 template <typename Derived>
641 [[nodiscard]] std::expected<internal::SquareMatrixOf<Derived>, GaussianError>
642 rootFromFactor(Eigen::MatrixBase<Derived> const & factor) const
643 {
644 auto const lower = Cholesky{}.rootFromFactor(factor);
645 if (!lower)
646 {
647 return std::unexpected{lower.error()};
648 }
649 return rotated(*lower);
650 }
651
652private:
653
657 template <typename Matrix>
658 [[nodiscard]] std::expected<Matrix, GaussianError>
659 rotated(Matrix const & lower) const
660 {
661 using Scalar = typename Matrix::Scalar;
662 using ColumnType = internal::RootColumnOf<Matrix>;
663 EMRYS_FILTER_ASSERT(
664 direction.size() == lower.rows() && "a direction has a coordinate per coordinate of the covariance"
665 );
666 ColumnType const whitened =
667 lower.template triangularView<Eigen::Lower>().solve(direction.template cast<Scalar>());
668 Scalar const length = whitened.norm();
669 if (!isFinite(length) || !(length > Scalar(0)))
670 {
671 return std::unexpected{GaussianError::NotPositiveDefinite};
672 }
673 ColumnType const unit = whitened / length;
674 // The reflection H = I - 2 v v^T / v^T v with v = e1 - s u takes e1 to s u. The sign s is
675 // the one that makes v^T v at least 2, so that no cancellation reaches it, and the first
676 // column is then flipped by s so that it points along d.
677 Scalar const sign = unit(0) < Scalar(0) ? Scalar(1) : Scalar(-1);
678 ColumnType v = -sign * unit;
679 v(0) += Scalar(1);
680 Matrix root = lower - (Scalar(2) / v.squaredNorm()) * (lower * v) * v.transpose();
681 root.col(0) *= sign;
682 return root;
683 }
684};
685
702template <double Relative = 1.0, double Absolute = 0.0>
703struct Ridge
704{
705 static_assert(Relative >= 0 && Absolute >= 0, "a ridge's margins are at least zero");
706
707 static constexpr double relative = Relative;
708 static constexpr double absolute = Absolute;
709
711 template <typename Scalar>
713
715 template <typename Derived>
716 [[nodiscard]] std::expected<typename Derived::PlainObject, GaussianError>
717 repaired(Eigen::MatrixBase<Derived> const & cov) const
718 {
719 return internal::ridgedCovariance<Relative, Absolute>(cov);
720 }
721
723 template <typename Derived, typename Sink>
724 [[nodiscard]] std::expected<typename Derived::PlainObject, GaussianError>
725 repaired(Eigen::MatrixBase<Derived> const & cov, Sink && sink) const
726 {
727 auto delta = typename Derived::Scalar(0);
728 auto result = internal::ridgedCovariance<Relative, Absolute>(cov, &delta);
730 return result;
731 }
732};
733
734} // namespace roots
735
736namespace internal
737{
738
742template <typename Entry, typename Scalar>
743struct ReportOf
744{
745 using type = roots::NoReport;
746};
747
749template <typename Entry, typename Scalar>
750 requires requires { typename Entry::template Report<Scalar>; }
751struct ReportOf<Entry, Scalar>
752{
753 using type = typename Entry::template Report<Scalar>;
754};
755
757
759template <typename Entry, typename Scalar>
760using ReportOfT = typename ReportOf<Entry, Scalar>::type;
761
765template <typename A, typename Scalar>
766struct FailureReportOf
767{
768 using type = ReportOfT<A, Scalar>;
769};
770
772template <typename A, typename Scalar>
773 requires requires { typename A::template FailureReport<Scalar>; }
774struct FailureReportOf<A, Scalar>
775{
776 using type = typename A::template FailureReport<Scalar>;
777};
778
780
782template <typename A, typename Scalar>
783using FailureReportOfT = typename FailureReportOf<A, Scalar>::type;
784
788template <typename Entry>
789[[nodiscard]] consteval bool
790entryCanFail()
791{
792 if constexpr (requires { Entry::canFail; })
793 {
794 return Entry::canFail;
795 }
796 else
797 {
798 return true;
799 }
800}
801
805template <typename... Fallbacks>
806[[nodiscard]] consteval bool
807fallbacksReached()
808{
809 constexpr bool canFail[] = {entryCanFail<Fallbacks>()..., true};
810 for (std::size_t i = 0; i + 1 < sizeof...(Fallbacks); ++i)
811 {
812 if (!canFail[i])
813 {
814 return false;
815 }
816 }
817 return true;
818}
819
823template <typename A, typename Derived, typename Sink>
824[[nodiscard]] auto
825rootWithSink(A const & axes, Eigen::MatrixBase<Derived> const & cov, Sink && sink)
826{
827 if constexpr (requires { axes.root(cov, sink); })
828 {
829 return axes.root(cov, sink);
830 }
831 else
832 {
833 sink(roots::NoReport{});
834 return axes.root(cov);
835 }
836}
837
839template <typename R, typename Derived, typename Sink>
840[[nodiscard]] auto
841repairWithSink(R const & repair, Eigen::MatrixBase<Derived> const & cov, Sink && sink)
842{
843 if constexpr (requires { repair.repaired(cov, sink); })
844 {
845 return repair.repaired(cov, sink);
846 }
847 else
848 {
849 sink(roots::NoReport{});
850 return repair.repaired(cov);
851 }
852}
853
857template <typename A, typename Derived>
858[[nodiscard]] auto
859rootFromFactor(A const & axes, Eigen::MatrixBase<Derived> const & factor)
860{
861 if constexpr (requires { axes.rootFromFactor(factor); })
862 {
863 return axes.rootFromFactor(factor);
864 }
865 else
866 {
867 return axes.root(SquareMatrixOf<Derived>{factor * factor.transpose()});
868 }
869}
870
872template <typename Matrix, typename Result>
873[[nodiscard]] std::expected<Matrix, GaussianError>
874asExpected(Result && result)
875{
876 if constexpr (requires { result.has_value(); })
877 {
878 if (!result)
879 {
880 return std::unexpected{result.error()};
881 }
882 return Matrix{std::move(*result)};
883 }
884 else
885 {
886 return Matrix{std::forward<Result>(result)};
887 }
888}
889
890} // namespace internal
891
892namespace roots
893{
894
915template <typename Head, typename... Fallbacks>
917{
918 static_assert(Axes<Head>, "a fallback list starts with axes");
919 static_assert(internal::entryCanFail<Head>(), "axes that cannot fail take no fallback");
920 static_assert(((Axes<Fallbacks> || Repair<Fallbacks>) && ...), "a fallback is axes or a repair");
921 static_assert(
922 (!internal::isFallbackList<Fallbacks> && ...),
923 "a fallback list is flat: give a fallback's fallbacks to the list itself"
924 );
925 static_assert(
926 internal::fallbacksReached<Fallbacks...>(),
927 "a fallback after axes that cannot fail is never reached"
928 );
929
931 static constexpr bool canFail =
932 internal::entryCanFail<std::tuple_element_t<sizeof...(Fallbacks) - 1, std::tuple<Fallbacks...>>>();
933
935 template <typename Scalar>
936 using Report = std::variant<internal::ReportOfT<Head, Scalar>, internal::ReportOfT<Fallbacks, Scalar>...>;
937
939 template <typename Scalar>
940 using FailureReport = std::tuple<
941 std::optional<internal::ReportOfT<Head, Scalar>>,
942 std::optional<internal::ReportOfT<Fallbacks, Scalar>>...>;
943
944 Head head;
945 std::tuple<Fallbacks...> fallbacks;
946
950 template <typename Derived>
951 [[nodiscard]] internal::RootResult<canFail, internal::SquareMatrixOf<Derived>>
952 root(Eigen::MatrixBase<Derived> const & cov) const
953 {
954 return root(cov, internal::DiscardReport{});
955 }
956
960 template <typename Derived, typename Sink>
961 [[nodiscard]] internal::RootResult<canFail, internal::SquareMatrixOf<Derived>>
962 root(Eigen::MatrixBase<Derived> const & cov, Sink && sink) const
963 {
964 using Matrix = internal::SquareMatrixOf<Derived>;
965 using Scalar = typename Derived::Scalar;
966 auto tried = FailureReport<Scalar>{};
967 auto const recordInto = [&tried]<std::size_t I>(std::integral_constant<std::size_t, I>) {
968 return [&tried](auto const & report) {
969 std::get<I>(tried) = report;
970 };
971 };
972 std::optional<Matrix> found;
973 std::size_t used = 0;
975 auto const keep = [&](std::size_t index, auto && result) {
976 auto const root = internal::asExpected<Matrix>(std::forward<decltype(result)>(result));
977 if (root)
978 {
979 found = *root;
980 used = index;
981 }
982 else
983 {
984 error = root.error();
985 }
986 };
987 keep(0, internal::rootWithSink(head, cov, recordInto(std::integral_constant<std::size_t, 0>{})));
988 [&]<std::size_t... I>(std::index_sequence<I...>) {
989 (
990 [&] {
991 if (found)
992 {
993 return;
994 }
995 auto const & entry = std::get<I>(fallbacks);
996 auto const record = recordInto(std::integral_constant<std::size_t, I + 1>{});
997 using Entry = std::tuple_element_t<I, std::tuple<Fallbacks...>>;
998 if constexpr (Repair<Entry>)
999 {
1000 auto const fixed = internal::repairWithSink(entry, cov, record);
1001 if (!fixed)
1002 {
1003 error = fixed.error();
1004 return;
1005 }
1006 keep(I + 1, head.root(*fixed));
1007 }
1008 else
1009 {
1010 keep(I + 1, internal::rootWithSink(entry, cov, record));
1011 }
1012 }(),
1013 ...
1014 );
1015 }(std::index_sequence_for<Fallbacks...>{});
1016 if (found)
1017 {
1018 sink(successReport<Scalar>(tried, used));
1019 return std::move(*found);
1020 }
1021 if constexpr (canFail)
1022 {
1023 sink(tried);
1024 return std::unexpected{error};
1025 }
1026 else
1027 {
1028 EMRYS_FILTER_ASSERT(false && "the last fallback cannot fail");
1029 return Matrix{};
1030 }
1031 }
1032
1034 template <typename Derived>
1035 [[nodiscard]] internal::RootResult<canFail, internal::SquareMatrixOf<Derived>>
1036 rootFromFactor(Eigen::MatrixBase<Derived> const & factor) const
1037 {
1038 using Matrix = internal::SquareMatrixOf<Derived>;
1039 auto first = internal::asExpected<Matrix>(internal::rootFromFactor(head, factor));
1040 if (first)
1041 {
1042 return std::move(*first);
1043 }
1044 std::optional<Matrix> found;
1045 auto error = first.error();
1046 std::optional<Matrix> covariance;
1047 [&]<std::size_t... I>(std::index_sequence<I...>) {
1048 (
1049 [&] {
1050 if (found)
1051 {
1052 return;
1053 }
1054 auto const & entry = std::get<I>(fallbacks);
1055 using Entry = std::tuple_element_t<I, std::tuple<Fallbacks...>>;
1056 std::expected<Matrix, GaussianError> root = std::unexpected{error};
1057 if constexpr (Repair<Entry>)
1058 {
1059 if (!covariance)
1060 {
1061 covariance = Matrix{factor * factor.transpose()};
1062 }
1063 auto const fixed = entry.repaired(*covariance);
1064 if (!fixed)
1065 {
1066 error = fixed.error();
1067 return;
1068 }
1069 root = internal::asExpected<Matrix>(head.root(*fixed));
1070 }
1071 else
1072 {
1073 root = internal::asExpected<Matrix>(internal::rootFromFactor(entry, factor));
1074 }
1075 if (root)
1076 {
1077 found = std::move(*root);
1078 }
1079 else
1080 {
1081 error = root.error();
1082 }
1083 }(),
1084 ...
1085 );
1086 }(std::index_sequence_for<Fallbacks...>{});
1087 if (found)
1088 {
1089 return std::move(*found);
1090 }
1091 if constexpr (canFail)
1092 {
1093 return std::unexpected{error};
1094 }
1095 else
1096 {
1097 EMRYS_FILTER_ASSERT(false && "the last fallback cannot fail");
1098 return Matrix{};
1099 }
1100 }
1101
1102private:
1103
1105 template <typename Scalar, std::size_t I = 0>
1106 [[nodiscard]] static Report<Scalar>
1107 successReport(FailureReport<Scalar> const & tried, std::size_t used)
1108 {
1109 if constexpr (I + 1 < std::tuple_size_v<FailureReport<Scalar>>)
1110 {
1111 if (used != I)
1112 {
1113 return successReport<Scalar, I + 1>(tried, used);
1114 }
1115 }
1116 EMRYS_FILTER_ASSERT(std::get<I>(tried).has_value());
1117 return Report<Scalar>{std::in_place_index<I>, *std::get<I>(tried)};
1118 }
1119};
1120
1121} // namespace roots
1122
1123namespace internal
1124{
1125
1127template <typename Head, typename... Fallbacks>
1128inline constexpr bool isFallbackList<roots::WithFallbacks<Head, Fallbacks...>>{true};
1129
1131
1138template <typename Derived>
1139[[nodiscard]] SquareMatrixOf<Derived>
1140defaultRoot(Eigen::MatrixBase<Derived> const & cov)
1141{
1142 return roots::WithFallbacks<roots::Cholesky, roots::Pivoted>{}.root(cov);
1143}
1144
1146template <typename Head, typename... Fallbacks>
1147[[nodiscard]] constexpr auto
1148withFallbacks(Head head, Fallbacks... fallbacks)
1149{
1150 if constexpr (sizeof...(Fallbacks) == 0)
1151 {
1152 return head;
1153 }
1154 else
1155 {
1156 return roots::WithFallbacks<Head, Fallbacks...>{
1157 std::move(head),
1158 std::tuple<Fallbacks...>{std::move(fallbacks)...}
1159 };
1160 }
1161}
1162
1163} // namespace internal
1164
1165namespace roots
1166{
1167
1172template <typename... Fallbacks>
1173[[nodiscard]] constexpr auto
1174cholesky(Fallbacks... fallbacks)
1175{
1176 return internal::withFallbacks(Cholesky{}, std::move(fallbacks)...);
1177}
1178
1182template <typename... Fallbacks>
1183[[nodiscard]] constexpr auto
1184pivoted(Fallbacks... fallbacks)
1185{
1186 return internal::withFallbacks(Pivoted{}, std::move(fallbacks)...);
1187}
1188
1192template <typename... Fallbacks>
1193[[nodiscard]] constexpr auto
1194principal(Fallbacks... fallbacks)
1195{
1196 return internal::withFallbacks(Principal{}, std::move(fallbacks)...);
1197}
1198
1202template <typename... Fallbacks>
1203[[nodiscard]] constexpr auto
1204symmetric(Fallbacks... fallbacks)
1205{
1206 return internal::withFallbacks(Symmetric{}, std::move(fallbacks)...);
1207}
1208
1214template <typename Derived, typename... Fallbacks>
1215[[nodiscard]] auto
1216along(Eigen::MatrixBase<Derived> const & direction, Fallbacks... fallbacks)
1217{
1218 static_assert(Derived::ColsAtCompileTime == 1, "along() takes a direction as a column vector");
1219 return internal::withFallbacks(
1220 Along<typename Derived::PlainObject>{typename Derived::PlainObject{direction}},
1221 std::move(fallbacks)...
1222 );
1223}
1224
1230template <double Relative = 1.0, double Absolute = 0.0>
1231[[nodiscard]] constexpr Ridge<Relative, Absolute>
1233{
1234 return {};
1235}
1236
1242template <typename Head, typename First, typename... Rest>
1243[[nodiscard]] constexpr WithFallbacks<Head, First, Rest...>
1244withFallbacks(Head axes, First first, Rest... rest)
1245{
1246 return {std::move(axes), std::tuple<First, Rest...>{std::move(first), std::move(rest)...}};
1247}
1248
1255
1269template <Axes A, typename Derived>
1270[[nodiscard]] auto
1271squareRoot(A const & axes, Eigen::MatrixBase<Derived> const & cov)
1272{
1273 EMRYS_FILTER_ASSERT(cov.rows() == cov.cols() && "a covariance must be square");
1274 return axes.root(cov);
1275}
1276
1284template <typename Matrix, typename Report>
1286{
1287 Matrix root;
1288 Report report;
1289};
1290
1296template <typename Report>
1302
1310template <Axes A, typename Derived>
1311[[nodiscard]] auto
1312squareRootWithReport(A const & axes, Eigen::MatrixBase<Derived> const & cov)
1313{
1314 using Scalar = typename Derived::Scalar;
1315 using Matrix = internal::SquareMatrixOf<Derived>;
1316 using Report = internal::ReportOfT<A, Scalar>;
1317 using Failure = internal::FailureReportOfT<A, Scalar>;
1318 std::optional<Report> report;
1319 std::optional<Failure> failure;
1320 auto result = internal::rootWithSink(axes, cov, [&](auto const & given) {
1321 using Given = std::remove_cvref_t<decltype(given)>;
1322 if constexpr (std::same_as<Given, Report>)
1323 {
1324 report = given;
1325 }
1326 if constexpr (std::same_as<Given, Failure>)
1327 {
1328 failure = given;
1329 }
1330 });
1331 if constexpr (A::canFail)
1332 {
1333 using Result = std::expected<RootWithReport<Matrix, Report>, RootFailure<Failure>>;
1334 if (!result)
1335 {
1336 return Result{std::unexpect, RootFailure<Failure>{result.error(), failure.value_or(Failure{})}};
1337 }
1338 return Result{RootWithReport<Matrix, Report>{std::move(*result), report.value_or(Report{})}};
1339 }
1340 else
1341 {
1342 return RootWithReport<Matrix, Report>{std::move(result), report.value_or(Report{})};
1343 }
1344}
1345
1346} // namespace roots
1347
1348} // namespace Emrys
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
NumericError, the reasons a factorization or a normalization can fail, and its two names,...
Triangularization: the triangular factor of a stacked matrix by Householder reflections,...
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 repair: what turns a covariance whose root failed into one to retry (decisions.md [sigma-point-axes...
Definition Roots.h:321
bool allFinite(Eigen::DenseBase< Derived > const &x)
Definition Math.h:354
bool isFinite(T const &x)
Definition Math.h:305
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
@ NonFinite
a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)
Definition NumericError.h:33
NumericError GaussianError
Reasons a Gaussian, in any of its forms (decisions.md [gaussian-forms]), can fail to give a density,...
Definition NumericError.h:45
constexpr auto symmetric(Fallbacks... fallbacks)
Definition Roots.h:1204
constexpr auto cholesky(Fallbacks... fallbacks)
Definition Roots.h:1174
auto squareRoot(A const &axes, Eigen::MatrixBase< Derived > const &cov)
Definition Roots.h:1271
constexpr WithFallbacks< Head, First, Rest... > withFallbacks(Head axes, First first, Rest... rest)
Definition Roots.h:1244
auto squareRootWithReport(A const &axes, Eigen::MatrixBase< Derived > const &cov)
Definition Roots.h:1312
constexpr Ridge< Relative, Absolute > ridge()
Definition Roots.h:1232
constexpr auto principal(Fallbacks... fallbacks)
Definition Roots.h:1194
internal::SquareMatrixOf< Derived > RootMatrix
The type of a root of the covariance or factor Derived: square, with a row and a column per row of it...
Definition Roots.h:350
constexpr auto pivoted(Fallbacks... fallbacks)
Definition Roots.h:1184
auto along(Eigen::MatrixBase< Derived > const &direction, Fallbacks... fallbacks)
Definition Roots.h:1216
WithFallbacks< Cholesky, Pivoted > DefaultAxes
The axes every root the library takes uses unless told otherwise, cholesky(pivoted()): the Cholesky f...
Definition Roots.h:1254
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
The axes and repairs, each made by its factory (decisions.md [policy-factories]), and the roots taken...
Definition Roots.h:342
Axes along a direction: a root whose first column is parallel to , so that a sigma-point rule puts it...
Definition Roots.h:619
Vector direction
, in the covariance's coordinates
Definition Roots.h:622
std::expected< internal::SquareMatrixOf< Derived >, GaussianError > rootFromFactor(Eigen::MatrixBase< Derived > const &factor) const
Definition Roots.h:642
std::expected< internal::SquareMatrixOf< Derived >, GaussianError > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:629
static constexpr bool canFail
a direction outside the covariance's range has no length
Definition Roots.h:620
Cholesky axes: the lower Cholesky factor , , by Eigen's LLT, which fails on a covariance that is not ...
Definition Roots.h:397
std::expected< internal::SquareMatrixOf< Derived >, GaussianError > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:403
std::expected< internal::SquareMatrixOf< Derived >, GaussianError > rootFromFactor(Eigen::MatrixBase< Derived > const &factor) const
Definition Roots.h:421
static constexpr bool canFail
a covariance that is not positive definite has no Cholesky factor
Definition Roots.h:398
The report of Pivoted, Principal, and Symmetric: how many pivots or eigenvalues were below zero and t...
Definition Roots.h:364
Eigen::Index clamped
the pivots or eigenvalues taken as zero
Definition Roots.h:365
The report of axes or a repair with nothing to say, as Cholesky's: it either took the root or failed.
Definition Roots.h:357
Pivoted axes: from Eigen's pivoted LDL decomposition , with a negative pivot, which rounding can giv...
Definition Roots.h:446
ClampReport Report
The report: the pivots taken as zero.
Definition Roots.h:451
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:456
static constexpr bool canFail
every positive semidefinite part has a root
Definition Roots.h:447
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov, Sink &&sink) const
Definition Roots.h:464
Principal axes: , the eigenvectors of scaled by the roots of their eigenvalues, largest first,...
Definition Roots.h:493
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:503
static constexpr bool canFail
every positive semidefinite part has a root
Definition Roots.h:494
ClampReport Report
The report: the eigenvalues taken as zero.
Definition Roots.h:498
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov, Sink &&sink) const
Definition Roots.h:511
internal::SquareMatrixOf< Derived > rootFromFactor(Eigen::MatrixBase< Derived > const &factor) const
Definition Roots.h:521
The report of a Ridge: the ridge it added to the diagonal.
Definition Roots.h:375
Scalar ridge
Definition Roots.h:376
The ridge repair: the Gershgorin ridge of decisions.md [covariance-repair], added to the diagonal of ...
Definition Roots.h:704
std::expected< typename Derived::PlainObject, GaussianError > repaired(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:717
RidgeReport< Scalar > Report
The report: the ridge added.
Definition Roots.h:712
static constexpr double absolute
Absolute
Definition Roots.h:708
static constexpr double relative
Relative
Definition Roots.h:707
std::expected< typename Derived::PlainObject, GaussianError > repaired(Eigen::MatrixBase< Derived > const &cov, Sink &&sink) const
Definition Roots.h:725
Why squareRootWithReport() failed, and what each entry tried did.
Definition Roots.h:1298
Report report
what each entry did
Definition Roots.h:1300
GaussianError error
the last entry's error
Definition Roots.h:1299
A root and the report of the axes that took it, from squareRootWithReport().
Definition Roots.h:1286
Report report
what the axes did
Definition Roots.h:1288
Matrix root
,
Definition Roots.h:1287
Symmetric axes: the principal square root , the one symmetric positive semidefinite root,...
Definition Roots.h:552
internal::SquareMatrixOf< Derived > rootFromFactor(Eigen::MatrixBase< Derived > const &factor) const
Definition Roots.h:582
ClampReport Report
The report: the eigenvalues taken as zero.
Definition Roots.h:557
static constexpr bool canFail
every positive semidefinite part has a root
Definition Roots.h:553
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:562
internal::SquareMatrixOf< Derived > root(Eigen::MatrixBase< Derived > const &cov, Sink &&sink) const
Definition Roots.h:570
Axes that can fail, followed by their fallbacks, tried in order (decisions.md [sigma-point-axes]): a ...
Definition Roots.h:917
internal::RootResult< canFail, internal::SquareMatrixOf< Derived > > rootFromFactor(Eigen::MatrixBase< Derived > const &factor) const
Definition Roots.h:1036
internal::RootResult< canFail, internal::SquareMatrixOf< Derived > > root(Eigen::MatrixBase< Derived > const &cov, Sink &&sink) const
Definition Roots.h:962
std::variant< internal::ReportOfT< Head, Scalar >, internal::ReportOfT< Fallbacks, Scalar >... > Report
The report of the entry that succeeded, at its index.
Definition Roots.h:936
internal::RootResult< canFail, internal::SquareMatrixOf< Derived > > root(Eigen::MatrixBase< Derived > const &cov) const
Definition Roots.h:952
static constexpr bool canFail
Definition Roots.h:931
std::tuple< Fallbacks... > fallbacks
Definition Roots.h:945
std::tuple< std::optional< internal::ReportOfT< Head, Scalar > >, std::optional< internal::ReportOfT< Fallbacks, Scalar > >... > FailureReport
The report of every entry, on failure.
Definition Roots.h:940