EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
FormSteps.h
Go to the documentation of this file.
1#pragma once
2
12
13#include <cstddef>
14#include <expected>
15#include <limits>
16#include <optional>
17#include <type_traits>
18#include <utility>
19#include <variant>
20
21#include <Eigen/Cholesky>
22#include <Eigen/LU>
23
44
45namespace Emrys
46{
47
48namespace internal
49{
50
55template <typename C>
56inline constexpr bool isOtherGaussianForm = GaussianForm<C> && !isGaussian<C>;
57
63template <Space Space_, RealScalar Scalar_>
64class RootInput
65{
66public:
67
68 using Scalar = Scalar_;
69 using SpaceType = Space_;
70 using MomentsType = Moments<Space_, Scalar_>;
71 using VectorType = typename MomentsType::VectorType;
72 using MatrixType = typename MomentsType::MatrixType;
73
75 static constexpr int ValueDimensionAtCompileTime = MomentsType::ValueDimensionAtCompileTime;
76
78 static constexpr int MaxValueDimensionAtCompileTime = MomentsType::MaxValueDimensionAtCompileTime;
79
81 static constexpr int DifferenceDimensionAtCompileTime = MomentsType::DifferenceDimensionAtCompileTime;
82
84 static constexpr int MaxDifferenceDimensionAtCompileTime = MomentsType::MaxDifferenceDimensionAtCompileTime;
85
89 RootInput(Space_ space, VectorType mean, MatrixType root)
90 : m_space{std::move(space)}
91 , m_mean{std::move(mean)}
92 , m_root{std::move(root)}
93 {}
94
96 [[nodiscard]] Space_ const &
97 space() const
98 {
99 return m_space;
100 }
101
103 [[nodiscard]] VectorType const &
104 mean() const
105 {
106 return m_mean;
107 }
108
110 [[nodiscard]] MatrixType const &
111 root() const
112 {
113 return m_root;
114 }
115
117 [[nodiscard]] MatrixType
118 covarianceLower() const
119 {
120 MatrixType result = multiply(asTriangular<Eigen::Lower>(m_root), m_root.transpose());
121 return result;
122 }
123
125 [[nodiscard]] Eigen::Index
126 valueDimension() const
127 {
128 return Emrys::valueDimension(m_space);
129 }
130
132 [[nodiscard]] Eigen::Index
133 differenceDimension() const
134 {
135 return Emrys::differenceDimension(m_space);
136 }
137
138private:
139
140 EMRYS_FILTER_NO_UNIQUE_ADDRESS Space_ m_space;
141 VectorType m_mean;
142 MatrixType m_root;
143};
144
156template <typename Space, typename MeasurementSpace, typename Scalar>
157struct MeasurementLinearization
158{
159 using SpaceType = Space;
160 using MeasurementSpaceType = MeasurementSpace;
161
163 using Linearization = StatisticalLinearization<Space, MeasurementSpace, Scalar>;
164
166 using JacobianMatrix = typename Linearization::JacobianMatrix;
167
168 using NoiseMatrix = typename Linearization::ErrorMatrix;
169 using MeasurementVector = typename GaussianParams<MeasurementSpace, Scalar>::VectorType;
170 using InnovationVector =
172
173 MeasurementSpace space;
174 MeasurementVector predicted;
175 InnovationVector residual;
176 JacobianMatrix jacobian;
177 NoiseMatrix noise;
178};
179
184template <typename Scalar, typename R, typename Linearization>
185void
186observeLinearization(R const & resolved, Linearization & linearization)
187{
188 if constexpr (R::HasObservation)
189 {
190 linearization.residual = observedResidual<Scalar>(resolved, linearization.space, linearization.predicted);
191 }
192}
193
201template <
202 typename Scalar,
203 typename Space,
204 typename Reads,
205 typename R,
206 typename MeasurementSpace,
207 typename Predicted,
208 typename ReadJacobian,
209 typename Omega>
210[[nodiscard]] MeasurementLinearization<Space, MeasurementSpace, Scalar>
211assembleLinearization(
212 Space const & space,
213 Reads const & reads,
214 R const & resolved,
215 MeasurementSpace const & measurementSpace,
216 Predicted const & predicted,
217 ReadJacobian const & readJacobian,
218 Omega const & omega
219)
220{
221 using Result = MeasurementLinearization<Space, MeasurementSpace, Scalar>;
222 using MeasurementVector = typename Result::MeasurementVector;
223 using InnovationVector = typename Result::InnovationVector;
224 using NoiseMatrix = typename Result::NoiseMatrix;
225 Eigen::Index const m = differenceDimension(measurementSpace);
226 Eigen::Index const n = differenceDimension(space);
227 auto result = Result{
228 measurementSpace,
229 MeasurementVector{},
230 InnovationVector{},
231 Result::JacobianMatrix::Zero(m, n),
232 NoiseMatrix{}
233 };
234 result.jacobian(Eigen::placeholders::all, reads.differenceIndices()) = readJacobian;
235 if constexpr (!R::HasAdditiveNoise)
236 {
237 result.predicted = predicted;
238 result.noise = omega;
239 }
240 else
241 {
242 static_assert(
243 !R::MixtureNoise,
244 "a square-root or information form takes measurement noise of one component; mixture noise, whose "
245 "branches are collapsed by moment matching, is the covariance form's (decisions.md [gaussian-forms])"
246 );
247 auto const & noise = resolved.noise().distribution();
248 result.predicted = Emrys::plus(measurementSpace, predicted, noise.mean());
249 result.noise = omega + symmetricFromLower<NoiseMatrix>(noise.covarianceLower());
250 }
251 result.noise = (Scalar(0.5) * (result.noise + result.noise.transpose())).eval();
252 observeLinearization<Scalar>(resolved, result);
253 return result;
254}
255
262template <typename Scalar, typename Space, typename R, typename Method>
263[[nodiscard]] auto
264linearizeMeasurementAtPoint(
265 Space const & space,
266 typename Moments<Space, Scalar>::VectorType const & point,
267 R const & resolved,
268 Method const & method
269)
270{
271 using Reads = typename R::Reads;
272 using ReadPoint = PointInput<typename Reads::JointSpace, Scalar>;
273 auto const & reads = resolved.reads();
274 auto const read = ReadPoint{reads.space(), typename ReadPoint::VectorType{point(reads.indices())}};
275 auto const f = ResolvedFunction{resolved};
276 static_assert(
277 isLinearMethod<Method>,
278 "a linearization at a point is for a method that linearizes once at the means"
279 );
280 return linearizeMeasurement(resolved, method, f, read, [&](auto const & problem, auto const & linearized) {
281 using Problem = std::remove_cvref_t<decltype(problem)>;
282 using Omega = typename Problem::OutputMatrix;
283 Eigen::Index const k = reads.differenceIndices().size();
284 auto const readJacobian =
285 linearized.jacobian.template middleCols<Reads::DifferenceSize>(problem.differenceOffset(0), k);
286 Omega const omega = inputsCovariance<Omega>(resolved, problem, linearized.jacobian);
287 return assembleLinearization<Scalar>(
288 space,
289 reads,
290 resolved,
291 problem.outputSpace(),
292 linearized.mean,
293 readJacobian,
294 omega
295 );
296 });
297}
298
302template <typename Scalar, typename Space, typename R>
303[[nodiscard]] auto
304linearizeMeasurementAtPoint(
305 Space const & space,
306 typename Moments<Space, Scalar>::VectorType const & point,
307 R const & resolved
308)
309{
310 return linearizeMeasurementAtPoint<Scalar>(space, point, resolved, resolved.method());
311}
312
316template <typename Linearization>
317struct IteratedLinearization
318{
319 Linearization linearization;
320 std::size_t iterations;
321 bool converged;
322};
323
328template <typename Root, unsigned int Mode>
329struct TriangularRoot
330{
331 Root const & root;
332};
333
335template <typename Cov>
336[[nodiscard]] auto
337spreadScale(Eigen::MatrixBase<Cov> const & cov, typename Cov::Scalar tolerance)
338{
339 return iterationScale(cov, tolerance);
340}
341
345template <typename Root, unsigned int Mode>
346[[nodiscard]] auto
347spreadScale(TriangularRoot<Root, Mode> const & spread, typename Root::Scalar tolerance)
348{
349 using Scalar = typename Root::Scalar;
350 EMRYS_FILTER_ASSERT(tolerance >= Scalar(0) && "an iteration's tolerance must not be negative");
351 using Result = Eigen::Matrix<Scalar, Root::RowsAtCompileTime, 1, Eigen::ColMajor, Root::MaxRowsAtCompileTime, 1>;
352 Result result = spread.root.rowwise().norm() * tolerance;
353 return result;
354}
355
357template <typename Cov, typename Indices>
358[[nodiscard]] auto
359readBlockCovariance(Eigen::MatrixBase<Cov> const & cov, Indices const & indices)
360{
361 using Result = MatrixOf<
362 typename Cov::Scalar,
363 Indices::SizeAtCompileTime,
364 Indices::SizeAtCompileTime,
365 Indices::MaxRowsAtCompileTime,
366 Indices::MaxRowsAtCompileTime>;
367 Result result = lowerSymmetricBlock(cov, indices, indices);
368 return result;
369}
370
374template <typename Root, unsigned int Mode, typename Indices>
375[[nodiscard]] auto
376readBlockCovariance(TriangularRoot<Root, Mode> const & spread, Indices const & indices)
377{
378 using Result = MatrixOf<
379 typename Root::Scalar,
380 Indices::SizeAtCompileTime,
381 Indices::SizeAtCompileTime,
382 Indices::MaxRowsAtCompileTime,
383 Indices::MaxRowsAtCompileTime>;
384 auto const rows = spread.root(indices, Eigen::placeholders::all);
385 auto result = Result{};
386 result.resize(indices.size(), indices.size());
387 result.noalias() = multiply(rows, rows.transpose());
388 return result;
389}
390
394template <typename Cov, typename Jacobian, typename Crossed, typename InnovationCov>
395void
396addSpread(
397 Eigen::MatrixBase<Cov> const & cov,
398 Jacobian const & jacobian,
399 Crossed & crossed,
400 InnovationCov & innovationCov
401)
402{
403 crossed.noalias() = multiply(cov, jacobian.transpose());
404 innovationCov.noalias() += multiply(jacobian, crossed);
405}
406
411template <typename Root, unsigned int Mode, typename Jacobian, typename Crossed, typename InnovationCov>
412void
413addSpread(
414 TriangularRoot<Root, Mode> const & spread,
415 Jacobian const & jacobian,
416 Crossed & crossed,
417 InnovationCov & innovationCov
418)
419{
420 using Scaled = std::remove_cvref_t<decltype(jacobian.eval())>;
421 auto scaled = Scaled{};
422 scaled.resize(jacobian.rows(), jacobian.cols());
423 scaled.noalias() = multiply(jacobian, asTriangular<Mode>(spread.root));
424 crossed.noalias() = multiply(asTriangular<Mode>(spread.root), scaled.transpose());
425 innovationCov.noalias() += multiply(scaled, scaled.transpose());
426}
427
435template <typename Scalar, typename Reads, typename R, typename ReadBase, typename Step, typename Linearization>
436void
437toBaseChart(
438 Reads const & reads,
439 R const & resolved,
440 ReadBase const & readBase,
441 Step const & step,
442 Linearization & linearization
443)
444{
446 using ReadJacobian = MatrixOf<
447 Scalar,
448 Linearization::JacobianMatrix::RowsAtCompileTime,
449 Reads::DifferenceSize,
450 Linearization::JacobianMatrix::MaxRowsAtCompileTime,
451 Reads::DifferenceMaxSize>;
452 ReadDifference const readStep = step(reads.differenceIndices());
453 ReadJacobian readJacobian = linearization.jacobian(Eigen::placeholders::all, reads.differenceIndices());
454 toPriorChart(reads, readJacobian, readBase, readStep);
455 linearization.jacobian(Eigen::placeholders::all, reads.differenceIndices()) = readJacobian;
456 auto back = typename Linearization::InnovationVector{};
457 back.resize(linearization.jacobian.rows());
458 back.noalias() = -multiply(linearization.jacobian, step);
459 linearization.predicted = Emrys::plus(linearization.space, linearization.predicted, back);
460 observeLinearization<Scalar>(resolved, linearization);
461}
462
471template <typename Scalar, typename Space, typename R, typename Reference>
472[[nodiscard]] auto
473linearizeMeasurementAbout(
474 Space const & space,
475 typename Moments<Space, Scalar>::VectorType const & base,
476 LinearizedAbout<Reference> const & about,
477 R const & resolved
478)
479{
480 using About = LinearizedAbout<Reference>;
481 using Method = typename R::Method;
482 using Reads = typename R::Reads;
483 using Vector = typename Moments<Space, Scalar>::VectorType;
484 using Difference = typename Moments<Space, Scalar>::DifferenceVectorType;
485 static_assert(checkReference<Method, About, Space>());
486 auto const & reads = resolved.reads();
487 Vector const point = about.mean();
488 EMRYS_FILTER_ASSERT(
489 point.size() == valueDimension(space) && "a point to linearize about must have the prior's value dimension"
490 );
491 auto linearization = [&] {
492 if constexpr (isLinearMethod<Method>)
493 {
494 return linearizeMeasurementAtPoint<Scalar>(space, point, resolved);
495 }
496 else if constexpr (About::IsDistribution)
497 {
498 using Input = RootInput<typename Reads::JointSpace, Scalar>;
499 auto const cov = readBlockCovariance(about.reference.covarianceLower(), reads.differenceIndices());
500 auto const input = Input{
501 reads.space(),
502 typename Input::VectorType{point(reads.indices())},
503 lowerTriangularRoot(defaultRoot(cov))
504 };
505 return linearizeMeasurementAtRoot<Scalar>(space, reads, input, resolved);
506 }
507 }();
509 Difference const step = Emrys::minus(space, point, base);
510 ReadVector const readBase = base(reads.indices());
511 toBaseChart<Scalar>(reads, resolved, readBase, step, linearization);
512 return linearization;
513}
514
528template <typename Scalar, typename Space, typename Spread, typename R>
529[[nodiscard]] auto
530iterateMeasurementLinearization(
531 Space const & space,
532 typename Moments<Space, Scalar>::VectorType const & mean,
533 std::optional<Spread> const & spread,
534 R const & resolved
535)
536{
537 using Reads = typename R::Reads;
538 using Vector = typename Moments<Space, Scalar>::VectorType;
539 using Difference = typename Moments<Space, Scalar>::DifferenceVectorType;
542 auto const & method = resolved.method();
543 auto const & reads = resolved.reads();
544 ReadVector const readMean = mean(reads.indices());
545 Difference delta = Difference::Zero(differenceDimension(space));
546 std::size_t iteration = 0;
547 bool converged = false;
548
549 // A sigma-point method spreads its points by a lower-triangular root of the posterior covariance
550 // of the blocks read at the iterate, in the iterate's chart, the prior's at first [sigma-point-sets]. A linear
551 // method reads no covariance, so it keeps none.
552 using Inner = std::remove_cvref_t<decltype(method.method)>;
553 constexpr bool Spreads = !isLinearMethod<Inner>;
554 using ReadMatrix = MatrixOf<
555 Scalar,
556 Reads::DifferenceSize,
557 Reads::DifferenceSize,
558 Reads::DifferenceMaxSize,
559 Reads::DifferenceMaxSize>;
560 using SpreadMatrix = std::conditional_t<Spreads, ReadMatrix, std::monostate>;
561 auto priorReadCov = SpreadMatrix{};
562 auto spreadRoot = SpreadMatrix{};
563 if constexpr (Spreads)
564 {
565 EMRYS_FILTER_ASSERT(spread && "a sigma-point method needs the prior's covariance to spread its points");
566 priorReadCov = readBlockCovariance(*spread, reads.differenceIndices());
567 spreadRoot = lowerTriangularRoot(defaultRoot(priorReadCov));
568 }
569
570 // The linearization about the iterate mu + delta: by Linearized at it, or by a sigma-point method
571 // over its posterior (spreadRoot). Its own lambda, since MSVC rejects a lambda inside one whose
572 // call decltype names.
573 auto const linearizeAtIterate = [&] {
574 if constexpr (Spreads)
575 {
576 auto const input = RootInput<typename Reads::JointSpace, Scalar>{
577 reads.space(),
578 iteration == 0 ? readMean
579 : ReadVector{Emrys::plus(reads.space(), readMean, delta(reads.differenceIndices()))},
580 spreadRoot
581 };
582 return linearizeMeasurementAtRoot<Scalar>(space, reads, input, resolved, method.method);
583 }
584 else
585 {
586 return linearizeMeasurementAtPoint<Scalar>(
587 space,
588 iteration == 0 ? mean : Vector{Emrys::plus(space, mean, delta)},
589 resolved,
590 method.method
591 );
592 }
593 };
594
595 // That linearization about delta = 0 in the prior's chart.
596 auto const atIterate = [&] {
597 auto result = linearizeAtIterate();
598 if (iteration > 0)
599 {
600 toBaseChart<Scalar>(reads, resolved, readMean, delta, result);
601 }
602 return result;
603 };
604
605 using Linearization = decltype(atIterate());
606 using Result = std::expected<IteratedLinearization<Linearization>, GaussianError>;
607 if (!spread)
608 {
609 return Result{IteratedLinearization<Linearization>{atIterate(), 1, false}};
610 }
611 using Gain = MatrixOf<
612 Scalar,
613 Linearization::JacobianMatrix::ColsAtCompileTime,
614 Linearization::JacobianMatrix::RowsAtCompileTime,
615 Linearization::JacobianMatrix::MaxColsAtCompileTime,
616 Linearization::JacobianMatrix::MaxRowsAtCompileTime>;
617 using NoiseMatrix = typename Linearization::NoiseMatrix;
618 auto const scale = spreadScale(*spread, Scalar(method.tolerance));
619 // A step that moved too little to count stops the iteration at the linearization after it.
620 for (; iteration + 1 < method.maxIterations && !converged; ++iteration)
621 {
622 Linearization const linearization = atIterate();
623 Gain crossed(delta.size(), linearization.residual.size());
624 NoiseMatrix innovationCov = linearization.noise;
625 addSpread(*spread, linearization.jacobian, crossed, innovationCov);
626 auto const llt = Eigen::LLT<NoiseMatrix>{innovationCov};
627 if (llt.info() != Eigen::Success)
628 {
629 return Result{std::unexpected{GaussianError::NotPositiveDefinite}};
630 }
631 auto next = Difference{};
632 next.resize(delta.size());
633 next.noalias() = multiply(crossed, llt.solve(linearization.residual));
634 if constexpr (Spreads)
635 {
636 // The posterior covariance of the blocks read, P_SS - C_S S^-1 C_S^T with C_S the rows of
637 // P H^T at them, carried to the next iterate's chart.
638 auto const readCross = std::as_const(crossed)(reads.differenceIndices(), Eigen::placeholders::all);
639 using ReadCross = MatrixOf<
640 Scalar,
641 Reads::DifferenceSize,
642 Gain::ColsAtCompileTime,
643 Reads::DifferenceMaxSize,
644 Gain::MaxColsAtCompileTime>;
645 ReadMatrix posterior = priorReadCov;
646 posterior.noalias() -=
647 multiply(transposedSolve<ReadCross>(llt, readCross.transpose()), readCross.transpose());
649 {
650 ReadDifference const nextReadStep = next(reads.differenceIndices());
651 auto const reset = resetJacobian(reads.space(), readMean, nextReadStep);
652 posterior = multiply(multiply(reset, posterior), reset.transpose()).eval();
653 }
654 spreadRoot = lowerTriangularRoot(defaultRoot(posterior));
655 }
656 converged = withinScale(next, delta, scale);
657 delta = next;
658 }
659 return Result{IteratedLinearization<Linearization>{atIterate(), iteration + 1, converged}};
660}
661
665template <typename Update, typename Iterated>
666[[nodiscard]] Update
667withIterations(Update update, Iterated const & iterated)
668{
669 auto const report = [&](auto & filterUpdate) {
670 for (auto & branch : filterUpdate.branches)
671 {
672 branch.iterations = iterated.iterations;
673 branch.converged = iterated.converged;
674 }
675 };
676 if constexpr (requires { update.has_value(); })
677 {
678 if (update)
679 {
680 report(*update);
681 }
682 }
683 else
684 {
685 report(update);
686 }
687 return update;
688}
689
699template <typename Scalar, typename Space, typename Reads, typename Read, typename R, typename Method>
700[[nodiscard]] auto
701linearizeMeasurementAtRoot(
702 Space const & space,
703 Reads const & reads,
704 Read const & read,
705 R const & resolved,
706 Method const & method
707)
708{
709 auto const f = ResolvedFunction{resolved};
710 auto const predicted = transformMeasurement(resolved, method, f, read);
711 // H_S and N + Omega from the prediction's parts: a solve with the root that spread the points, the
712 // read blocks' own factor where they hold one, and no subtraction (decisions.md [linearization-forms]).
713 auto const [jacobian, omega] = statisticalLinearization(predicted);
714 return assembleLinearization<Scalar>(
715 space,
716 reads,
717 resolved,
718 predicted.output().space(),
719 predicted.output().mean(),
720 jacobian,
721 omega
722 );
723}
724
726template <typename Scalar, typename Space, typename Reads, typename Read, typename R>
727[[nodiscard]] auto
728linearizeMeasurementAtRoot(Space const & space, Reads const & reads, Read const & read, R const & resolved)
729{
730 return linearizeMeasurementAtRoot<Scalar>(space, reads, read, resolved, resolved.method());
731}
732
736template <typename Scalar, typename Space, typename M>
737using LinearizationOf = MeasurementLinearization<Space, typename ResolvedMeasurement<M, Space>::Out, Scalar>;
738
742template <typename Matrix>
743[[nodiscard]] Matrix
744infiniteCovariance(Eigen::Index m)
745{
746 Matrix result = Matrix::Zero(m, m);
747 result.diagonal().setConstant(std::numeric_limits<typename Matrix::Scalar>::infinity());
748 return result;
749}
750
754template <typename MeasurementSpace, typename Scalar>
755[[nodiscard]] FilterUpdate<MeasurementSpace, 1, Scalar>
756oneBranch(Innovation<MeasurementSpace, Scalar> innovation, bool accepted = true)
757{
758 return FilterUpdate<MeasurementSpace, 1, Scalar>{
759 {FilterBranch<MeasurementSpace, Scalar>{Scalar(0), std::move(innovation), 0, 0}},
760 Scalar(0),
761 accepted
762 };
763}
764
787template <typename Space, typename Scalar, typename MeasurementSpace, typename Accept = AcceptEvery>
788[[nodiscard]] std::expected<FilterUpdate<MeasurementSpace, 1, Scalar>, GaussianError>
789foldIntoSqrt(
790 SqrtGaussianParams<Space, Scalar> & state,
791 MeasurementLinearization<Space, MeasurementSpace, Scalar> const & linearization,
792 Accept const & accept = {}
793)
794{
795 using Form = SqrtGaussianParams<Space, Scalar>;
796 using InnovationGaussian = GaussianParams<MeasurementSpace, Scalar>;
797 constexpr int N = Form::DifferenceDimensionAtCompileTime;
798 constexpr int NMax = Form::MaxDifferenceDimensionAtCompileTime;
799 constexpr int M = InnovationGaussian::DifferenceDimensionAtCompileTime;
800 constexpr int MMax = InnovationGaussian::MaxDifferenceDimensionAtCompileTime;
801 using Joint = MatrixOf<Scalar, sizeSum<M, N>, sizeSum<M, N>, sizeSum<MMax, NMax>, sizeSum<MMax, NMax>>;
802 using GainFactor = MatrixOf<Scalar, N, M, NMax, MMax>;
803 auto const & space = state.space();
804 Eigen::Index const n = state.differenceDimension();
805 Eigen::Index const m = linearization.residual.size();
806 auto const & factor = state.root();
807
808 Joint array(m + n, m + n);
809 array.topLeftCorner(m, n).noalias() = multiply(linearization.jacobian, asTriangular<Eigen::Lower>(factor));
810 array.topRightCorner(m, m) = defaultRoot(linearization.noise);
811 array.bottomLeftCorner(n, n) = factor;
812 array.bottomRightCorner(n, m).setZero();
813 Joint const lower = lowerTriangularRoot(array);
814 auto const innovationFactor = blockOf<M, M>(lower, 0, 0, m, m);
815 if ((innovationFactor.diagonal().array() == Scalar(0)).any())
816 {
817 return std::unexpected{GaussianError::NotPositiveDefinite};
818 }
819 GainFactor const gainFactor = lower.block(m, 0, n, m);
820
821 typename InnovationGaussian::DifferenceVectorType whitened = linearization.residual;
822 innovationFactor.template triangularView<Eigen::Lower>().solveInPlace(whitened);
823 auto const statistics = InnovationStatistics<Scalar>{
824 whitened.squaredNorm(),
825 Scalar(2) * innovationFactor.diagonal().array().abs().log().sum()
826 };
827 typename InnovationGaussian::MatrixType innovationCov = multiply(innovationFactor, innovationFactor.transpose());
828 if constexpr (acceptsByNis<Accept>)
829 {
830 if (!accept(statistics.nis, m))
831 {
832 return oneBranch(
833 Innovation<MeasurementSpace, Scalar>{linearization.residual, std::move(innovationCov), statistics},
834 false
835 );
836 }
837 }
838
839 typename Form::DifferenceVectorType const step = multiply(gainFactor, whitened);
840 typename Form::VectorType const start = state.mean();
841 typename Form::MatrixType posterior = lower.bottomRightCorner(n, n);
842 if constexpr (!hasIdentityReset<Space>)
843 {
844 auto const g = resetJacobian(space, start, step);
845 posterior = lowerTriangularRoot(typename Form::MatrixType{multiply(g, posterior)});
846 }
847 state.setMean(Emrys::plus(space, start, step));
848 state.setRoot(std::move(posterior));
849 return oneBranch(
850 Innovation<MeasurementSpace, Scalar>{linearization.residual, std::move(innovationCov), statistics}
851 );
852}
853
858template <typename Reads, typename Space, typename Scalar>
859[[nodiscard]] RootInput<typename Reads::JointSpace, Scalar>
860readRoot(Reads const & reads, SqrtGaussianParams<Space, Scalar> const & state)
861{
862 using Result = RootInput<typename Reads::JointSpace, Scalar>;
863 using Rows = MatrixOf<
864 Scalar,
865 Reads::DifferenceSize,
867 Reads::DifferenceMaxSize,
869 Rows const rows = state.root()(reads.differenceIndices(), Eigen::placeholders::all);
870 return Result{reads.space(), typename Result::VectorType{state.mean()(reads.indices())}, lowerTriangularRoot(rows)};
871}
872
878template <typename Reads, typename Point, typename Root>
879[[nodiscard]] RootInput<typename Reads::JointSpace, typename Root::Scalar>
880readRootOf(Reads const & reads, Point const & point, Root const & covarianceRoot)
881{
882 using Scalar = typename Root::Scalar;
883 using Result = RootInput<typename Reads::JointSpace, Scalar>;
884 using Rows = MatrixOf<
885 Scalar,
886 Reads::DifferenceSize,
887 Root::ColsAtCompileTime,
888 Reads::DifferenceMaxSize,
889 Root::MaxColsAtCompileTime>;
890 Rows const rows = covarianceRoot(reads.differenceIndices(), Eigen::placeholders::all);
891 return Result{reads.space(), typename Result::VectorType{point(reads.indices())}, lowerTriangularRoot(rows)};
892}
893
906template <
907 bool Statistics,
908 typename Space,
909 typename Scalar,
910 Measurement M,
911 typename Reference = NoReference,
912 typename Accept = AcceptEvery>
913[[nodiscard]] auto
914updateInPlace(
915 SqrtGaussianParams<Space, Scalar> & state,
916 M const & measurement,
917 Reference const & reference = {},
918 Accept const & accept = {}
919)
920{
921 static_assert(
922 !hasInnovationRepair<M>,
923 "repairing the innovation covariance is the covariance form's (decisions.md [gaussian-forms])"
924 );
925 static_assert(
926 !hasConsider<M>,
927 "a consider() update is the covariance form's: the square-root and information forms keep no block's "
928 "covariance as it was (decisions.md [consider-states]); filter in a Gaussian"
929 );
930 static_assert(
931 M::HasMethod,
932 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
933 );
934 using Method = typename M::MethodType;
935 auto const & space = state.space();
936 auto const resolved = resolveMeasurement(measurement, space);
937 if constexpr (isLinearizedAbout<Reference>)
938 {
939 return foldIntoSqrt(state, linearizeMeasurementAbout<Scalar>(space, state.mean(), reference, resolved), accept);
940 }
941 else if constexpr (isIterated<Method>)
942 {
943 // The factor only places the iterates; the fold of the last linearization is the factor's own.
944 using Matrix = typename SqrtGaussianParams<Space, Scalar>::MatrixType;
945 auto const spread = std::optional<TriangularRoot<Matrix, Eigen::Lower>>{std::in_place, state.root()};
946 auto const iterated = iterateMeasurementLinearization<Scalar>(space, state.mean(), spread, resolved);
947 using Result = decltype(foldIntoSqrt(state, iterated->linearization, accept));
948 if (!iterated)
949 {
950 return Result{std::unexpected{iterated.error()}};
951 }
952 return withIterations(foldIntoSqrt(state, iterated->linearization, accept), *iterated);
953 }
954 else if constexpr (isLinearMethod<Method>)
955 {
956 return foldIntoSqrt(state, linearizeMeasurementAtPoint<Scalar>(space, state.mean(), resolved), accept);
957 }
958 else
959 {
960 auto const & reads = resolved.reads();
961 return foldIntoSqrt(
962 state,
963 linearizeMeasurementAtRoot<Scalar>(space, reads, readRoot(reads, state), resolved),
964 accept
965 );
966 }
967}
968
983template <typename Space, typename Scalar, typename MeasurementSpace, typename Accept = AcceptEvery>
984[[nodiscard]] std::expected<FilterUpdate<MeasurementSpace, 1, Scalar>, GaussianError>
985foldIntoInfo(
986 InfoGaussianParams<Space, Scalar> & state,
987 MeasurementLinearization<Space, MeasurementSpace, Scalar> const & linearization,
988 std::optional<typename InfoGaussianParams<Space, Scalar>::MatrixType> const & covariance,
989 Accept const & accept = {}
990)
991{
992 using InnovationMatrix = typename GaussianParams<MeasurementSpace, Scalar>::MatrixType;
993 using Lin = MeasurementLinearization<Space, MeasurementSpace, Scalar>;
994 Eigen::Index const m = linearization.residual.size();
995 auto const llt = Eigen::LLT<InnovationMatrix>{linearization.noise};
996 if (llt.info() != Eigen::Success)
997 {
998 return std::unexpected{GaussianError::NotPositiveDefinite};
999 }
1000 typename Lin::JacobianMatrix whitened = linearization.jacobian;
1001 solveInPlace(llt, whitened);
1002 auto added = typename InfoGaussianParams<Space, Scalar>::MatrixType{};
1003 added.noalias() = multiply(linearization.jacobian.transpose(), whitened);
1005 vector.noalias() = multiply(linearization.jacobian.transpose(), llt.solve(linearization.residual));
1006
1007 auto innovation = [&] {
1008 if (!covariance)
1009 {
1010 // The statistics of an infinite covariance, stated rather than computed from it.
1011 return Innovation<MeasurementSpace, Scalar>{
1012 linearization.residual,
1013 infiniteCovariance<InnovationMatrix>(m),
1014 InnovationStatistics<Scalar>{Scalar(0), std::numeric_limits<Scalar>::infinity()}
1015 };
1016 }
1017 InnovationMatrix innovationCov = linearization.noise;
1018 innovationCov.noalias() +=
1019 multiply(multiply(linearization.jacobian, *covariance), linearization.jacobian.transpose());
1020 return Innovation<MeasurementSpace, Scalar>{linearization.residual, std::move(innovationCov)};
1021 }();
1022 if constexpr (acceptsByNis<Accept>)
1023 {
1024 if (!accept(innovation.nis(), m))
1025 {
1026 return oneBranch(std::move(innovation), false);
1027 }
1028 }
1029 state.addInfo(added, vector);
1030 return oneBranch(std::move(innovation));
1031}
1032
1048template <
1049 bool Statistics,
1050 typename Space,
1051 typename Scalar,
1052 Measurement M,
1053 typename Reference = NoReference,
1054 typename Accept = AcceptEvery>
1055[[nodiscard]] auto
1056updateInPlace(
1057 InfoGaussianParams<Space, Scalar> & state,
1058 M const & measurement,
1059 Reference const & reference = {},
1060 Accept const & accept = {}
1061)
1062{
1063 static_assert(
1064 !hasInnovationRepair<M>,
1065 "repairing the innovation covariance is the covariance form's (decisions.md [gaussian-forms])"
1066 );
1067 static_assert(
1068 !hasConsider<M>,
1069 "a consider() update is the covariance form's: the square-root and information forms keep no block's "
1070 "covariance as it was (decisions.md [consider-states]); filter in a Gaussian"
1071 );
1072 static_assert(
1073 M::HasMethod,
1074 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
1075 );
1076 using Form = InfoGaussianParams<Space, Scalar>;
1077 using Method = typename M::MethodType;
1078 using Lin = LinearizationOf<Scalar, Space, M>;
1079 using Result = std::expected<FilterUpdate<typename Lin::MeasurementSpaceType, 1, Scalar>, GaussianError>;
1080 auto const & space = state.space();
1081 auto const resolved = resolveMeasurement(measurement, space);
1082
1083 auto const mean = state.mean();
1084 if (!mean && mean.error() != GaussianError::Singular)
1085 {
1086 return Result{std::unexpected{mean.error()}};
1087 }
1088 auto covariance = std::optional<typename Form::MatrixType>{};
1089 auto root = std::optional<typename Form::MatrixType>{};
1090 if (mean)
1091 {
1092 state.setReference(*mean);
1093 auto factor = state.covarianceRoot();
1094 if (!factor)
1095 {
1096 return Result{std::unexpected{factor.error()}};
1097 }
1098 root = std::move(*factor);
1099 covariance = multiply(*root, root->transpose());
1100 }
1101 if constexpr (isLinearizedAbout<Reference>)
1102 {
1103 return foldIntoInfo(
1104 state,
1105 linearizeMeasurementAbout<Scalar>(space, state.reference(), reference, resolved),
1106 covariance,
1107 accept
1108 );
1109 }
1110 else if constexpr (isIterated<Method>)
1111 {
1112 if constexpr (!isLinearMethod<std::remove_cvref_t<decltype(measurement.method().method)>>)
1113 {
1114 if (!covariance)
1115 {
1116 return Result{std::unexpected{GaussianError::Singular}};
1117 }
1118 }
1119 auto const iterated = iterateMeasurementLinearization<Scalar>(space, state.reference(), covariance, resolved);
1120 if (!iterated)
1121 {
1122 return Result{std::unexpected{iterated.error()}};
1123 }
1124 return withIterations(foldIntoInfo(state, iterated->linearization, covariance, accept), *iterated);
1125 }
1126 else if constexpr (isLinearMethod<Method>)
1127 {
1128 return foldIntoInfo(
1129 state,
1130 linearizeMeasurementAtPoint<Scalar>(space, state.reference(), resolved),
1131 covariance,
1132 accept
1133 );
1134 }
1135 else
1136 {
1137 if (!root)
1138 {
1139 return Result{std::unexpected{GaussianError::Singular}};
1140 }
1141 auto const & reads = resolved.reads();
1142 return foldIntoInfo(
1143 state,
1144 linearizeMeasurementAtRoot<Scalar>(space, reads, readRootOf(reads, state.reference(), *root), resolved),
1145 covariance,
1146 accept
1147 );
1148 }
1149}
1150
1170template <typename Space, typename Scalar, typename MeasurementSpace, typename Accept = AcceptEvery>
1171[[nodiscard]] std::expected<FilterUpdate<MeasurementSpace, 1, Scalar>, GaussianError>
1172foldIntoSqrtInfo(
1173 SqrtInfoGaussianParams<Space, Scalar> & state,
1174 MeasurementLinearization<Space, MeasurementSpace, Scalar> const & linearization,
1175 std::optional<typename SqrtInfoGaussianParams<Space, Scalar>::MatrixType> const & covarianceRoot,
1176 Accept const & accept = {}
1177)
1178{
1179 using Form = SqrtInfoGaussianParams<Space, Scalar>;
1180 using InnovationGaussian = GaussianParams<MeasurementSpace, Scalar>;
1181 using InnovationMatrix = typename InnovationGaussian::MatrixType;
1182 constexpr int N = Form::DifferenceDimensionAtCompileTime;
1183 constexpr int NMax = Form::MaxDifferenceDimensionAtCompileTime;
1184 constexpr int M = InnovationGaussian::DifferenceDimensionAtCompileTime;
1185 constexpr int MMax = InnovationGaussian::MaxDifferenceDimensionAtCompileTime;
1186 using Stacked = MatrixOf<Scalar, sizeSum<N, M>, sizeSum<N, 1>, sizeSum<NMax, MMax>, sizeSum<NMax, 1>>;
1187 Eigen::Index const n = state.differenceDimension();
1188 Eigen::Index const m = linearization.residual.size();
1189
1190 auto const llt = Eigen::LLT<InnovationMatrix>{linearization.noise};
1191 if (llt.info() != Eigen::Success)
1192 {
1193 return std::unexpected{GaussianError::NotPositiveDefinite};
1194 }
1195 Stacked stacked(n + m, n + 1);
1196 stacked.topLeftCorner(n, n) = state.infoRoot();
1197 stacked.col(n).head(n) = state.whitenedOffset();
1198 typename MeasurementLinearization<Space, MeasurementSpace, Scalar>::JacobianMatrix whitened =
1199 linearization.jacobian;
1200 solveInPlace(llt.matrixL(), whitened);
1201 stacked.bottomLeftCorner(m, n) = whitened;
1202 stacked.col(n).tail(m) = llt.matrixL().solve(linearization.residual);
1203 Scalar const priorLogDet = Scalar(2) * state.infoRoot().diagonal().array().abs().log().sum();
1204 triangularize(stacked);
1205
1206 Scalar const nis = stacked.col(n).tail(m).squaredNorm();
1207 Scalar logDeterminant = std::numeric_limits<Scalar>::infinity();
1208 InnovationMatrix innovationCov = infiniteCovariance<InnovationMatrix>(m);
1209 if (covarianceRoot)
1210 {
1211 Scalar const posteriorLogDet = Scalar(2) * stacked.diagonal().head(n).array().abs().log().sum();
1212 logDeterminant = Scalar(2) * llt.matrixLLT().diagonal().array().log().sum() + posteriorLogDet - priorLogDet;
1213 typename MeasurementLinearization<Space, MeasurementSpace, Scalar>::JacobianMatrix const scaled =
1214 multiply(linearization.jacobian, *covarianceRoot);
1215 innovationCov = linearization.noise;
1216 innovationCov.noalias() += multiply(scaled, scaled.transpose());
1217 }
1218 auto innovation = Innovation<MeasurementSpace, Scalar>{
1219 linearization.residual,
1220 std::move(innovationCov),
1221 InnovationStatistics<Scalar>{nis, logDeterminant}
1222 };
1223 if constexpr (acceptsByNis<Accept>)
1224 {
1225 if (!accept(nis, m))
1226 {
1227 return oneBranch(std::move(innovation), false);
1228 }
1229 }
1230 state.setRoot(
1231 typename Form::MatrixType{stacked.topLeftCorner(n, n)},
1232 typename Form::DifferenceVectorType{stacked.col(n).head(n)}
1233 );
1234 return oneBranch(std::move(innovation));
1235}
1236
1246template <
1247 bool Statistics,
1248 typename Space,
1249 typename Scalar,
1250 Measurement M,
1251 typename Reference = NoReference,
1252 typename Accept = AcceptEvery>
1253[[nodiscard]] auto
1254updateInPlace(
1255 SqrtInfoGaussianParams<Space, Scalar> & state,
1256 M const & measurement,
1257 Reference const & reference = {},
1258 Accept const & accept = {}
1259)
1260{
1261 static_assert(
1262 !hasInnovationRepair<M>,
1263 "repairing the innovation covariance is the covariance form's (decisions.md [gaussian-forms])"
1264 );
1265 static_assert(
1266 !hasConsider<M>,
1267 "a consider() update is the covariance form's: the square-root and information forms keep no block's "
1268 "covariance as it was (decisions.md [consider-states]); filter in a Gaussian"
1269 );
1270 static_assert(
1271 M::HasMethod,
1272 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
1273 );
1274 using Form = SqrtInfoGaussianParams<Space, Scalar>;
1275 using Method = typename M::MethodType;
1276 using Lin = LinearizationOf<Scalar, Space, M>;
1277 using Result = std::expected<FilterUpdate<typename Lin::MeasurementSpaceType, 1, Scalar>, GaussianError>;
1278 auto const & space = state.space();
1279 auto const resolved = resolveMeasurement(measurement, space);
1280
1281 auto const mean = state.mean();
1282 if (!mean && mean.error() != GaussianError::Singular)
1283 {
1284 return Result{std::unexpected{mean.error()}};
1285 }
1286 auto root = std::optional<typename Form::MatrixType>{};
1287 if (mean)
1288 {
1289 state.setReference(*mean);
1290 auto factor = state.covarianceRoot();
1291 root = std::move(*factor);
1292 }
1293 if constexpr (isLinearizedAbout<Reference>)
1294 {
1295 return foldIntoSqrtInfo(
1296 state,
1297 linearizeMeasurementAbout<Scalar>(space, state.reference(), reference, resolved),
1298 root,
1299 accept
1300 );
1301 }
1302 else if constexpr (isIterated<Method>)
1303 {
1304 auto spread = std::optional<TriangularRoot<typename Form::MatrixType, Eigen::Upper>>{};
1305 if (root)
1306 {
1307 spread.emplace(*root);
1308 }
1309 else if constexpr (!isLinearMethod<std::remove_cvref_t<decltype(measurement.method().method)>>)
1310 {
1311 return Result{std::unexpected{GaussianError::Singular}};
1312 }
1313 auto const iterated = iterateMeasurementLinearization<Scalar>(space, state.reference(), spread, resolved);
1314 if (!iterated)
1315 {
1316 return Result{std::unexpected{iterated.error()}};
1317 }
1318 return withIterations(foldIntoSqrtInfo(state, iterated->linearization, root, accept), *iterated);
1319 }
1320 else if constexpr (isLinearMethod<Method>)
1321 {
1322 return foldIntoSqrtInfo(
1323 state,
1324 linearizeMeasurementAtPoint<Scalar>(space, state.reference(), resolved),
1325 root,
1326 accept
1327 );
1328 }
1329 else
1330 {
1331 if (!root)
1332 {
1333 return Result{std::unexpected{GaussianError::Singular}};
1334 }
1335 auto const & reads = resolved.reads();
1336 return foldIntoSqrtInfo(
1337 state,
1338 linearizeMeasurementAtRoot<Scalar>(space, reads, readRootOf(reads, state.reference(), *root), resolved),
1339 root,
1340 accept
1341 );
1342 }
1343}
1344
1351template <typename Space, typename Scalar, typename Transition, typename NoiseRoot, typename Mean>
1352void
1353propagateRoot(
1354 SqrtGaussianParams<Space, Scalar> & state,
1355 Transition const & transition,
1356 NoiseRoot const & noiseRoot,
1357 Mean && mean
1358)
1359{
1360 using Form = SqrtGaussianParams<Space, Scalar>;
1361 constexpr int N = Form::DifferenceDimensionAtCompileTime;
1362 constexpr int NMax = Form::MaxDifferenceDimensionAtCompileTime;
1363 using Stacked = MatrixOf<Scalar, N, sizeSum<N, N>, NMax, sizeSum<NMax, NMax>>;
1364 Eigen::Index const n = state.differenceDimension();
1365 Stacked stacked(n, 2 * n);
1366 stacked.leftCols(n).noalias() = multiply(transition, asTriangular<Eigen::Lower>(state.root()));
1367 stacked.rightCols(n) = noiseRoot;
1368 state.setMean(std::forward<Mean>(mean));
1369 state.setRoot(lowerTriangularRoot(stacked));
1370}
1371
1389template <
1390 typename Method,
1391 typename Space,
1392 typename Scalar,
1393 typename R,
1394 typename Noise,
1395 typename Reference = NoReference>
1396[[nodiscard]] std::expected<void, GaussianError>
1397predictRootWithNoise(
1398 Method const & method,
1399 SqrtGaussianParams<Space, Scalar> & state,
1400 R const & resolved,
1401 Scalar dt,
1402 Eigen::MatrixBase<Noise> const & noise,
1403 Reference const & about = {}
1404)
1405{
1406 using Form = SqrtGaussianParams<Space, Scalar>;
1407 using MatrixType = typename Form::MatrixType;
1408 constexpr int N = Form::DifferenceDimensionAtCompileTime;
1409 constexpr int NMax = Form::MaxDifferenceDimensionAtCompileTime;
1410 auto const & space = state.space();
1411 Eigen::Index const n = state.differenceDimension();
1412 // Through the covariance, factorized after: for a reference, and for a method with no root form.
1413 // Generic, so that only a branch that calls it instantiates the prediction through the moments.
1414 auto const throughMoments = [&]() -> std::expected<void, GaussianError> {
1415 auto covariance = state.covariance();
1416 auto const prior = GaussianParams<Space, Scalar>{space, state.mean(), std::move(*covariance)};
1417 GaussianParams<Space, Scalar> const predicted = predictWithNoise(method, prior, resolved, dt, noise, about);
1418 state.setMean(predicted.mean());
1419 // The pivoted root is not triangular, so it is triangularized before the form keeps its lower triangle.
1420 state.setRoot(lowerTriangularRoot(defaultRoot(symmetricFromLower<MatrixType>(predicted.covarianceLower()))));
1421 return {};
1422 };
1423 if constexpr (isLinearizedAbout<Reference>)
1424 {
1425 return throughMoments();
1426 }
1427 else if constexpr (predictsByJacobian<Method, R>)
1428 {
1429 MatrixType const noiseRoot = defaultRoot(symmetricFromLower<MatrixType>(noise));
1430 auto step = stepWithJacobian(resolved, state.mean(), dt);
1431 propagateRoot(state, step.jacobian, noiseRoot, std::move(step.value));
1432 return {};
1433 }
1434 else
1435 {
1436 return std::apply(
1437 [&](auto const &... inputs) -> std::expected<void, GaussianError> {
1438 MatrixType const noiseRoot = defaultRoot(symmetricFromLower<MatrixType>(noise));
1439 auto const f = ProcessStep{resolved, dt};
1440 using Input = RootInput<Space, Scalar>;
1441 using Problem = Transform<decltype(f) const, Space, Input, std::remove_cvref_t<decltype(inputs)>...>;
1442 auto const input = Input{space, state.mean(), state.root()};
1443 if constexpr (isLinearMethod<Method>)
1444 {
1445 auto const problem = Problem{f, space, input, inputs...};
1446 auto const linearized = linearizeAt(method, problem);
1447 using Stacked = MatrixOf<
1448 Scalar,
1449 N,
1450 sizeSum<Problem::InputDifferenceSize, N>,
1451 NMax,
1452 sizeSum<Problem::InputDifferenceMaxSize, NMax>>;
1453 Stacked stacked(n, problem.inputDifferenceSize() + n);
1454 problem.forEachUncertain([&]<std::size_t I>(auto const & in) {
1455 Eigen::Index const d = in.differenceDimension();
1456 Eigen::Index const offset = problem.differenceOffset(I);
1457 using In = std::remove_cvref_t<decltype(in)>;
1458 auto const block =
1459 middleColsOf<In::DifferenceDimensionAtCompileTime>(linearized.jacobian, offset, d);
1460 if constexpr (I == 0)
1461 {
1462 stacked.middleCols(offset, d).noalias() =
1463 multiply(block, asTriangular<Eigen::Lower>(in.root()));
1464 }
1465 else
1466 {
1467 using Cov = MatrixOf<
1468 Scalar,
1469 In::DifferenceDimensionAtCompileTime,
1470 In::DifferenceDimensionAtCompileTime,
1471 In::MaxDifferenceDimensionAtCompileTime,
1472 In::MaxDifferenceDimensionAtCompileTime>;
1473 stacked.middleCols(offset, d).noalias() =
1474 multiply(block, defaultRoot(symmetricFromLower<Cov>(covarianceLowerOf(in))));
1475 }
1476 });
1477 stacked.rightCols(n) = noiseRoot;
1478 state.setMean(typename Form::VectorType{linearized.mean});
1479 state.setRoot(lowerTriangularRoot(stacked));
1480 return {};
1481 }
1482 else if constexpr (isSigmaPointMethod<Method>)
1483 {
1484 auto const problem = Problem{f, space, input, inputs...};
1485 auto deviations = transformRoot(method, problem);
1486 using Deviations = decltype(deviations);
1487 Eigen::Index const points = deviations.root.cols();
1488 if (!deviations.centerNegative)
1489 {
1490 using Stacked = MatrixOf<
1491 Scalar,
1492 N,
1493 sizeSum<Deviations::Points, N>,
1494 NMax,
1495 sizeSum<Deviations::MaxPoints, NMax>>;
1496 Stacked stacked(n, points + n);
1497 stacked.leftCols(points) = deviations.root;
1498 stacked.rightCols(n) = noiseRoot;
1499 state.setMean(typename Form::VectorType{std::move(deviations.mean)});
1500 state.setRoot(lowerTriangularRoot(stacked));
1501 return {};
1502 }
1503 auto const spread = deviations.root.leftCols(points - 1);
1504 auto const center = deviations.root.col(points - 1);
1505 MatrixType cov = symmetricFromLower<MatrixType>(noise);
1506 cov.noalias() += multiply(spread, spread.transpose());
1507 cov.noalias() -= multiply(center, center.transpose());
1508 cov = (Scalar(0.5) * (cov + cov.transpose())).eval();
1509 auto const llt = Eigen::LLT<MatrixType>{cov};
1510 if (llt.info() != Eigen::Success)
1511 {
1512 return std::unexpected{GaussianError::NotPositiveDefinite};
1513 }
1514 state.setMean(typename Form::VectorType{std::move(deviations.mean)});
1515 state.setRoot(MatrixType{llt.matrixL()});
1516 return {};
1517 }
1518 else
1519 {
1520 return throughMoments();
1521 }
1522 },
1523 resolved.inputs()
1524 );
1525 }
1526}
1527
1531template <typename Method, typename Space, typename Scalar, ProcessModel Model, typename... Args>
1532[[nodiscard]] std::expected<void, GaussianError>
1533predictInPlace(
1534 Method const & method,
1535 SqrtGaussianParams<Space, Scalar> & state,
1536 Model const & model,
1537 Scalar dt,
1538 Args const &... args
1539)
1540{
1541 internal::assertPartitions(model, state.space());
1542 return withReference(
1543 [&](auto const & about, auto const &... inputs) {
1544 auto const call = modelWith(std::cref(model), inputs...);
1545 auto const resolved = resolveProcess(call, state.space());
1546 return predictRootWithNoise(method, state, resolved, dt, resolved.template noise<Scalar>(dt), about);
1547 },
1548 args...
1549 );
1550}
1551
1556template <
1557 typename Space,
1558 typename Scalar,
1559 typename Space_,
1560 typename Model_,
1561 typename Method_,
1562 typename Scalar_,
1563 typename... Args>
1564[[nodiscard]] std::expected<void, GaussianError>
1565predictInPlace(
1566 SqrtGaussianParams<Space, Scalar> & state,
1567 FixedStepModel<Space_, Model_, Method_, Scalar_> const & model,
1568 Args const &... args
1569)
1570{
1571 using Model = FixedStepModel<Space_, Model_, Method_, Scalar_>;
1572 static_assert(std::is_same_v<Space, Space_>, "the model must be over the state's space");
1573 static_assert(std::is_same_v<Scalar, Scalar_>, "the model's scalar must be the state's");
1574 internal::assertPartitions(model, state.space());
1575 if constexpr (Model::CachesTransition && sizeof...(Args) == 0)
1576 {
1577 propagateRoot(state, model.transition(), model.noiseRoot(), model.propagate(state.space(), state.mean()));
1578 return {};
1579 }
1580 else
1581 {
1582 return withReference(
1583 [&](auto const & about, auto const &... inputs) {
1584 auto const call = modelWith(std::cref(model.model()), inputs...);
1585 auto const resolved = resolveProcess(call, state.space());
1586 return predictRootWithNoise(model.method(), state, resolved, model.timeStep(), model.noise(), about);
1587 },
1588 args...
1589 );
1590 }
1591}
1592
1600template <typename Method, typename Space, typename Scalar, typename Model, typename... Inputs>
1601[[nodiscard]] std::expected<void, GaussianError>
1602predictInPlace(
1603 Method const & method,
1604 InfoGaussianParams<Space, Scalar> & state,
1605 Model const & model,
1606 Scalar dt,
1607 Inputs const &... inputs
1608)
1609{
1610 auto const gaussian = toGaussian(state);
1611 if (!gaussian)
1612 {
1613 return std::unexpected{gaussian.error()};
1614 }
1615 auto info = toInfo(Emrys::predict(method, *gaussian, model, dt, inputs...));
1616 if (!info)
1617 {
1618 return std::unexpected{info.error()};
1619 }
1620 state = std::move(*info);
1621 return {};
1622}
1623
1627template <
1628 typename Space,
1629 typename Scalar,
1630 typename Space_,
1631 typename Model_,
1632 typename Method_,
1633 typename Scalar_,
1634 typename... Inputs>
1635[[nodiscard]] std::expected<void, GaussianError>
1636predictInPlace(
1637 InfoGaussianParams<Space, Scalar> & state,
1638 FixedStepModel<Space_, Model_, Method_, Scalar_> const & model,
1639 Inputs const &... inputs
1640)
1641{
1642 auto const gaussian = toGaussian(state);
1643 if (!gaussian)
1644 {
1645 return std::unexpected{gaussian.error()};
1646 }
1647 auto info = toInfo(Emrys::predict(*gaussian, model, inputs...));
1648 if (!info)
1649 {
1650 return std::unexpected{info.error()};
1651 }
1652 state = std::move(*info);
1653 return {};
1654}
1655
1671template <typename Space, typename Scalar, typename Transition, typename NoiseRoot>
1672[[nodiscard]] std::expected<void, GaussianError>
1673propagateRootInfo(
1674 SqrtInfoGaussianParams<Space, Scalar> & state,
1675 Transition const & transition,
1676 NoiseRoot const & noiseRoot,
1678)
1679{
1680 using MatrixType = typename SqrtInfoGaussianParams<Space, Scalar>::MatrixType;
1681 auto const lu = Eigen::FullPivLU<MatrixType>{transition};
1682 if (!lu.isInvertible())
1683 {
1684 return std::unexpected{GaussianError::Singular};
1685 }
1686 propagateRootInfoByInverse(state, inverseFrom<MatrixType>(lu), noiseRoot, std::move(propagated));
1687 return {};
1688}
1689
1694template <typename Space, typename Scalar, typename Inverse, typename NoiseRoot>
1695void
1696propagateRootInfoByInverse(
1697 SqrtInfoGaussianParams<Space, Scalar> & state,
1698 Inverse const & inverse,
1699 NoiseRoot const & noiseRoot,
1701)
1702{
1703 using Form = SqrtInfoGaussianParams<Space, Scalar>;
1704 using MatrixType = typename Form::MatrixType;
1705 constexpr int N = Form::DifferenceDimensionAtCompileTime;
1706 constexpr int NMax = Form::MaxDifferenceDimensionAtCompileTime;
1707 using Stacked = MatrixOf<Scalar, sizeSum<N, N>, sizeSum<N, N, 1>, sizeSum<NMax, NMax>, sizeSum<NMax, NMax, 1>>;
1708 Eigen::Index const n = state.differenceDimension();
1709
1710 MatrixType const scaled = multiply(asTriangular<Eigen::Upper>(state.infoRoot()), inverse);
1711 Stacked stacked = Stacked::Zero(2 * n, 2 * n + 1);
1712 stacked.topLeftCorner(n, n).setIdentity();
1713 stacked.block(n, 0, n, n).noalias() = -multiply(scaled, noiseRoot);
1714 stacked.block(n, n, n, n) = scaled;
1715 stacked.col(2 * n).tail(n) = state.whitenedOffset();
1716 triangularize(stacked);
1717 state = Form{
1718 state.space(),
1719 std::move(propagated),
1720 MatrixType{stacked.block(n, n, n, n)},
1721 typename Form::DifferenceVectorType{stacked.col(2 * n).tail(n)}
1722 };
1723}
1724
1740template <
1741 typename Method,
1742 typename Space,
1743 typename Scalar,
1744 typename R,
1745 typename Noise,
1746 typename Reference = NoReference>
1747[[nodiscard]] std::expected<void, GaussianError>
1748predictRootInfoWithNoise(
1749 Method const & method,
1750 SqrtInfoGaussianParams<Space, Scalar> & state,
1751 R const & resolved,
1752 Scalar dt,
1753 Eigen::MatrixBase<Noise> const & noise,
1754 Reference const & about = {}
1755)
1756{
1757 using Form = SqrtInfoGaussianParams<Space, Scalar>;
1758 using MatrixType = typename Form::MatrixType;
1759 using VectorType = typename Form::VectorType;
1760 auto const & space = state.space();
1761 Eigen::Index const n = state.differenceDimension();
1762 if constexpr (isLinearMethod<Method>)
1763 {
1764 auto const mean = state.mean();
1765 if (mean)
1766 {
1767 state.setReference(*mean);
1768 }
1769 else if (mean.error() != GaussianError::Singular)
1770 {
1771 return std::unexpected{mean.error()};
1772 }
1773 VectorType const & reference = state.reference();
1774 MatrixType total = symmetricFromLower<MatrixType>(noise);
1775 if constexpr (isLinearizedAbout<Reference>)
1776 {
1777 // The step linearized about the given reference, in the chart of the form's own.
1778 auto const linearization = linearizeStepAbout(method, resolved, space, dt, reference, about);
1779 total += linearization.error();
1780 return propagateRootInfo(
1781 state,
1782 linearization.jacobian,
1783 defaultRoot(total),
1784 VectorType{linearization.value}
1785 );
1786 }
1787 else if constexpr (predictsByJacobian<Method, R>)
1788 {
1789 auto step = stepWithJacobian(resolved, reference, dt);
1790 normalizeValues(space, step.value);
1791 return propagateRootInfo(state, step.jacobian, defaultRoot(total), std::move(step.value));
1792 }
1793 else
1794 {
1795 return std::apply(
1796 [&](auto const &... inputs) -> std::expected<void, GaussianError> {
1797 auto const f = ProcessStep{resolved, dt};
1798 using Point = PointInput<Space, Scalar>;
1799 using Problem =
1800 Transform<decltype(f) const, Space, Point, std::remove_cvref_t<decltype(inputs)>...>;
1801 auto const point = Point{space, reference};
1802 auto const problem = Problem{f, space, point, inputs...};
1803 auto const linearized = linearizeAt(method, problem);
1804 MatrixType const transition = linearized.jacobian.middleCols(problem.differenceOffset(0), n);
1805 problem.forEachUncertain([&]<std::size_t I>(auto const & in) {
1806 if constexpr (I != 0)
1807 {
1808 using In = std::remove_cvref_t<decltype(in)>;
1809 using Cov = MatrixOf<
1810 Scalar,
1811 In::DifferenceDimensionAtCompileTime,
1812 In::DifferenceDimensionAtCompileTime,
1813 In::MaxDifferenceDimensionAtCompileTime,
1814 In::MaxDifferenceDimensionAtCompileTime>;
1815 Eigen::Index const d = in.differenceDimension();
1816 auto const block = middleColsOf<In::DifferenceDimensionAtCompileTime>(
1817 linearized.jacobian,
1818 problem.differenceOffset(I),
1819 d
1820 );
1821 total.noalias() += multiply(
1822 multiply(block, symmetricFromLower<Cov>(covarianceLowerOf(in))),
1823 block.transpose()
1824 );
1825 }
1826 });
1827 return propagateRootInfo(state, transition, defaultRoot(total), VectorType{linearized.mean});
1828 },
1829 resolved.inputs()
1830 );
1831 }
1832 }
1833 else
1834 {
1835 auto const gaussian = toGaussian(state);
1836 if (!gaussian)
1837 {
1838 return std::unexpected{gaussian.error()};
1839 }
1840 auto result = toSqrtInfo(predictWithNoise(method, *gaussian, resolved, dt, noise, about));
1841 if (!result)
1842 {
1843 return std::unexpected{result.error()};
1844 }
1845 state = std::move(*result);
1846 return {};
1847 }
1848}
1849
1853template <typename Method, typename Space, typename Scalar, ProcessModel Model, typename... Args>
1854[[nodiscard]] std::expected<void, GaussianError>
1855predictInPlace(
1856 Method const & method,
1857 SqrtInfoGaussianParams<Space, Scalar> & state,
1858 Model const & model,
1859 Scalar dt,
1860 Args const &... args
1861)
1862{
1863 internal::assertPartitions(model, state.space());
1864 return withReference(
1865 [&](auto const & about, auto const &... inputs) {
1866 auto const call = modelWith(std::cref(model), inputs...);
1867 auto const resolved = resolveProcess(call, state.space());
1868 return predictRootInfoWithNoise(method, state, resolved, dt, resolved.template noise<Scalar>(dt), about);
1869 },
1870 args...
1871 );
1872}
1873
1879template <
1880 typename Space,
1881 typename Scalar,
1882 typename Space_,
1883 typename Model_,
1884 typename Method_,
1885 typename Scalar_,
1886 typename... Args>
1887[[nodiscard]] std::expected<void, GaussianError>
1888predictInPlace(
1889 SqrtInfoGaussianParams<Space, Scalar> & state,
1890 FixedStepModel<Space_, Model_, Method_, Scalar_> const & model,
1891 Args const &... args
1892)
1893{
1894 using Model = FixedStepModel<Space_, Model_, Method_, Scalar_>;
1895 static_assert(std::is_same_v<Space, Space_>, "the model must be over the state's space");
1896 static_assert(std::is_same_v<Scalar, Scalar_>, "the model's scalar must be the state's");
1897 internal::assertPartitions(model, state.space());
1898 if constexpr (Model::CachesTransition && sizeof...(Args) == 0)
1899 {
1900 using VectorType = typename SqrtInfoGaussianParams<Space, Scalar>::VectorType;
1901 auto const mean = state.mean();
1902 if (mean)
1903 {
1904 state.setReference(*mean);
1905 }
1906 else if (mean.error() != GaussianError::Singular)
1907 {
1908 return std::unexpected{mean.error()};
1909 }
1910 VectorType propagated = model.propagate(state.space(), state.reference());
1911 normalizeValues(state.space(), propagated);
1912 // The fixed step keeps its transition's inverse and its noise's root [fixed-step-models].
1913 if (!model.transitionInverse())
1914 {
1915 return std::unexpected{GaussianError::Singular};
1916 }
1917 propagateRootInfoByInverse(state, *(model.transitionInverse()), model.noiseRoot(), std::move(propagated));
1918 return {};
1919 }
1920 else
1921 {
1922 return withReference(
1923 [&](auto const & about, auto const &... inputs) {
1924 auto const call = modelWith(std::cref(model.model()), inputs...);
1925 auto const resolved = resolveProcess(call, state.space());
1926 return predictRootInfoWithNoise(
1927 model.method(),
1928 state,
1929 resolved,
1930 model.timeStep(),
1931 model.noise(),
1932 about
1933 );
1934 },
1935 args...
1936 );
1937 }
1938}
1939
1940} // namespace internal
1941
1942} // namespace Emrys
What a container of several Gaussians needs of each one (decisions.md [component-first]): the Compone...
The coordinate types (decisions.md [no-bounded-coordinates]): Linear, plain coordinates,...
What the four forms of the Gaussian share (decisions.md [gaussian-forms], [form-densities]): the Gaus...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
What marginals and conditionals are built from: resolving Eigen index sets against a space; the share...
The information form of the Gaussian (decisions.md [gaussian-forms]): its parameters InfoGaussianPara...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The update of a Gaussian by a measurement (decisions.md [templated-measurements], [weighted-branches]...
Moments: the mean and covariance that a Gaussian carries and that a mixture or a sample set is moment...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Process models as functions with reads and writes (decisions.md [model-policies], [continuous-time-no...
The covariance reset (decisions.md [covariance-reset]): hasIdentityReset<>, resetJacobian(),...
Drawing samples: SamplesWithLogDensity and SampleWithLogDensity, the Gaussian draws drawSamples(),...
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...
The square-root form of the Gaussian (decisions.md [gaussian-forms]): its parameters SqrtGaussianPara...
The square-root information form of the Gaussian (decisions.md [gaussian-forms]): its parameters Sqrt...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
Triangularization: the triangular factor of a stacked matrix by Householder reflections,...
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
typename MomentsType::DifferenceVectorType DifferenceVectorType
A difference, or the information vector.
Definition InfoGaussian.h:83
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition InfoGaussian.h:85
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
Definition Moments.h:76
DifferenceVector< Space_, Scalar > DifferenceVectorType
a difference, such as a residual
Definition Moments.h:75
ValueVector< Space_, Scalar > VectorType
Definition Moments.h:74
static constexpr int MaxDifferenceDimensionAtCompileTime
Definition Moments.h:72
static constexpr int DifferenceDimensionAtCompileTime
Definition Moments.h:69
static constexpr int MaxValueDimensionAtCompileTime
Definition Moments.h:66
static constexpr int ValueDimensionAtCompileTime
Definition Moments.h:63
static constexpr int DifferenceDimensionAtCompileTime
the length of a difference and the rows of the root, or Eigen::Dynamic
Definition SqrtGaussian.h:100
static constexpr int MaxDifferenceDimensionAtCompileTime
a bound on the length of a difference, or Eigen::Dynamic
Definition SqrtGaussian.h:103
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition SqrtGaussian.h:89
typename MomentsType::VectorType VectorType
a value, such as the reference
Definition SqrtInfoGaussian.h:75
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition SqrtInfoGaussian.h:77
void triangularize(M &&m)
Reduces m to upper-triangular form in place by Householder reflections from the left,...
Definition Triangularization.h:52
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
@ Singular
the precision is not positive definite, so the mean and covariance are undefined
Definition NumericError.h:32
NumericError GaussianError
Reasons a Gaussian, in any of its forms (decisions.md [gaussian-forms]), can fail to give a density,...
Definition NumericError.h:45
std::expected< InfoGaussianParams< typename G::SpaceType, typename G::Scalar >, GaussianError > toInfo(G const &form)
Definition InfoGaussian.h:798
CategoricalParams< Space, Scalar > predict(CategoricalParams< Space, Scalar > const &prior, MarkovChain< Space, Scalar > const &chain)
Definition Categorical.h:1049
constexpr bool isGaussian
Whether C is a Gaussian in covariance form: the component for which a container offers the convenienc...
Definition Component.h:111
std::expected< GaussianParams< typename G::SpaceType, typename G::Scalar >, GaussianError > toGaussian(G const &form)
Definition GaussianForm.h:91
std::expected< SqrtInfoGaussianParams< typename G::SpaceType, typename G::Scalar >, GaussianError > toSqrtInfo(G const &form)
Definition SqrtInfoGaussian.h:674
std::expected< CategoricalUpdate< Space, Scalar >, WeightError > update(CategoricalParams< Space, Scalar > const &prior, E const &emission)
Definition Emission.h:97
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
auto block(Space auto const &space, X &&x)
Definition TagPath.h:979
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
auto resetJacobian(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Reset.h:377
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
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
void normalizeValues(S const &space, Eigen::MatrixBase< Derived > const &block)
Brings the values in block (rows are space's coordinates, columns samples) into canonical form,...
Definition Space.h:331
constexpr Spread< Rule, A > spread(Rule rule, A axes)
Definition Methods.h:750
StatisticalLinearization< InputSpace, OutputSpace, Scalar > statisticalLinearization(OutputWithCross< InputSpace, OutputSpace, Form, Scalar > const &predicted)
Definition Condition.h:635
constexpr Linearized linearized()
Definition Methods.h:333
Iterated< Method, Scalar > iterated(Method method={}, IteratedOptions< Scalar > options={})
Definition Methods.h:985
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > >, valueDimensionAtCompileTime< DifferenceSpace< Space > >, maxValueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< Space > > > JacobianMatrix
Definition Condition.h:159
internal::MatrixOf< Scalar, valueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > >, valueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > >, maxValueDimensionAtCompileTime< DifferenceSpace< MeasurementSpace > > > ErrorMatrix
Definition Condition.h:167