EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Measurement.h
Go to the documentation of this file.
1#pragma once
2
13
14#include <array>
15#include <concepts>
16#include <cstddef>
17#include <expected>
18#include <optional>
19#include <span>
20#include <tuple>
21#include <type_traits>
22#include <utility>
23#include <variant>
24#include <vector>
25
26#include <Eigen/Cholesky>
27
49
50namespace Emrys
51{
52
53namespace internal
54{
55
62template <bool Statistics, UncertainInput Prior, typename R, typename Sink, typename Reference = NoReference>
63[[nodiscard]] decltype(auto)
64updateResolved(Prior const & prior, R const & resolved, Sink sink, Reference const & reference = {})
65{
66 using Scalar = typename Prior::Scalar;
67 using Space = typename Prior::SpaceType;
68 using Reads = typename R::Reads;
71
72 static_assert(
73 R::HasMethod,
74 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
75 );
76 static_assert(
77 R::HasFunction,
78 "a Kalman update needs the model's function; a model known by its likelihood alone serves the filters that "
79 "weight points"
80 );
81 auto const & reads = resolved.reads();
82 auto const read = GaussianMarginal<Prior, typename Reads::Selection, typename Reads::JointSpace>{
83 prior,
84 reads.selection(),
85 reads.space()
86 };
87 auto const f = ResolvedFunction{resolved};
88 auto const & method = resolved.method();
89 using Method = std::remove_cvref_t<decltype(method)>;
90 auto const all = internal::consecutiveIndices<N, NMax>(0, prior.differenceDimension());
91 // The covariance of the whole state with the blocks read, P_xS.
92 using StateReadMatrix = internal::MatrixOf<Scalar, N, Reads::DifferenceSize, NMax, Reads::DifferenceMaxSize>;
93 StateReadMatrix const stateRead =
94 internal::lowerSymmetricBlock(covarianceLowerOf(prior), all, reads.differenceIndices());
95
96 if constexpr (isLinearizedAbout<Reference>)
97 {
98 // One linearization about the reference serves every noise component, which conditionOnPrediction()
99 // then branches on as it does for one at the prior.
100 static_assert(checkReference<Method, Reference, Space>());
101 using Vector = typename GaussianParams<Space, Scalar>::VectorType;
102 using Difference = typename GaussianParams<Space, Scalar>::DifferenceVectorType;
103 using ReadVector = typename MovedInput<std::remove_cvref_t<decltype(read)>>::VectorType;
105 Vector const point = reference.mean();
106 EMRYS_FILTER_ASSERT(
107 point.size() == prior.valueDimension() && "a point to linearize about must have the prior's value dimension"
108 );
109 Difference const step = Emrys::minus(prior.space(), point, prior.mean());
110 ReadVector const readMean = read.mean();
111 ReadDifference const readStep = step(reads.differenceIndices());
112 auto const condition = [&](auto const & y, auto const & cross, auto const & readJacobian) -> decltype(auto) {
113 return conditionOnPrediction<Statistics>(prior, resolved, y, cross, readJacobian, sink);
114 };
115 if constexpr (isLinearMethod<Method>)
116 {
117 return linearizeReadsAt<Space, Scalar>(
118 resolved,
119 method,
120 reads,
121 read,
122 f,
123 stateRead,
124 readMean,
125 true,
126 readStep,
127 std::monostate{},
129 );
130 }
131 else if constexpr (Reference::IsDistribution)
132 {
133 using ReadMatrix = internal::MatrixOf<
134 Scalar,
135 Reads::DifferenceSize,
136 Reads::DifferenceSize,
137 Reads::DifferenceMaxSize,
138 Reads::DifferenceMaxSize>;
139 // The reference's spread over the blocks read, in its own chart, as an iterate's is in the iterate's.
140 ReadMatrix const spreadCov = internal::lowerSymmetricBlock(
141 reference.reference.covarianceLower(),
142 reads.differenceIndices(),
143 reads.differenceIndices()
144 );
145 return linearizeReadsAt<Space, Scalar>(
146 resolved,
147 method,
148 reads,
149 read,
150 f,
151 stateRead,
152 readMean,
153 true,
154 readStep,
155 spreadCov,
157 );
158 }
159 }
160 else if constexpr (isIterated<Method>)
161 {
162 if constexpr (R::MixtureNoise)
163 {
164 // Each component's branch is its own Iterated update, weighted as conditionOnPrediction()
165 // weights the branches of one linearization.
166 static_assert(!isIntoPrior<Sink>, "conditioning in place takes noise of one component");
167 auto const & noise = resolved.noise().distribution();
168 using Component = std::remove_cvref_t<decltype(noise.begin()->component)>;
169 using One = WithNoiseComponent<typename R::ObservedType, Component>;
170 using Single =
171 typename decltype(updateInto<true>(prior, std::declval<One const &>(), NewPosteriors{}))::value_type;
172 using MeasurementSpace = typename Single::MeasurementSpaceType;
173 auto const total = noise.logTotalWeight();
174 EMRYS_FILTER_ASSERT(total && "the noise mixture's weights must be valid");
175 decltype(auto) result = mixtureUpdateFor<Space, MeasurementSpace, Scalar>(sink);
176 using Outcome = MixtureOutcome<Sink, std::remove_cvref_t<decltype(result)>>;
177 result.branches.reserve(std::size_t(noise.size()));
178 auto evidence = LogSumExp<Scalar>{};
179 Eigen::Index k = 0;
180 for (auto const & entry : noise)
181 {
182 auto const one = One{resolved.observed(), entry.component};
183 auto single = updateInto<true>(prior, one, NewPosteriors{});
184 if (!single)
185 {
186 return Outcome{std::unexpected{single.error()}};
187 }
188 auto & branch = single->branches.front();
189 branch.component = k++;
190 branch.logWeight = entry.logWeight - *total + branch.logLikelihood();
191 evidence.add(branch.logWeight);
192 result.branches.push_back(std::move(branch));
193 }
194 auto const logEvidence = evidence.value();
195 MixtureEvidenceAccess::setMixtureLogLikelihood(result, logEvidence);
196 for (auto & branch : result.branches)
197 {
198 branch.logWeight -= logEvidence;
199 }
200 return mixtureOutcome<Sink>(result);
201 }
202 else
203 {
204 return iteratedUpdate<Statistics>(prior, resolved, reads, read, f, stateRead, sink);
205 }
206 }
207 else if constexpr (internal::isLinearMethod<Method>)
208 {
209 // The method sees the measurement as affine in the blocks read, J_S (S - mu_S) plus terms
210 // independent of the state, so its cross-covariance with the whole state is P_xS J_S^T,
211 // with no solve against P_SS.
212 auto const condition = [&](auto const & problem, auto const & linearized) -> decltype(auto) {
213 using Problem = std::remove_cvref_t<decltype(problem)>;
214 using Predicted = OutputWithCross<Space, typename Problem::OutputSpaceType, JacobianForm, Scalar>;
215 auto const & jacobian = linearized.jacobian;
216 Eigen::Index const readSize = reads.differenceIndices().size();
217 auto const readJacobian = ReadLinearization{
218 jacobian.template middleCols<Reads::DifferenceSize>(problem.differenceOffset(0), readSize),
219 reads.differenceIndices(),
220 [&] {
221 using Error = typename Problem::OutputMatrix;
222 return internal::inputsCovariance<Error>(resolved, problem, jacobian);
223 }
224 };
225 auto cross = typename Predicted::CrossMatrix{};
226 cross.noalias() = internal::multiply(stateRead, readJacobian.jacobian.transpose());
227 if constexpr (R::InputCount > 0)
228 {
229 typename Problem::CrossMatrix const joint =
230 internal::multiply(problem.covarianceLower(), jacobian.transpose());
231 auto const y = internal::PredictionMoments{problem.outputSpace(), linearized.mean, jacobian, joint};
232 return internal::conditionOnPrediction<Statistics>(prior, resolved, y, cross, readJacobian, sink);
233 }
234 else
235 {
236 // The blocks read are the only uncertain input, so P_SS J^T is the rows of
237 // P_xS J^T at them, borrowed rather than formed again (decisions.md [borrowed-intermediates]).
238 auto const joint = std::as_const(cross)(reads.differenceIndices(), Eigen::placeholders::all);
239 auto const y = internal::PredictionMoments{problem.outputSpace(), linearized.mean, jacobian, joint};
240 return internal::conditionOnPrediction<Statistics>(prior, resolved, y, cross, readJacobian, sink);
241 }
242 };
243 return internal::linearizeMeasurement(resolved, method, f, read, condition);
244 }
245 else
246 {
247 // Lift the cross-covariance from the blocks read to the full state: P_xS P_SS^-1 Sigma_Sy, which
248 // is P_xS S^-T Psi with S the root that spread the points, so P_SS is not factorized again
249 // (decisions.md [linearization-forms]).
250 auto const predicted = internal::transformMeasurement(resolved, method, f, read);
251 using Predicted = std::remove_cvref_t<decltype(predicted)>;
252 using Solved = typename Predicted::CrossMatrix;
253 Solved const solved = internal::solveWithRoot<Solved>(predicted.root(), predicted.whitenedCross());
254 using MeasurementSpace = std::remove_cvref_t<decltype(predicted.output().space())>;
256 internal::multiply(stateRead, solved);
257 // The measurement's Jacobian in the blocks read, Sigma_Sy^T P_SS^-1, and N + Omega, for updates::Joseph.
258 auto const readJacobian =
259 internal::ReadLinearization{solved.transpose(), reads.differenceIndices(), [&] {
260 using Error = typename Predicted::OutputMatrix;
261 return Error{
262 predicted.inputsCovariance() + predicted.unexplainedCovariance()
263 };
264 }};
265 return internal::conditionOnPrediction<Statistics>(
266 prior,
267 resolved,
268 predicted.output(),
269 cross,
270 readJacobian,
271 sink
272 );
273 }
274}
275
282template <bool Statistics, UncertainInput Prior, Measurement M, typename Sink, typename Reference = NoReference>
283[[nodiscard]] decltype(auto)
284updateInto(Prior const & prior, M const & measurement, Sink sink, Reference const & reference = {})
285{
286 auto const resolved = resolveMeasurement(measurement, prior.space());
287 return updateResolved<Statistics>(prior, resolved, std::move(sink), reference);
288}
289
294template <bool Statistics, UncertainInput Prior, Measurement M, typename Reference = NoReference>
295[[nodiscard]] auto
296updateBranches(Prior const & prior, M const & measurement, Reference const & reference = {})
297{
298 return updateInto<Statistics>(prior, measurement, NewPosteriors{}, reference);
299}
300
304template <UncertainInput Prior, Measurement M>
305using MeasuredUpdate =
306 typename decltype(updateBranches<false>(std::declval<Prior const &>(), std::declval<M const &>()))::value_type;
307
315template <
316 bool Statistics,
317 UncertainInput Prior,
318 Measurement M,
319 typename Space,
320 typename MeasurementSpace,
321 typename Scalar,
322 typename Allocator,
323 typename Reference = NoReference>
324[[nodiscard]] std::expected<void, GaussianError>
325updateBranchesInto(
326 Prior const & prior,
327 M const & measurement,
328 MeasurementUpdate<Space, MeasurementSpace, std::dynamic_extent, Scalar, Allocator> & into,
329 Reference const & reference = {}
330)
331{
332 using Update = MeasurementUpdate<Space, MeasurementSpace, std::dynamic_extent, Scalar, Allocator>;
333 return updateInto<Statistics>(prior, measurement, ReusedUpdate<Update>{&into}, reference);
334}
335
349template <
350 bool Statistics,
351 typename Space,
352 typename Scalar,
353 Measurement M,
354 typename Reference = NoReference,
355 typename Accept = AcceptEvery>
356[[nodiscard]] auto
357updateInPlace(
358 GaussianParams<Space, Scalar> & state,
359 M const & measurement,
360 Reference const & reference = {},
361 Accept const & accept = {}
362)
363{
364 return updateInto<Statistics>(
365 std::as_const(state),
366 measurement,
367 IntoPrior<GaussianParams<Space, Scalar>, Accept>{state, accept},
368 reference
369 );
370}
371
372} // namespace internal
373
419template <UncertainInput Prior, Measurement M>
420[[nodiscard]] auto
421update(Prior const & prior, M const & measurement)
422{
423 return internal::updateBranches<false>(internal::withCovariance(prior), measurement);
424}
425
442template <UncertainInput Prior, Measurement M, typename Reference>
443[[nodiscard]] auto
444update(Prior const & prior, M const & measurement, LinearizedAbout<Reference> const & about)
445{
446 return internal::updateBranches<false>(internal::withCovariance(prior), measurement, about);
447}
448
449namespace internal
450{
451
459template <
460 bool Statistics,
461 typename Prior,
462 typename R,
463 typename Output,
464 typename Cross,
465 typename Jacobian,
466 typename Sink>
467[[nodiscard]] decltype(auto)
468conditionOnPrediction(
469 Prior const & prior,
470 R const & resolved,
471 Output const & y,
472 Cross const & cross,
473 [[maybe_unused]] Jacobian const & readJacobian,
474 Sink && sink
475)
476{
477 using Scalar = typename Prior::Scalar;
478 using Space = typename Prior::SpaceType;
479 using MeasurementSpace = std::remove_cvref_t<decltype(y.space())>;
480 using MeasurementVector = typename GaussianParams<MeasurementSpace, Scalar>::VectorType;
482 using InnovationMatrix = typename GaussianParams<MeasurementSpace, Scalar>::MatrixType;
483 MeasurementVector const observed = observedValue<Scalar>(resolved, y.space());
484
485 // updates::Joseph's H and linearization error, once for every noise component: of the measurement
486 // before additive noise, which each component then adds back as its own R. H is the measurement's
487 // Jacobian in the columns its ReadLinearization names, and the error the N + Omega it gives, a
488 // product of the prediction's, never Sigma_yy less H P H^T (decisions.md [linearization-forms]).
489 auto const & form = resolved.covarianceUpdate();
490 using Form = std::remove_cvref_t<decltype(form)>;
491 auto const linearization = [&] {
492 if constexpr (std::is_same_v<Form, updates::Joseph>)
493 {
494 using Result = StatisticalLinearization<Space, MeasurementSpace, Scalar>;
495 typename Result::JacobianMatrix jacobian =
496 Result::JacobianMatrix::Zero(y.differenceDimension(), prior.differenceDimension());
497 jacobian(Eigen::placeholders::all, readJacobian.indices) = readJacobian.jacobian;
498 typename Result::ErrorMatrix errorCov = readJacobian.error();
499 return Result{std::move(jacobian), std::move(errorCov)};
500 }
501 else
502 {
503 return std::tuple<>{};
504 }
505 }();
506
507 constexpr bool MixtureParams = R::MixtureNoise;
508 static_assert(
509 !(MixtureParams && isIntoPrior<std::remove_cvref_t<Sink>>),
510 "conditioning in place takes noise of one component"
511 );
512 using Branch = UpdateBranch<Space, MeasurementSpace, Scalar>;
513 constexpr bool InPlace = isIntoPrior<std::remove_cvref_t<Sink>>;
514 constexpr bool BranchStatistics = Statistics || MixtureParams;
515 using BranchRepair = typename R::InnovationRepair;
516 // The blocks considered, which every branch keeps as the prior has them [consider-states].
517 auto const considered = consideredRows<R>(prior.space());
518
519 // What the updates take to make updates::Joseph's terms for a noise component whose covariance is
520 // noise: a callable that makes them when the form asks, and nothing for the forms that never
521 // ask. noise must outlive the callable.
522 auto const termsFor = [&]([[maybe_unused]] InnovationMatrix const & noise) {
523 if constexpr (std::is_same_v<Form, updates::Joseph>)
524 {
525 return [&] {
526 // The Jacobian is the same for every component, so the terms borrow it.
527 using Linearization = StatisticalLinearization<Space, MeasurementSpace, Scalar>;
528 return JosephTerms<typename Linearization::JacobianMatrix const &, InnovationMatrix>{
529 linearization.jacobian,
530 InnovationMatrix{linearization.errorCov + noise}
531 };
532 };
533 }
534 else
535 {
536 return std::tuple<>{};
537 }
538 };
539
540 // The branch whose predicted measurement has mean mean and the covariance makeCov() returns,
541 // conditioned on the residual z - mean, with its own posterior, or the error of a covariance
542 // that is not positive definite (decisions.md [innovation-cholesky]). Its NIS and log-likelihood come from the
543 // gain's factorization when they will be read, as weighting a mixture's branches reads them.
544 auto const condition = [&](MeasurementVector const & mean,
545 auto const & makeCov,
546 InnovationMatrix const & noise,
547 Eigen::Index component) -> std::expected<Branch, GaussianError> {
548 InnovationVector residual = Emrys::minus(y.space(), observed, mean);
549 InnovationMatrix cov = makeCov();
550 auto conditioned = kalmanCondition<BranchStatistics, BranchRepair>(
551 prior,
552 residual,
553 cov,
554 cross,
555 form,
556 termsFor(noise),
557 considered
558 );
559 if (!conditioned)
560 {
561 return std::unexpected{conditioned.error()};
562 }
563 return Branch{
564 Scalar(0),
565 std::move(conditioned->posterior),
566 {std::move(residual), std::move(cov), conditioned->statistics},
567 0,
568 component
569 };
570 };
571
572 if constexpr (MixtureParams)
573 {
574 auto const & noise = resolved.noise().distribution();
575 auto const total = noise.logTotalWeight();
576 EMRYS_FILTER_ASSERT(total && "the noise mixture's weights must be valid");
577 decltype(auto) result = mixtureUpdateFor<Space, MeasurementSpace, Scalar>(sink);
578 using Outcome = MixtureOutcome<std::remove_cvref_t<Sink>, std::remove_cvref_t<decltype(result)>>;
579 result.branches.reserve(std::size_t(noise.size()));
580 auto evidence = LogSumExp<Scalar>{};
581 Eigen::Index k = 0;
582 // A component whose covariance is not positive definite fails the whole update [innovation-cholesky].
583 for (auto const & entry : noise)
584 {
585 auto branch = condition(
586 Emrys::plus(y.space(), y.mean(), entry.component.mean()),
587 [&] {
588 return innovationCovariancePlus<InnovationMatrix>(y, entry.component.covarianceLower());
589 },
590 entry.component.covarianceLower(),
591 k++
592 );
593 if (!branch)
594 {
595 return Outcome{std::unexpected{branch.error()}};
596 }
597 branch->logWeight = entry.logWeight - *total + branch->logLikelihood();
598 evidence.add(branch->logWeight);
599 result.branches.push_back(std::move(*branch));
600 }
601 auto const logEvidence = evidence.value();
602 MixtureEvidenceAccess::setMixtureLogLikelihood(result, logEvidence);
603 for (auto & branch : result.branches)
604 {
605 branch.logWeight -= logEvidence;
606 }
607 return mixtureOutcome<std::remove_cvref_t<Sink>>(result);
608 }
609 else
610 {
611 // The one branch conditioned in place: S is made in the Innovation that keeps it, and the
612 // state conditioned on it there; a failed conditioning, or one the sink's acceptor refuses
613 // on its NIS, leaves the state as it was (decisions.md [update-outcomes]). The update is
614 // built where it is returned, since each move of the branch would copy the innovation's
615 // fixed-size residual and S: transform() initializes its value from the prvalue, and the
616 // one return lets the compiler construct it in the caller's object, which Clang does in a
617 // generic lambda only when its return type is written out. into is the sink, an IntoPrior.
618 using InPlaceUpdate = FilterUpdate<MeasurementSpace, 1, Scalar>;
619 auto const inPlace = [&](auto & into,
620 MeasurementVector const & mean,
621 auto const & makeCov,
622 InnovationMatrix const & noise) -> std::expected<InPlaceUpdate, GaussianError> {
623 auto failure = std::optional<GaussianError>{};
624 bool accepted = true;
625 auto const terms = termsFor(noise);
626 auto result = std::expected<void, GaussianError>{}.transform([&] {
627 return InPlaceUpdate{
628 {FilterBranch<MeasurementSpace, Scalar>{
629 Scalar(0),
630 Innovation<MeasurementSpace, Scalar>{
631 InnovationInPlace{},
632 InnovationVector{Emrys::minus(y.space(), observed, mean)},
633 makeCov,
634 [&](auto const & residual, auto & cov) {
635 auto conditioned = kalmanConditionInPlace<Statistics, BranchRepair>(
636 into.state,
637 residual,
638 cov,
639 cross,
640 form,
641 terms,
642 considered,
643 into.accept
644 );
645 if (!conditioned)
646 {
647 failure = conditioned.error();
648 return std::optional<InnovationStatistics<Scalar>>{};
649 }
650 accepted = conditioned->accepted;
651 return conditioned->statistics;
652 }
653 },
654 0,
655 0
656 }},
657 Scalar(0),
658 true
659 };
660 });
661 if (failure)
662 {
663 result = std::unexpected{*failure};
664 }
665 else
666 {
667 result->accepted = accepted;
668 }
669 return result;
670 };
671
672 // The one branch, or the error of its conditioning.
673 auto const single = [&](MeasurementVector const & mean, auto const & makeCov, InnovationMatrix const & noise) {
674 if constexpr (InPlace)
675 {
676 return inPlace(sink, mean, makeCov, noise);
677 }
678 else
679 {
680 using One = MeasurementUpdate<Space, MeasurementSpace, 1, Scalar>;
681 auto branch = condition(mean, makeCov, noise, 0);
682 if (!branch)
683 {
684 return std::expected<One, GaussianError>{std::unexpected{branch.error()}};
685 }
686 return std::expected<One, GaussianError>{One{{std::move(*branch)}}};
687 }
688 };
689 if constexpr (!R::HasAdditiveNoise)
690 {
691 return single(
692 y.mean(),
693 [&] {
694 return InnovationMatrix{y.covarianceLower()};
695 },
696 InnovationMatrix::Zero(y.differenceDimension(), y.differenceDimension())
697 );
698 }
699 else
700 {
701 auto const & noise = resolved.noise().distribution();
702 return single(
703 Emrys::plus(y.space(), y.mean(), noise.mean()),
704 [&] {
705 return innovationCovariancePlus<InnovationMatrix>(y, noise.covarianceLower());
706 },
707 noise.covarianceLower()
708 );
709 }
710 }
711}
712
713} // namespace internal
714
721template <typename Space, typename P, typename Value>
723{
725 Value const & value;
726};
727
728namespace internal
729{
730
735template <typename Space, typename P, typename M>
736class PinnedMeasurement
737{
738public:
739
741 using ResolvedType = ResolvedMeasurement<M, Space>;
742
743private:
744
745 using Reads = typename ResolvedType::Reads;
746 static constexpr int N = valueDimensionAtCompileTime<Space>;
747 static constexpr int NMax = maxValueDimensionAtCompileTime<Space>;
748 static constexpr int D =
750 static constexpr int DMax = maxValueDimensionAtCompileTime<DifferenceSpace<Space>>;
751
753 static constexpr int PinnedD = valueDimensionAtCompileTime<DifferenceSpace<PathTag<P>>>;
754
756 static constexpr int RestD = D == Eigen::Dynamic || PinnedD == Eigen::Dynamic ? Eigen::Dynamic : D - PinnedD;
757
758 static constexpr int RestDMax = RestD == Eigen::Dynamic ? DMax : RestD;
759
761 static constexpr int PinnedN = valueDimensionAtCompileTime<PathTag<P>>;
762
764 static constexpr int RestN = N == Eigen::Dynamic || PinnedN == Eigen::Dynamic ? Eigen::Dynamic : N - PinnedN;
765
766 static constexpr int RestNMax = RestN == Eigen::Dynamic ? NMax : RestN;
767
768public:
769
773 PinnedMeasurement(Space const & space, M const & measurement)
774 : m_resolved{measurement, space}
775 , m_rows{blockRows<P>(space)}
776 , m_differenceRows{differenceBlockRows<P>(space)}
777 , m_size{valueDimension(space)}
778 , m_differenceSize{differenceDimension(space)}
779 {
780 m_rest.resize(m_size - m_rows.size);
781 Eigen::Index k = 0;
782 for (Eigen::Index i = 0; i < m_size; ++i)
783 {
784 if (i < m_rows.start || i >= m_rows.start + m_rows.size)
785 {
786 m_rest(k++) = i;
787 }
788 }
789 }
790
792 [[nodiscard]] ResolvedType const &
793 resolved() const
794 {
795 return m_resolved;
796 }
797
799 [[nodiscard]] Eigen::Index
800 restSize() const
801 {
802 return m_rest.size();
803 }
804
810 template <typename X, typename Pinned, typename... Inputs>
811 [[nodiscard]] auto
812 operator()(X const & x, Pinned const & p, Inputs const &... inputs) const
813 {
814 using T = ScalarOf<X>;
815 EMRYS_FILTER_ASSERT(x.size() == m_rest.size() && p.size() == m_rows.size && "the blocks do not fit the space");
816 auto state = Eigen::Matrix<T, N, 1, Eigen::ColMajor, NMax, 1>{};
817 state.resize(m_size);
818 state(m_rest) = x;
819 middleRowsOf<PinnedN>(state, m_rows.start, m_rows.size) = p.template cast<T>();
820 using Returned = std::remove_cvref_t<decltype(m_resolved.evaluateAtState(state, inputs...))>;
821 if constexpr (!isWithJacobian<Returned>)
822 {
823 return m_resolved.evaluateAtState(state, inputs...);
824 }
825 else
826 {
827 auto returned = m_resolved.evaluateAtState(state, inputs...);
828 return withJacobian(
829 std::move(returned.value),
830 splitJacobian(returned.jacobian, std::integral_constant<std::size_t, sizeof...(Inputs)>{})
831 );
832 }
833 }
834
837 static constexpr bool EvaluatesBatches = ResolvedType::BatchedFunction;
838
844 template <typename Y, typename Xs, typename Pinned, typename... Inputs>
845 void
846 evaluateBatch(Y & y, Xs const & xs, Pinned const & p, Inputs const &... inputs) const
847 requires EvaluatesBatches
848 {
849 using T = typename Xs::Scalar;
850 using States = MatrixOf<T, N, Y::ColsAtCompileTime, NMax, Y::MaxColsAtCompileTime>;
851 Eigen::Index const count = xs.cols();
852 EMRYS_FILTER_ASSERT(
853 xs.rows() == m_rest.size() && p.rows() == m_rows.size && (p.cols() == 1 || p.cols() == count)
854 && "the blocks do not fit the space"
855 );
856 auto states = States{};
857 states.resize(m_size, count);
858 for (Eigen::Index j = 0; j < count; ++j)
859 {
860 auto state = states.col(j);
861 state(m_rest) = xs.col(j);
862 middleRowsOf<PinnedN>(state, m_rows.start, m_rows.size) = p.col(p.cols() == 1 ? 0 : j).template cast<T>();
863 }
864 m_resolved.evaluateStateColumns(y, states, inputs...);
865 }
866
867private:
868
873 template <typename Blocks, std::size_t InputCount>
874 [[nodiscard]] auto
875 splitJacobian(Blocks const & blocks, std::integral_constant<std::size_t, InputCount>) const
876 {
877 auto const & whole = jacobianBlock<0>(blocks);
878 using Whole = std::remove_cvref_t<decltype(whole)>;
879 using Scalar = typename Whole::Scalar;
880 constexpr int Rows = Whole::RowsAtCompileTime;
881 constexpr int MaxRows = Whole::MaxRowsAtCompileTime;
882 using RestJacobian = MatrixOf<Scalar, Rows, RestD, MaxRows, RestDMax>;
883 using GivenJacobian =
884 MatrixOf<Scalar, Rows, PinnedD, MaxRows, maxValueDimensionAtCompileTime<DifferenceSpace<PathTag<P>>>>;
885 Eigen::Index const before = m_differenceRows.start;
886 Eigen::Index const after = m_differenceSize - m_differenceRows.start - m_differenceRows.size;
887 RestJacobian rest(whole.rows(), before + after);
888 rest.leftCols(before) = whole.leftCols(before);
889 rest.rightCols(after) = whole.rightCols(after);
890 GivenJacobian given = middleColsOf<PinnedD>(whole, m_differenceRows.start, m_differenceRows.size);
891 return [&]<std::size_t... K>(std::index_sequence<K...>) {
892 using Tuple =
893 std::tuple<RestJacobian, GivenJacobian, std::remove_cvref_t<decltype(jacobianBlock<1 + K>(blocks))>...>;
894 return ArgumentJacobians<Tuple>{Tuple{std::move(rest), std::move(given), jacobianBlock<1 + K>(blocks)...}};
895 }(std::make_index_sequence<InputCount>{});
896 }
897
898 ResolvedType m_resolved;
899 BlockRows m_rows;
900 BlockRows m_differenceRows;
901 Eigen::Index m_size;
902 Eigen::Index m_differenceSize;
903 IndexVector<RestN, RestNMax> m_rest;
904};
905
906} // namespace internal
907
915template <typename P, typename Space, typename Value>
916[[nodiscard]] GivenBlock<Space, P, Value>
917given(Space const & space, Value const & value)
918{
919 return {space, value};
920}
921
927template <typename P, typename Space, typename Value>
928void
929given(Space const & space, Value const && value) = delete;
930
931namespace internal
932{
933
938template <bool Statistics, UncertainInput Prior, typename Space, typename P, typename M, typename Value>
939[[nodiscard]] auto
940updateBranchesWith(Prior const & prior, PinnedMeasurement<Space, P, M> const & f, Value const & value)
941{
942 auto const & resolved = f.resolved();
943 using R = std::remove_cvref_t<decltype(resolved)>;
944 static_assert(
945 R::HasMethod,
946 "a Kalman update pushes the prior through the model by a method: observe(method, model, z)"
947 );
948 static_assert(!internal::isIterated<typename R::Method>, "an update given a block does not support Iterated yet");
949 static_assert(
950 !hasConsider<R>,
951 "an update given a block, as a Rao-Blackwellized filter's, does not consider blocks yet (decisions.md "
952 "[consider-states])"
953 );
954 EMRYS_FILTER_ASSERT(
955 prior.valueDimension() == f.restSize() && "the prior must cover the state outside the given block"
956 );
957 auto const & method = resolved.method();
958 auto const predicted = std::apply(
959 [&](auto const &... inputs) {
960 return internal::transformPrediction(resolved, method, f, withCross(prior), value, inputs...);
961 },
962 resolved.inputs()
963 );
964 // The measurement reads the whole prior, so its linearization is the prediction's own.
967 auto const all = internal::consecutiveIndices<N, NMax>(0, prior.differenceDimension());
968 using Predicted = std::remove_cvref_t<decltype(predicted)>;
969 auto const & cross = predicted.crossCovariance();
970 return internal::conditionOnPrediction<Statistics>(
971 prior,
972 resolved,
973 predicted.output(),
974 cross,
975 internal::ReadLinearization{
976 typename Predicted::JacobianMatrix{predicted.jacobian()},
977 all,
978 [&] {
979 using Error = typename Predicted::OutputMatrix;
980 return Error{predicted.inputsCovariance() + predicted.unexplainedCovariance()};
981 }
982 },
983 NewPosteriors{}
984 );
985}
986
988template <typename T>
989inline constexpr bool isGivenBlock = false;
990
992template <typename Space, typename P, typename Value>
993inline constexpr bool isGivenBlock<GivenBlock<Space, P, Value>> = true;
994
999template <typename F, typename Value>
1000struct GivenMeasurement
1001{
1002 F pinned;
1003 Value const & value;
1004};
1005
1009template <typename G>
1010concept GivenMeasurementLike = requires(G const & given) {
1011 given.pinned.resolved();
1012 given.value;
1013};
1014
1018template <bool Statistics, UncertainInput Prior, Measurement M, typename Space, typename P, typename Value>
1019[[nodiscard]] auto
1020updateBranches(Prior const & prior, M const & measurement, GivenBlock<Space, P, Value> const & pinned)
1021{
1022 auto const f = PinnedMeasurement<Space, P, M>{pinned.space, measurement};
1023 return updateBranchesWith<Statistics>(prior, f, pinned.value);
1024}
1025
1026} // namespace internal
1027
1048template <UncertainInput Prior, Measurement M, typename Space, typename P, typename Value>
1049[[nodiscard]] auto
1050update(Prior const & prior, M const & measurement, GivenBlock<Space, P, Value> const & pinned)
1051{
1052 return internal::updateBranches<false>(internal::withCovariance(prior), measurement, pinned);
1053}
1054
1055} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
The coordinate types (decisions.md [no-bounded-coordinates]): Linear, plain coordinates,...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
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 iterated measurement update, all internal (decisions.md [iterated-update], [sigma-point-sets]): t...
The likelihoods that sample-based filters weight by (decisions.md [likelihood-measurements],...
The macros every header includes first.
Measurement models and their observations (decisions.md [model-policies], [sensors],...
How an update reads a measurement, all internal (decisions.md [model-policies], [measurement-space-fu...
What an update returns (decisions.md [weighted-branches], [innovation-statistics]): an UpdateBranch p...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Product: the product of independent densities, each on its own block of a space, built with on<Path>(...
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...
Subspaces, and growing and shrinking blocks (decisions.md [several-resizable-blocks]).
Tag paths and the index utilities (decisions.md [tag-addressing], [index-names]): TagPath and TagPath...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
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
internal::CrossMatrixOf< InputSpace, OutputSpace, Scalar > CrossMatrix
The cross-covariance's type, and 's: input differences by output differences.
Definition Linearization.h:258
A type usable as the space of a distribution.
Definition Space.h:186
typename std::remove_cvref_t< X >::Scalar ScalarOf
The scalar type of the Eigen object or distribution X, for writing functions generic in it.
Definition Moments.h:26
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
std::expected< CategoricalUpdate< Space, Scalar >, WeightError > update(CategoricalParams< Space, Scalar > const &prior, E const &emission)
Definition Emission.h:97
constexpr Residual< Inner > residual(Inner inner={})
Definition Resampling.h:544
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 minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
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
CrossMarked< Input > withCross(Input const &input)
Marks the uncertain input, usually the state, whose cross-covariance with the output transform() also...
Definition Transform.h:1649
constexpr Linearized linearized()
Definition Methods.h:333
WithJacobian< internal::PlainResult< std::remove_cvref_t< Value > >, internal::PlainResult< std::remove_cvref_t< Jacobian > > > withJacobian(Value &&value, Jacobian &&jacobian)
Definition Methods.h:222
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
Rows of a contiguous block of a space.
Definition TagPath.h:825
A block of a state given from outside the Gaussian being updated, as given() makes it,...
Definition Measurement.h:723
Value const & value
the block's value, known or an uncertain input
Definition Measurement.h:725
Space space
the whole state space
Definition Measurement.h:724
Where a step linearizes its input: a point, or a distribution, in place of the input's own mean and s...
Definition TransformArgs.h:156
What a model reads, as reads() declares it: the items, each a path into the state or the tag of an in...
Definition Policies.h:173