EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Condition.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <array>
13#include <cstddef>
14#include <expected>
15#include <optional>
16#include <tuple>
17#include <type_traits>
18#include <utility>
19
20#include <Eigen/Cholesky>
21
30
31namespace Emrys
32{
33
41template <typename Space, typename OutputSpace, typename Scalar = double>
54
60namespace updates
61{
62
75{};
76
80[[nodiscard]] constexpr Standard
82{
83 return {};
84}
85
94{};
95
99[[nodiscard]] constexpr Symmetric
101{
102 return {};
103}
104
125struct Joseph
126{};
127
131[[nodiscard]] constexpr Joseph
133{
134 return {};
135}
136
137} // namespace updates
138
142template <typename T>
144 std::is_same_v<T, updates::Standard> || std::is_same_v<T, updates::Symmetric> || std::is_same_v<T, updates::Joseph>;
145
155template <typename InputSpace, typename OutputSpace, typename Scalar = double>
184
185namespace internal
186{
187
189template <typename Jacobian, typename Noise>
190struct JosephTerms
191{
192 Jacobian jacobian;
193 Noise noise;
194};
195
200template <typename Root, typename Whitened, typename Noise>
201struct JosephRootTerms
202{
203 Root root;
204 Whitened whitened;
205 Noise noise;
206};
207
211template <typename Matrix, typename Gain, typename Cross, typename OutputCov, typename Linearize>
212[[nodiscard]] Matrix
213posteriorCov(
214 updates::Standard,
215 Matrix const & cov,
216 Gain const & gain,
217 Cross const & cross,
218 OutputCov const & /*outputCov*/,
219 Linearize const & /*linearize*/
220)
221{
222 return Matrix{cov - multiply(gain, cross.transpose())};
223}
224
229template <typename Matrix, typename Gain, typename Cross, typename OutputCov, typename Linearize>
230[[nodiscard]] Matrix
231posteriorCov(
232 updates::Symmetric,
233 Matrix const & cov,
234 Gain const & gain,
235 Cross const & /*cross*/,
236 OutputCov const & outputCov,
237 Linearize const & /*linearize*/
238)
239{
240 using Scalar = typename Matrix::Scalar;
241 OutputCov const symmetric = outputCov.template selfadjointView<Eigen::Lower>();
242 Matrix result = cov - multiply(multiply(gain, symmetric), gain.transpose());
243 return Matrix{Scalar(0.5) * (result + result.transpose())};
244}
245
253template <typename Matrix, typename Gain, typename Cross, typename OutputCov, typename Linearize>
254[[nodiscard]] Matrix
255posteriorCov(
256 updates::Joseph,
257 Matrix const & cov,
258 Gain const & gain,
259 Cross const & /*cross*/,
260 OutputCov const & /*outputCov*/,
261 Linearize const & linearize
262)
263{
264 using Scalar = typename Matrix::Scalar;
265 auto const terms = linearize();
266 Matrix result = multiply(multiply(gain, terms.noise), gain.transpose());
267 if constexpr (requires { terms.root; })
268 {
269 Matrix const factor = terms.root - multiply(gain, terms.whitened.transpose());
270 result.noalias() += multiply(factor, factor.transpose());
271 }
272 else
273 {
274 Matrix const factor = Matrix::Identity(cov.rows(), cov.cols()) - multiply(gain, terms.jacobian);
275 result.noalias() += multiply(multiply(factor, cov), factor.transpose());
276 }
277 return Matrix{Scalar(0.5) * (result + result.transpose())};
278}
279
284template <std::size_t K>
285using ConsideredRows = std::array<BlockRows, K>;
286
288using NoConsidered = ConsideredRows<0>;
289
293template <typename Gain, std::size_t K>
294void
295zeroConsideredRows(Gain & gain, ConsideredRows<K> const & considered)
296{
297 for (BlockRows const rows : considered)
298 {
299 gain.middleRows(rows.start, rows.size).setZero();
300 }
301}
302
306template <typename Matrix, std::size_t K>
307void
308keepConsideredCovariance(Matrix & cov, Matrix const & prior, ConsideredRows<K> const & considered)
309{
310 for (BlockRows const a : considered)
311 {
312 for (BlockRows const b : considered)
313 {
314 cov.block(a.start, b.start, a.size, b.size) = prior.block(a.start, b.start, a.size, b.size);
315 }
316 }
317}
318
320template <typename Space, typename Scalar>
321struct KalmanConditioned
322{
323 GaussianParams<Space, Scalar> posterior;
324 std::optional<InnovationStatistics<Scalar>> statistics;
325};
326
330template <typename Scalar>
331struct ConditionedInPlace
332{
333 std::optional<InnovationStatistics<Scalar>> statistics;
334 bool accepted = true;
335};
336
369template <
370 bool Statistics,
371 typename Repair,
372 typename Space,
373 typename Scalar,
374 typename Residual,
375 typename OutputMatrix,
376 typename Cross,
377 typename Form,
378 typename Linearize,
379 std::size_t K,
380 typename Accept = AcceptEvery>
381[[nodiscard]] std::expected<ConditionedInPlace<Scalar>, GaussianError>
382kalmanConditionInPlace(
383 GaussianParams<Space, Scalar> & state,
384 Eigen::MatrixBase<Residual> const & residual,
385 OutputMatrix & outputCov,
386 Cross const & cross,
387 Form const & form,
388 Linearize const & linearize,
389 [[maybe_unused]] ConsideredRows<K> const & considered,
390 Accept const & accept = {}
391)
392{
393 using Matrix = typename GaussianParams<Space, Scalar>::MatrixType;
394 [[maybe_unused]] Eigen::Index const nd = state.differenceDimension();
395 [[maybe_unused]] Eigen::Index const md = outputCov.rows();
396 EMRYS_FILTER_ASSERT(cross.rows() == nd && cross.cols() == md && "the prediction was not made from this prior");
397 EMRYS_FILTER_ASSERT(residual.size() == md);
398
399 auto const factored = innovationCholesky<Repair>(outputCov);
400 if (!factored)
401 {
402 return std::unexpected{factored.error()};
403 }
404 auto const & llt = *factored;
405 auto result = ConditionedInPlace<Scalar>{};
406 if constexpr (Statistics || acceptsByNis<Accept>)
407 {
408 result.statistics = innovationStatistics(llt, residual);
409 }
410 if constexpr (acceptsByNis<Accept>)
411 {
412 if (!accept(result.statistics->nis, md))
413 {
414 result.accepted = false;
415 return result;
416 }
417 }
418
419 // With nothing to condition, only the likelihood remains; MSVC can't form an empty gain.
421 {
422 auto gain = transposedSolve<typename Cross::PlainObject>(llt, cross.transpose());
423 auto & moments = GaussianAccess::moments(state);
424 Matrix & cov = moments.covarianceLower();
425 if constexpr (K > 0)
426 {
427 auto const symmetric = symmetricFromLower<Matrix>(cov);
428 if constexpr (std::is_same_v<Form, updates::Joseph>)
429 {
430 zeroConsideredRows(gain, considered);
431 cov = posteriorCov(form, symmetric, gain, cross, outputCov, linearize);
432 }
433 else
434 {
435 cov = posteriorCov(form, symmetric, gain, cross, outputCov, linearize);
436 zeroConsideredRows(gain, considered);
437 }
438 keepConsideredCovariance(cov, symmetric, considered);
439 }
440 else if constexpr (std::is_same_v<Form, updates::Standard>)
441 {
442 cov.template triangularView<Eigen::StrictlyUpper>() = cov.transpose();
443 cov.noalias() -= multiply(gain, cross.transpose());
444 }
445 else
446 {
447 auto const symmetric = symmetricFromLower<Matrix>(cov);
448 cov = posteriorCov(form, symmetric, gain, cross, outputCov, linearize);
449 }
450 if constexpr (hasIdentityReset<Space>)
451 {
452 moments.mean() = Emrys::plus(state.space(), moments.mean(), multiply(gain, residual));
453 }
454 else
455 {
456 using Vector = typename GaussianParams<Space, Scalar>::VectorType;
457 using Difference = typename GaussianParams<Space, Scalar>::DifferenceVectorType;
458 Vector const start = moments.mean();
459 auto step = Difference{};
460 step.resize(nd);
461 step.noalias() = multiply(gain, residual);
462 moments.mean() = Emrys::plus(state.space(), start, step);
463 resetCovarianceAt(state.space(), start, step, moments.mean(), cov);
464 }
465 }
466 return result;
467}
468
478template <
479 bool Statistics,
480 typename Repair,
481 typename Prior,
482 typename Residual,
483 typename OutputMatrix,
484 typename Cross,
485 typename Form,
486 typename Linearize,
487 std::size_t K>
488[[nodiscard]] std::expected<KalmanConditioned<typename Prior::SpaceType, typename Prior::Scalar>, GaussianError>
489kalmanCondition(
490 Prior const & prior,
491 Eigen::MatrixBase<Residual> const & residual,
492 OutputMatrix & outputCov,
493 Cross const & cross,
494 Form const & form,
495 Linearize const & linearize,
496 ConsideredRows<K> const & considered
497)
498{
499 using Posterior = GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>;
500 auto result = KalmanConditioned<typename Prior::SpaceType, typename Prior::Scalar>{
501 Posterior{
502 prior.space(),
503 typename Posterior::VectorType{prior.mean()},
504 typename Posterior::MatrixType{covarianceLowerOf(prior)}
505 },
506 std::nullopt
507 };
508 auto conditioned = kalmanConditionInPlace<Statistics, Repair>(
509 result.posterior,
510 residual,
511 outputCov,
512 cross,
513 form,
514 linearize,
515 considered
516 );
517 if (!conditioned)
518 {
519 return std::unexpected{conditioned.error()};
520 }
521 result.statistics = conditioned->statistics;
522 return result;
523}
524
528template <
529 typename Prior,
530 typename OutputSpace,
531 typename LinearizationForm,
532 typename Derived,
533 typename Form,
534 typename Linearize>
535[[nodiscard]] std::expected<Conditioned<typename Prior::SpaceType, OutputSpace, typename Prior::Scalar>, GaussianError>
536kalmanUpdate(
537 Prior const & prior,
538 OutputWithCross<typename Prior::SpaceType, OutputSpace, LinearizationForm, typename Prior::Scalar> const &
539 predicted,
540 Eigen::MatrixBase<Derived> const & observed,
541 Form const & form,
542 Linearize const & linearize
543)
544{
545 using Output = GaussianParams<OutputSpace, typename Prior::Scalar>;
546 Output const & output = predicted.output();
547 EMRYS_FILTER_ASSERT(observed.size() == output.valueDimension());
548 typename Output::DifferenceVectorType residual = Emrys::minus(output.space(), observed, output.mean());
549 auto const & cross = predicted.crossCovariance();
550 auto conditioned = kalmanCondition<false, NoRepair>(
551 prior,
552 residual,
553 output.covarianceLower(),
554 cross,
555 form,
556 linearize,
557 NoConsidered{}
558 );
559 if (!conditioned)
560 {
561 return std::unexpected{conditioned.error()};
562 }
563 return Conditioned<typename Prior::SpaceType, OutputSpace, typename Prior::Scalar>{
564 std::move(conditioned->posterior),
565 {std::move(residual), output.covarianceLower()}
566 };
567}
568
575template <typename Form, typename InputSpace, typename OutputSpace, typename LinearizationForm, typename Scalar>
576[[nodiscard]] auto
577linearizeFor(OutputWithCross<InputSpace, OutputSpace, LinearizationForm, Scalar> const & predicted)
578{
579 if constexpr (std::is_same_v<Form, updates::Joseph>)
580 {
581 using Linearization = StatisticalLinearization<InputSpace, OutputSpace, Scalar>;
582 using Predicted = OutputWithCross<InputSpace, OutputSpace, LinearizationForm, Scalar>;
583 typename Linearization::ErrorMatrix noise = predicted.inputsCovariance() + predicted.unexplainedCovariance();
584 if constexpr (std::is_same_v<LinearizationForm, RootForm>)
585 {
586 using Terms = JosephRootTerms<
587 typename Predicted::RootMatrix const &,
588 typename Predicted::CrossMatrix const &,
589 typename Linearization::ErrorMatrix>;
590 return Terms{predicted.root(), predicted.whitenedCross(), std::move(noise)};
591 }
592 else
593 {
594 return JosephTerms<typename Linearization::JacobianMatrix const &, typename Linearization::ErrorMatrix>{
595 predicted.jacobian(),
596 std::move(noise)
597 };
598 }
599 }
600 else
601 {
602 return std::tuple<>{};
603 }
604}
605
606} // namespace internal
607
633template <typename InputSpace, typename OutputSpace, typename Form, typename Scalar>
636{
638 typename Result::ErrorMatrix errorCov = predicted.inputsCovariance() + predicted.unexplainedCovariance();
639 return Result{typename Result::JacobianMatrix{predicted.jacobian()}, std::move(errorCov)};
640}
641
669template <
670 UncertainInput Prior,
671 typename OutputSpace,
672 typename LinearizationForm,
673 typename Derived,
674 CovarianceUpdate Form = updates::Standard>
675[[nodiscard]] auto
677 Prior const & prior,
679 predicted,
680 Eigen::MatrixBase<Derived> const & observed,
681 Form const & form = {}
682)
683{
684 return internal::kalmanUpdate(prior, predicted, observed, form, [&] {
685 return internal::linearizeFor<Form>(predicted);
686 });
687}
688
689} // namespace Emrys
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
What a transform returns and what a measurement update reads from it (decisions.md [linearization-for...
The macros every header includes first.
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
The covariance reset (decisions.md [covariance-reset]): hasIdentityReset<>, resetJacobian(),...
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...
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
A Gaussian distribution: a mean and covariance in a space.
Definition Gaussian.h:259
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Gaussian.h:267
typename MomentsType::VectorType VectorType
a value, such as the mean
Definition Gaussian.h:265
typename MomentsType::DifferenceVectorType DifferenceVectorType
a difference, such as a residual
Definition Gaussian.h:266
The innovation of an update: the residual of the observation from the predicted output,...
Definition Linearization.h:536
Scalar logLikelihood() const
Definition Linearization.h:615
The result of transform() with an input marked by withCross(): the output's Gaussian and its lineariz...
Definition Linearization.h:237
JacobianMatrix const & jacobian() const
Definition Linearization.h:377
OutputMatrix const & unexplainedCovariance() const
Definition Linearization.h:350
OutputMatrix const & inputsCovariance() const
Definition Linearization.h:339
One of the covariance updates: updates::Standard, updates::Symmetric, or updates::Joseph.
Definition Condition.h:143
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 Residual< Inner > residual(Inner inner={})
Definition Resampling.h:544
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
constexpr bool hasIdentityReset
Whether the reset Jacobian of S is the identity, so that a covariance needs no change when its mean m...
Definition Reset.h:100
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
StatisticalLinearization< InputSpace, OutputSpace, Scalar > statisticalLinearization(OutputWithCross< InputSpace, OutputSpace, Form, Scalar > const &predicted)
Definition Condition.h:635
constexpr Joseph joseph()
Definition Condition.h:132
constexpr Symmetric symmetric()
Definition Condition.h:100
constexpr Standard standard()
Definition Condition.h:81
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 covariance update forms, each made by its factory (decisions.md [policy-factories],...
Definition Condition.h:61
The result of condition(): the posterior, and the innovation, from which the log-likelihood of the ob...
Definition Condition.h:43
Scalar logLikelihood() const
Definition Condition.h:49
GaussianParams< Space, Scalar > posterior
the input conditioned on the observation
Definition Condition.h:44
Innovation< OutputSpace, Scalar > innovation
the observation's residual and its covariance
Definition Condition.h:45
The statistical linearization of an output in an input, from statisticalLinearization(): the affine f...
Definition Condition.h:157
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 Condition.h:159
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< OutputSpace > >, valueDimensionAtCompileTime< DifferenceSpace< OutputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< OutputSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< OutputSpace > > > ErrorMatrix
The error covariance's type: over the output's differences.
Definition Condition.h:167
The Joseph covariance update: , with and from the statistical linearization of the output in the in...
Definition Condition.h:126
The standard covariance update of condition(): , which is written without .
Definition Condition.h:75
The symmetric covariance update: , equal to the standard form when is the optimal gain,...
Definition Condition.h:94