EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Linearization.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <cmath>
13#include <concepts>
14#include <expected>
15#include <limits>
16#include <optional>
17#include <type_traits>
18#include <utility>
19
20#include <Eigen/Cholesky>
21
28
29namespace Emrys
30{
31
42template <typename T>
43concept UncertainInput = requires(T const & input) {
44 typename T::Scalar;
45 typename T::SpaceType;
46 input.mean();
47 input.space();
48 input.valueDimension();
49 input.differenceDimension();
50} && (requires(T const & input) { input.covarianceLower(); } || requires(T const & input) { input.root(); });
51
52namespace internal
53{
54
59template <UncertainInput Input>
60[[nodiscard]] decltype(auto)
61covarianceLowerOf(Input const & input)
62{
63 if constexpr (requires { input.covarianceLower(); })
64 {
65 return input.covarianceLower();
66 }
67 else
68 {
69 using Matrix = std::remove_cvref_t<decltype(input.root())>;
70 Matrix result = multiply(asTriangular<Eigen::Lower>(input.root()), input.root().transpose());
71 return result;
72 }
73}
74
79template <UncertainInput Prior>
80[[nodiscard]] decltype(auto)
81withCovariance(Prior const & prior)
82{
83 if constexpr (requires { prior.covarianceLower(); })
84 {
85 return (prior);
86 }
87 else
88 {
89 using Result = GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>;
90 return Result{
91 prior.space(),
92 typename Result::VectorType{prior.mean()},
93 typename Result::MatrixType{covarianceLowerOf(prior)}
94 };
95 }
96}
97
98} // namespace internal
99
108{};
109
119{};
120
121namespace internal
122{
123
125template <typename T>
126inline constexpr bool isLinearizationForm = std::is_same_v<T, JacobianForm> || std::is_same_v<T, RootForm>;
127
131template <typename InputSpace, typename OutputSpace, typename Scalar>
132using CrossMatrixOf = MatrixOf<
133 Scalar,
138
142template <typename Derived>
143[[nodiscard]] bool
144isInvertibleLowerTriangular(Eigen::MatrixBase<Derived> const & root)
145{
146 using Scalar = typename Derived::Scalar;
147 for (Eigen::Index j = 0; j < root.cols(); ++j)
148 {
149 if (!(root(j, j) > Scalar(0)))
150 {
151 return false;
152 }
153 for (Eigen::Index i = 0; i < j; ++i)
154 {
155 if (root(i, j) != Scalar(0))
156 {
157 return false;
158 }
159 }
160 }
161 return true;
162}
163
175template <typename Result, typename Root, typename Whitened>
176[[nodiscard]] Result
177solveWithRoot(Eigen::MatrixBase<Root> const & root, Eigen::MatrixBase<Whitened> const & whitened)
178{
179 using RootMatrix = typename Root::PlainObject;
180 Result result = whitened;
181 // MSVC can't form an empty solve.
182 if constexpr (Root::RowsAtCompileTime != 0)
183 {
184 if (isInvertibleLowerTriangular(root))
185 {
186 root.template triangularView<Eigen::Lower>().transpose().solveInPlace(result);
187 }
188 else
189 {
190 auto const ldlt = Eigen::LDLT<RootMatrix>{RootMatrix{multiply(root, root.transpose())}};
191 result.noalias() = multiply(root, whitened);
192 solveInPlace(ldlt, result);
193 }
194 }
195 return result;
196}
197
198} // namespace internal
199
235template <typename InputSpace, typename OutputSpace, typename Form, typename Scalar = double>
237{
238 static_assert(internal::isLinearizationForm<Form>);
239
241 static constexpr bool IsRoot = std::is_same_v<Form, RootForm>;
242
243public:
244
246 using FormType = Form;
247
250
253
256
258 using CrossMatrix = internal::CrossMatrixOf<InputSpace, OutputSpace, Scalar>;
259
261 using JacobianMatrix = internal::MatrixOf<
262 Scalar,
267
269 using RootMatrix = internal::MatrixOf<
270 Scalar,
275
279 using FactorMatrix = std::conditional_t<IsRoot, RootMatrix, JacobianMatrix>;
280
287 template <
288 MatrixExpression Mean,
289 MatrixExpression Factor,
290 MatrixExpression Cross,
291 MatrixExpression Explained,
292 MatrixExpression Inputs,
293 MatrixExpression Unexplained>
295 OutputSpace space,
296 Mean && mean,
297 Factor && factor,
298 Cross && cross,
299 Explained && explained,
300 Inputs && inputs,
301 Unexplained && unexplained
302 )
303 : m_explained{std::forward<Explained>(explained)}
304 , m_inputs{std::forward<Inputs>(inputs)}
305 , m_unexplained{std::forward<Unexplained>(unexplained)}
306 , m_output{std::move(space), std::forward<Mean>(mean), OutputMatrix{m_explained + m_inputs + m_unexplained}}
307 , m_factor{std::forward<Factor>(factor)}
308 , m_cross{std::forward<Cross>(cross)}
309 {
310 EMRYS_FILTER_ASSERT(
311 m_cross.cols() == m_output.differenceDimension() && m_factor.cols() == m_cross.rows()
312 && "the linearization must have a row per input coordinate and a column per output coordinate"
313 );
314 }
315
317 [[nodiscard]] OutputType const &
318 output() const &
319 {
320 return m_output;
321 }
322
324 [[nodiscard]] OutputType
326 {
327 return std::move(m_output);
328 }
329
331 [[nodiscard]] OutputMatrix const &
333 {
334 return m_explained;
335 }
336
338 [[nodiscard]] OutputMatrix const &
340 {
341 return m_inputs;
342 }
343
349 [[nodiscard]] OutputMatrix const &
351 {
352 return m_unexplained;
353 }
354
358 [[nodiscard]] CrossMatrix const &
360 requires(!IsRoot)
361 {
362 return m_cross;
363 }
364
368 [[nodiscard]] CrossMatrix
370 requires IsRoot
371 {
372 return internal::multiply(m_factor, m_cross);
373 }
374
376 [[nodiscard]] JacobianMatrix const &
377 jacobian() const
378 requires(!IsRoot)
379 {
380 return m_factor;
381 }
382
386 [[nodiscard]] JacobianMatrix
387 jacobian() const
388 requires IsRoot
389 {
390 return internal::solveWithRoot<CrossMatrix>(m_factor, m_cross).transpose();
391 }
392
394 [[nodiscard]] RootMatrix const &
395 root() const
396 requires IsRoot
397 {
398 return m_factor;
399 }
400
402 [[nodiscard]] CrossMatrix const &
404 requires IsRoot
405 {
406 return m_cross;
407 }
408
409private:
410
411 OutputMatrix m_explained;
412 OutputMatrix m_inputs;
413 OutputMatrix m_unexplained;
414 OutputType m_output;
415 FactorMatrix m_factor;
416 CrossMatrix m_cross;
417};
418
419namespace internal
420{
421
423template <typename Scalar>
424struct InnovationStatistics
425{
426 Scalar nis;
427 Scalar logDeterminant;
428};
429
443template <typename R, typename Matrix>
444[[nodiscard]] std::expected<Eigen::LLT<std::remove_const_t<Matrix>>, GaussianError>
445innovationCholesky(Matrix & cov)
446{
447 auto llt = Eigen::LLT<std::remove_const_t<Matrix>>{cov};
448 if (llt.info() == Eigen::Success)
449 {
450 return llt;
451 }
452 if constexpr (!std::is_same_v<R, NoRepair>)
453 {
454 static_assert(!std::is_const_v<Matrix>, "a repaired innovation covariance replaces the one given");
455 auto repaired = R{}.repaired(cov);
456 if (!repaired)
457 {
458 return std::unexpected{repaired.error()};
459 }
460 llt.compute(*repaired);
461 if (llt.info() != Eigen::Success)
462 {
463 return std::unexpected{GaussianError::NotPositiveDefinite};
464 }
465 cov = std::move(*repaired);
466 return llt;
467 }
468 else
469 {
470 return std::unexpected{GaussianError::NotPositiveDefinite};
471 }
472}
473
478template <typename Matrix, typename Residual>
479[[nodiscard]] InnovationStatistics<typename Matrix::Scalar>
480innovationStatistics(Eigen::LLT<Matrix> const & llt, Eigen::MatrixBase<Residual> const & residual)
481{
482 using Scalar = typename Matrix::Scalar;
483 typename Residual::PlainObject whitened = residual;
484 llt.matrixL().solveInPlace(whitened);
485 return {whitened.squaredNorm(), Scalar(2) * llt.matrixLLT().diagonal().array().log().sum()};
486}
487
491struct InnovationInPlace
492{};
493
499struct AcceptEvery
500{
502 template <typename Scalar>
503 [[nodiscard]] constexpr bool
504 operator()(Scalar /*nis*/, Eigen::Index /*dimension*/) const
505 {
506 return true;
507 }
508};
509
511template <typename Accept>
512inline constexpr bool acceptsByNis = !std::is_same_v<Accept, AcceptEvery>;
513
514} // namespace internal
515
534template <typename Space, typename Scalar = double>
536{
537public:
538
541
544
548 template <MatrixExpression Residual, MatrixExpression Cov>
550 Residual && residual,
551 Cov && cov,
552 std::optional<internal::InnovationStatistics<Scalar>> statistics = std::nullopt
553 )
554 : m_residual{std::forward<Residual>(residual)}
555 , m_cov{std::forward<Cov>(cov)}
556 , m_statistics{statistics}
557 {
558 EMRYS_FILTER_ASSERT(
559 m_cov.rows() == m_residual.size() && m_cov.cols() == m_residual.size()
560 && "the covariance must have a row per residual coordinate"
561 );
562 }
563
571 template <MatrixExpression Residual, typename MakeCov, typename Condition>
572 Innovation(internal::InnovationInPlace, Residual && residual, MakeCov const & makeCov, Condition const & condition)
573 : m_residual{std::forward<Residual>(residual)}
574 , m_cov{makeCov()}
575 , m_statistics{condition(std::as_const(m_residual), m_cov)}
576 {
577 EMRYS_FILTER_ASSERT(
578 m_cov.rows() == m_residual.size() && m_cov.cols() == m_residual.size()
579 && "the covariance must have a row per residual coordinate"
580 );
581 }
582
584 [[nodiscard]] VectorType const &
585 residual() const
586 {
587 return m_residual;
588 }
589
591 [[nodiscard]] MatrixType const &
593 {
594 return m_cov;
595 }
596
598 [[nodiscard]] Eigen::Index
600 {
601 return m_residual.size();
602 }
603
607 [[nodiscard]] Scalar
608 nis() const
609 {
610 return statistics().nis;
611 }
612
614 [[nodiscard]] Scalar
616 {
617 internal::InnovationStatistics<Scalar> const & known = statistics();
618 return Scalar(-0.5) * (Scalar(differenceDimension()) * log2Pi<Scalar>() + known.logDeterminant + known.nis);
619 }
620
621private:
622
624 [[nodiscard]] internal::InnovationStatistics<Scalar> const &
625 statistics() const
626 {
627 if (!m_statistics)
628 {
629 auto const llt = Eigen::LLT<MatrixType>{m_cov};
630 EMRYS_FILTER_ASSERT(llt.info() == Eigen::Success && "the innovation covariance must be positive definite");
631 m_statistics = internal::innovationStatistics(llt, m_residual);
632 }
633 return *m_statistics;
634 }
635
636 VectorType m_residual;
637 MatrixType m_cov;
638
640 mutable std::optional<internal::InnovationStatistics<Scalar>> m_statistics;
641};
642
643} // namespace Emrys
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Solving by a factorization of a small matrix: transposedSolve() gives , the shape of a Kalman gain an...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
A Gaussian distribution: a mean and covariance in a space.
Definition Gaussian.h:259
typename MomentsType::MatrixType MatrixType
Definition Gaussian.h:267
typename MomentsType::VectorType VectorType
Definition Gaussian.h:265
typename MomentsType::DifferenceVectorType DifferenceVectorType
a difference, such as a residual
Definition Gaussian.h:266
VectorType const & residual() const
Definition Linearization.h:585
Eigen::Index differenceDimension() const
Definition Linearization.h:599
typename GaussianParams< Space, Scalar >::MatrixType MatrixType
the covariance's type
Definition Linearization.h:543
MatrixType const & covarianceLower() const
Definition Linearization.h:592
Innovation(Residual &&residual, Cov &&cov, std::optional< internal::InnovationStatistics< Scalar > > statistics=std::nullopt)
The innovation with residual residual and covariance cov, moving either one passed as an rvalue of th...
Definition Linearization.h:549
Innovation(internal::InnovationInPlace, Residual &&residual, MakeCov const &makeCov, Condition const &condition)
The innovation with residual residual and the covariance makeCov returns, made where the innovation k...
Definition Linearization.h:572
Scalar nis() const
Definition Linearization.h:608
typename GaussianParams< Space, Scalar >::DifferenceVectorType VectorType
the residual's type
Definition Linearization.h:540
Scalar logLikelihood() const
Definition Linearization.h:615
JacobianMatrix const & jacobian() const
Definition Linearization.h:377
std::conditional_t< IsRoot, RootMatrix, JacobianMatrix > FactorMatrix
What the form holds beside its cross-covariance: in the Jacobian form, in the root form.
Definition Linearization.h:279
OutputMatrix const & unexplainedCovariance() const
Definition Linearization.h:350
CrossMatrix crossCovariance() const
Definition Linearization.h:369
internal::CrossMatrixOf< InputSpace, OutputSpace, Scalar > CrossMatrix
The cross-covariance's type, and 's: input differences by output differences.
Definition Linearization.h:258
CrossMatrix const & whitenedCross() const
Definition Linearization.h:403
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< InputSpace > >, valueDimensionAtCompileTime< DifferenceSpace< InputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< InputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< InputSpace > > > RootMatrix
The root's type: over the input's differences.
Definition Linearization.h:269
OutputMatrix const & explainedCovariance() const
Definition Linearization.h:332
OutputType output() &&
Definition Linearization.h:325
JacobianMatrix jacobian() const
Definition Linearization.h:387
OutputMatrix const & inputsCovariance() const
Definition Linearization.h:339
GaussianParams< OutputSpace, Scalar > OutputType
The output's distribution.
Definition Linearization.h:249
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< OutputSpace > >, valueDimensionAtCompileTime< DifferenceSpace< InputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< OutputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< InputSpace > > > JacobianMatrix
The Jacobian's type: output differences by input differences.
Definition Linearization.h:261
OutputWithCross(OutputSpace space, Mean &&mean, Factor &&factor, Cross &&cross, Explained &&explained, Inputs &&inputs, Unexplained &&unexplained)
The prediction in space with mean mean, normalized, and the parts of its linearization: factor,...
Definition Linearization.h:294
typename OutputType::MatrixType OutputMatrix
A covariance over the output's differences.
Definition Linearization.h:255
OutputType const & output() const &
Definition Linearization.h:318
Form FormType
JacobianForm or RootForm.
Definition Linearization.h:246
CrossMatrix const & crossCovariance() const
Definition Linearization.h:359
typename OutputType::VectorType OutputVector
An output value.
Definition Linearization.h:252
RootMatrix const & root() const
Definition Linearization.h:395
A dense matrix or matrix expression, possibly cv- or reference-qualified: what a const Eigen::MatrixB...
Definition Forwarding.h:39
An argument of transform() that is uncertain: something with a mean, a space, and either a covariance...
Definition Linearization.h:43
Scalar log2Pi()
Definition Math.h:177
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
NumericError GaussianError
Reasons a Gaussian, in any of its forms (decisions.md [gaussian-forms]), can fail to give a density,...
Definition NumericError.h:45
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 Residual< Inner > residual(Inner inner={})
Definition Resampling.h:544
constexpr int valueDimensionAtCompileTime
The value dimension of Space at compile time, or Eigen::Dynamic if it is resizable.
Definition Space.h:251
constexpr int maxValueDimensionAtCompileTime
The largest valid value dimension of Space, or Eigen::Dynamic if it is unbounded.
Definition Space.h:258
auto condition(Prior const &prior, OutputWithCross< typename Prior::SpaceType, OutputSpace, LinearizationForm, typename Prior::Scalar > const &predicted, Eigen::MatrixBase< Derived > const &observed, Form const &form={})
Definition Condition.h:676
The Jacobian form of a linearization (decisions.md [linearization-forms]), which methods::linearized(...
Definition Linearization.h:108
A model's output space given by its instance, or computed from the state's space, as outputSpace() de...
Definition Policies.h:681
The root form of a linearization (decisions.md [linearization-forms]), which every sigma-point method...
Definition Linearization.h:119