EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
MeasurementReads.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <array>
13#include <concepts>
14#include <cstddef>
15#include <expected>
16#include <optional>
17#include <span>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21#include <variant>
22#include <vector>
23
24#include <Eigen/Cholesky>
25
47
48namespace Emrys
49{
50
51namespace internal
52{
53
55struct NoNoise
56{};
57
68template <typename M, typename Space>
69class ResolvedMeasurement : public ResolvedReads<typename M::ModelType, typename M::ArgumentsType, Space>
70{
72 using Base = ResolvedReads<typename M::ModelType, typename M::ArgumentsType, Space>;
73
74public:
75
76 using ObservedType = M;
77 using Model = typename M::ModelType;
78 using Function = typename Model::FunctionType;
79 using Method = typename M::MethodType;
80
81 static constexpr bool HasFunction = Model::HasFunction;
82 static constexpr bool HasMethod = M::HasMethod;
83 static constexpr bool HasObservation = M::HasObservation;
84 static constexpr bool OwnNoise = requires { M::OwnNoise; };
85
87 static constexpr bool HasAdditiveNoise = OwnNoise || Model::template has<PolicyKind::additiveNoise>;
88
90 static constexpr bool HasLogLikelihood = Model::template has<PolicyKind::logLikelihood>;
91
93 static constexpr bool HasSample = Model::template has<PolicyKind::sample>;
94
96 static constexpr bool BatchedLikelihood = [] {
97 if constexpr (HasLogLikelihood)
98 {
99 return isBatched<decltype(Model::template Policy<PolicyKind::logLikelihood>::function)>;
100 }
101 else
102 {
103 return false;
104 }
105 }();
106
107 using Base::ArgumentCount;
108 using Base::InputCount;
109 using Base::ItemCount;
110 using Base::StateCount;
111 using typename Base::In;
112 using typename Base::Reads;
113 using typename Base::StatePaths;
114
116 template <std::size_t I>
117 using Item = typename Base::template Item<I>;
118
120 template <std::size_t J>
121 using Argument = typename Base::template Argument<J>;
122
124 template <typename T>
125 using InVector = typename Base::template InVector<T>;
126
127private:
128
129 using Base::argumentIsUncertain;
130 using Base::argumentOf;
131 using Base::inputIndexOf;
132 using Base::inputItems;
133 using Base::inState;
134 using Base::m_differenceRows;
135 using Base::m_in;
136 using Base::m_reads;
137 using Base::m_stateDifferenceSize;
138 using Base::m_valueRows;
139 using Base::stateIndexOf;
140 using Base::stateItems;
141
143 using ProbeScalar = double;
144
146 static constexpr bool OutputFromNoise = [] {
147 if constexpr (Model::template has<PolicyKind::outputSpace> || Model::template has<PolicyKind::writes>)
148 {
149 return false;
150 }
151 else if constexpr (Model::template has<PolicyKind::additiveNoise>)
152 {
153 return Model::template Policy<PolicyKind::additiveNoise>::HasOwnSpace;
154 }
155 else
156 {
157 return false;
158 }
159 }();
160
165 [[nodiscard]] static consteval auto
166 outputType()
167 {
168 if constexpr (Model::template has<PolicyKind::outputSpace>)
169 {
170 using Policy = typename Model::template Policy<PolicyKind::outputSpace>;
171 return std::type_identity<decltype(std::declval<Policy const &>().in(std::declval<Space const &>()))>{};
172 }
173 else if constexpr (Model::template has<PolicyKind::writes>)
174 {
175 return std::type_identity<typename Model::template Policy<PolicyKind::writes>::OutputSpace>{};
176 }
177 else if constexpr (OutputFromNoise)
178 {
179 return std::type_identity<
180 typename Model::template Policy<PolicyKind::additiveNoise>::Distribution::SpaceType>{};
181 }
182 else if constexpr (requires { typename Function::OutputSpaceType; })
183 {
184 return std::type_identity<typename Function::OutputSpaceType>{};
185 }
186 else if constexpr (HasFunction && functionForm<Function, In, NoOutputSpace, InVector<ProbeScalar>> == 0)
187 {
188 // A function in no form: instantiating callForm() gives its message, and a space of one
189 // coordinate stands in for the output's, so that what follows adds no errors about it.
190 using Refused [[maybe_unused]] = decltype(callForm(
191 std::declval<Function const &>(),
192 std::declval<In const &>(),
193 std::declval<NoOutputSpace const &>(),
194 std::declval<InVector<ProbeScalar> const &>()
195 ));
196 return std::type_identity<Linear<1>>{};
197 }
198 else if constexpr (HasFunction)
199 {
200 using Returned = decltype(vectorReturned(callForm(
201 std::declval<Function const &>(),
202 std::declval<In const &>(),
203 std::declval<NoOutputSpace const &>(),
204 std::declval<InVector<ProbeScalar> const &>()
205 )));
206 using Evaluated = std::remove_cvref_t<decltype(valueOf(std::declval<Returned>()).eval())>;
207 static_assert(Evaluated::ColsAtCompileTime == 1, "a measurement model's function returns a column vector");
208 return std::type_identity<Linear<Evaluated::RowsAtCompileTime, Evaluated::MaxRowsAtCompileTime>>{};
209 }
210 else
211 {
212 return std::type_identity<NoOutputSpace>{};
213 }
214 }
215
216public:
217
219 using Out = typename decltype(outputType())::type;
220
221 static constexpr bool HasOutputSpace = !std::is_same_v<Out, NoOutputSpace>;
222
224 using NoiseType = typename decltype([] {
225 if constexpr (OwnNoise)
226 {
227 return std::type_identity<typename M::NoiseType>{};
228 }
229 else if constexpr (HasAdditiveNoise)
230 {
231 static_assert(
232 HasOutputSpace,
233 "additive noise needs the model's output space, from writes(), outputSpace(), or its function's result"
234 );
235 return std::type_identity<decltype(placeNoise(
236 std::declval<Out const &>(),
237 std::declval<Model const &>().template policy<PolicyKind::additiveNoise>()
238 ))>{};
239 }
240 else
241 {
242 return std::type_identity<NoNoise>{};
243 }
244 }())::type;
245
247 static constexpr bool MixtureNoise = [] {
248 if constexpr (HasAdditiveNoise)
249 {
250 return isGaussianMixture<typename NoiseType::Distribution>;
251 }
252 else
253 {
254 return false;
255 }
256 }();
257
261 using InnovationRepair = typename M::InnovationRepair;
262
264 using ConsiderType = typename M::ConsiderType;
265
267 ResolvedMeasurement(M const & measurement, Space const & space)
268 : Base{measurement.arguments(), space}
269 , m_measurement{measurement}
270 , m_out{makeOut(measurement, space)}
271 , m_noise{makeNoise(measurement)}
272 {}
273
275 [[nodiscard]] M const &
276 observed() const
277 {
278 return m_measurement;
279 }
280
282 [[nodiscard]] Model const &
283 model() const
284 {
285 return m_measurement.model();
286 }
287
289 [[nodiscard]] Method const &
290 method() const
291 {
292 return m_measurement.method();
293 }
294
296 [[nodiscard]] decltype(auto)
297 observation() const
298 requires HasObservation
299 {
300 return m_measurement.observation();
301 }
302
304 [[nodiscard]] decltype(auto)
305 covarianceUpdate() const
306 {
307 return m_measurement.covarianceUpdate();
308 }
309
311 [[nodiscard]] Out const &
312 space() const
313 requires HasOutputSpace
314 {
315 return m_out;
316 }
317
319 [[nodiscard]] NoiseType const &
320 noise() const
321 requires HasAdditiveNoise
322 {
323 return m_noise;
324 }
325
331 template <typename Joint, typename... Inputs>
332 [[nodiscard]] auto
333 evaluate(Joint const & joint, Inputs const &... inputs) const
334 {
335 static_assert(sizeof...(Inputs) == InputCount, "the function takes one vector per uncertain input");
336 using T = typename Joint::Scalar;
337 return split<false>(call(this->template gather<T>(joint, std::tuple<Inputs const &...>{inputs...})));
338 }
339
345 template <typename State, typename... Inputs>
346 [[nodiscard]] auto
347 evaluateAtState(State const & state, Inputs const &... inputs) const
348 {
349 static_assert(
350 sizeof...(Inputs) == InputCount || sizeof...(Inputs) == 0,
351 "the function takes one vector per uncertain input, or none to read each at its mean"
352 );
353 using T = typename State::Scalar;
354 return split<true>(
355 call(this->template gatherFromRows<T>(state, Eigen::Index{0}, std::tuple<Inputs const &...>{inputs...}))
356 );
357 }
358
362 template <typename State>
363 [[nodiscard]] auto
364 logLikelihoodAt(State const & state) const
365 requires(HasLogLikelihood && HasObservation)
366 {
367 return logLikelihoodAtRows(state, Eigen::Index{0});
368 }
369
373 template <typename Rows>
374 [[nodiscard]] auto
375 logLikelihoodAtRows(Rows const & rows, Eigen::Index start) const
376 requires(HasLogLikelihood && HasObservation)
377 {
378 using T = typename Rows::Scalar;
379 if constexpr (BatchedLikelihood)
380 {
381 // A batch of one column.
382 auto x = InMatrix<T>{};
383 x.resize(valueDimension(m_in), 1);
384 this->template gatherColumnsFromRows<T>(
385 x,
386 [&](Eigen::Index) -> Rows const & {
387 return rows;
388 },
389 start
390 );
391 auto result = Eigen::Matrix<T, 1, 1>{T(0)};
392 batchedLogLikelihoods(x, result);
393 return result(0);
394 }
395 else
396 {
397 return callWithScalar<T>(
398 model().template policy<PolicyKind::logLikelihood>().function,
399 m_in,
400 this->template gatherFromRows<T>(rows, start, std::tuple<>{}),
401 m_measurement.observation()
402 );
403 }
404 }
405
410 template <typename State, typename G, typename... Inputs>
411 [[nodiscard]] auto
412 sampleAtState(State const & state, G & rng, Inputs const &... inputs) const
413 requires HasSample
414 {
415 static_assert(
416 sizeof...(Inputs) == InputCount || sizeof...(Inputs) == 0,
417 "the draw takes one vector per uncertain input, or none to read each at its mean"
418 );
419 using T = typename State::Scalar;
420 return callWithScalar<T>(
421 model().template policy<PolicyKind::sample>().function,
422 m_in,
423 this->template gatherFromRows<T>(state, Eigen::Index{0}, std::tuple<Inputs const &...>{inputs...}),
424 rng
425 );
426 }
427
429 template <typename T>
430 using InMatrix = MatrixOf<
431 T,
433 Eigen::Dynamic,
435 Eigen::Dynamic>;
436
438 static constexpr bool BatchedFunction = takesBatches<Function>;
439
441 template <typename T, typename S = Out>
442 using OutMatrix =
443 MatrixOf<T, valueDimensionAtCompileTime<S>, Eigen::Dynamic, maxValueDimensionAtCompileTime<S>, Eigen::Dynamic>;
444
449 template <typename X, typename Y>
450 void
451 evaluateBatch(X const & x, Y && y) const
452 requires BatchedFunction
453 {
454 callBatched(m_measurement.model().function(), m_in, m_out, x, y);
455 }
456
461 template <typename X, typename Y, typename J>
462 void
463 evaluateBatchWithInputJacobians(X const & x, Y & y, J & jacobians) const
464 requires BatchedFunction
465 {
466 auto wanted = std::array<bool, std::tuple_size_v<InputBlocks<In>>>{};
467 [&]<std::size_t... I>(std::index_sequence<I...>) {
468 ((wanted[I] = argumentIsUncertain(I)), ...);
469 }(std::make_index_sequence<ItemCount>{});
470 callBatchedWithJacobians(m_measurement.model().function(), m_in, m_out, x, wanted, y, jacobians);
471 }
472
477 template <typename Y, typename Joint, typename... Inputs>
478 [[nodiscard]] auto
479 gatherColumns(Joint const & joint, Inputs const &... inputs) const
480 {
481 static_assert(sizeof...(Inputs) == InputCount);
482 using T = typename Joint::Scalar;
483 using X = MatrixOf<
484 T,
486 Y::ColsAtCompileTime,
488 Y::MaxColsAtCompileTime>;
489 auto x = X{};
490 x.resize(valueDimension(m_in), joint.cols());
491 for (Eigen::Index j = 0; j < joint.cols(); ++j)
492 {
493 x.col(j) = this->template gather<T>(joint.col(j), std::tuple{inputs.col(j)...});
494 }
495 return x;
496 }
497
503 template <typename Y, typename Joint, typename... Inputs>
504 void
505 evaluateColumns(Y & y, Joint const & joint, Inputs const &... inputs) const
506 requires BatchedFunction
507 {
508 callBatched(m_measurement.model().function(), m_in, m_out, gatherColumns<Y>(joint, inputs...), y);
509 }
510
515 template <typename Y, typename States, typename... Inputs>
516 void
517 evaluateStateColumns(Y & y, States const & states, Inputs const &... inputs) const
518 requires BatchedFunction
519 {
520 static_assert(sizeof...(Inputs) == InputCount);
521 using T = typename States::Scalar;
522 using X = MatrixOf<
523 T,
525 Y::ColsAtCompileTime,
527 Y::MaxColsAtCompileTime>;
528 auto x = X{};
529 x.resize(valueDimension(m_in), states.cols());
530 for (Eigen::Index j = 0; j < states.cols(); ++j)
531 {
532 x.col(j) = this->template gatherFromRows<T>(states.col(j), Eigen::Index{0}, std::tuple{inputs.col(j)...});
533 }
534 callBatched(m_measurement.model().function(), m_in, m_out, x, y);
535 }
536
541 template <typename Y, typename J, typename Joint, typename... Inputs>
542 void
543 evaluateColumnsWithAffineJacobians(Y & y, J & jacobians, Joint const & joint, Inputs const &... inputs) const
544 requires BatchedFunction
545 {
546 auto wanted = std::array<bool, std::tuple_size_v<InputBlocks<In>>>{};
547 [&]<std::size_t... I>(std::index_sequence<I...>) {
548 ((wanted[I] = Base::template itemFactored<I>()), ...);
549 }(std::make_index_sequence<ItemCount>{});
550 auto const x = gatherColumns<Y>(joint, inputs...);
551 callBatchedWithJacobians(m_measurement.model().function(), m_in, m_out, x, wanted, y, jacobians);
552 }
553
558 template <typename X, typename Result>
559 void
560 batchedLogLikelihoods(X const & x, Result && result) const
561 requires(BatchedLikelihood && HasObservation)
562 {
563 using T = typename X::Scalar;
564 using Likelihood = typename Model::template Policy<PolicyKind::logLikelihood>;
565 using Output = BatchedOutput<In, Linear<1>, T, typename decltype(Likelihood::function)::MarkType>;
566 EMRYS_FILTER_ASSERT(result.size() == x.cols() && "give one entry per column of the batch");
567 auto y = Output{
568 typename Output::ValuesType{result.data(), 1, x.cols()},
569 typename Output::JacobiansType{nullptr, 1, 0},
570 m_in,
571 {}
572 };
573 callWithScalar<T>(
574 model().template policy<PolicyKind::logLikelihood>().function.function,
575 m_in,
576 x,
577 m_measurement.observation(),
578 y
579 );
580 }
581
582private:
583
587 [[nodiscard]] static Out
588 makeOut([[maybe_unused]] M const & measurement, [[maybe_unused]] Space const & space)
589 {
590 if constexpr (Model::template has<PolicyKind::outputSpace>)
591 {
592 return measurement.model().template policy<PolicyKind::outputSpace>().in(space);
593 }
594 else if constexpr (OutputFromNoise)
595 {
596 return measurement.model().template policy<PolicyKind::additiveNoise>().distribution().space();
597 }
598 else if constexpr (FixedSizeSpace<Out> || !HasOutputSpace)
599 {
600 return Out{};
601 }
602 else
603 {
604 static_assert(
605 requires(Function const & f) { f.outputRows(); },
606 "a function that returns a vector of run-time size names its output with writes() or outputSpace()"
607 );
608 return Out{measurement.model().function().outputRows()};
609 }
610 }
611
615 [[nodiscard]] NoiseType
616 makeNoise([[maybe_unused]] M const & measurement) const
617 {
618 if constexpr (OwnNoise)
619 {
620 return measurement.noise();
621 }
622 else if constexpr (HasAdditiveNoise)
623 {
624 return placeNoise(m_out, measurement.model().template policy<PolicyKind::additiveNoise>());
625 }
626 else
627 {
628 return {};
629 }
630 }
631
635 template <typename X>
636 [[nodiscard]] auto
637 call(X const & x) const
638 {
639 static_assert(HasFunction, "a measurement model known by its likelihood alone has no function to evaluate");
640 return vectorReturned(callForm(m_measurement.model().function(), m_in, m_out, x));
641 }
642
648 template <bool Whole, typename R>
649 [[nodiscard]] auto
650 split(R && returned) const
651 {
652 using Plain = std::remove_cvref_t<R>;
653 if constexpr (!isWithJacobian<Plain>)
654 {
655 return std::forward<R>(returned);
656 }
657 else
658 {
659 using J = std::remove_cvref_t<decltype(returned.jacobian)>;
660 static_assert(
661 MatrixExpression<J>,
662 "a measurement model's function returns its Jacobian as one matrix over its input's differences, "
663 "withJacobian(y, J)"
664 );
665 auto const & jacobian = returned.jacobian;
666 EMRYS_FILTER_ASSERT(
667 jacobian.cols() == differenceDimension(m_in)
668 && "a returned Jacobian has a column per difference coordinate of the model's input"
669 );
670 using Scalar = typename J::Scalar;
671 constexpr int Rows = J::RowsAtCompileTime;
672 constexpr int MaxRows = J::MaxRowsAtCompileTime;
673 constexpr int Cols = Whole ? valueDimensionAtCompileTime<DifferenceSpace<Space>> : Reads::DifferenceSize;
674 constexpr int MaxCols =
675 Whole ? maxValueDimensionAtCompileTime<DifferenceSpace<Space>> : Reads::DifferenceMaxSize;
676 using StateBlock = MatrixOf<Scalar, Rows, Cols, MaxRows, MaxCols>;
677 StateBlock stateBlock =
678 StateBlock::Zero(jacobian.rows(), Whole ? m_stateDifferenceSize : m_reads.differenceIndices().size());
679 [&]<std::size_t... K>(std::index_sequence<K...>) {
680 ((stateBlock.middleCols(
681 Whole ? m_reads.differenceRows(K).start : m_reads.jointDifferenceStart(K),
682 m_differenceRows[stateItems(K)].size
683 ) = jacobian.middleCols(m_differenceRows[stateItems(K)].start, m_differenceRows[stateItems(K)].size)),
684 ...);
685 }(std::make_index_sequence<StateCount>{});
686 if constexpr (InputCount == 0)
687 {
688 return withJacobian(std::move(returned.value), std::move(stateBlock));
689 }
690 else
691 {
692 auto blocks = [&]<std::size_t... P>(std::index_sequence<P...>) {
693 return std::tuple{std::move(stateBlock), inputBlock<P>(jacobian)...};
694 }(std::make_index_sequence<InputCount>{});
695 return withJacobian(std::move(returned.value), std::move(blocks));
696 }
697 }
698 }
699
701 template <std::size_t P, typename J>
702 [[nodiscard]] auto
703 inputBlock(J const & jacobian) const
704 {
705 using Input =
706 std::remove_cvref_t<decltype(inputOf(std::declval<Argument<argumentOf(inputItems(P))> const &>()))>;
707 using Block = MatrixOf<
708 typename J::Scalar,
709 J::RowsAtCompileTime,
710 Input::DifferenceDimensionAtCompileTime,
711 J::MaxRowsAtCompileTime,
712 Input::MaxDifferenceDimensionAtCompileTime>;
713 BlockRows const & rows = m_differenceRows[inputItems(P)];
714 Block block = jacobian.middleCols(rows.start, rows.size);
715 return block;
716 }
717
718 M const & m_measurement;
720 EMRYS_FILTER_NO_UNIQUE_ADDRESS NoiseType m_noise;
721};
722
728template <typename R>
729class ResolvedFunction
730{
731public:
732
734 static constexpr bool EvaluatesBatches = R::BatchedFunction;
735
737 explicit ResolvedFunction(R const & resolved)
738 : m_resolved{resolved}
739 {}
740
742 template <typename Joint, typename... Inputs>
743 [[nodiscard]] auto
744 operator()(Joint const & joint, Inputs const &... inputs) const
745 {
746 return m_resolved.evaluate(joint, inputs...);
747 }
748
752 template <typename Y, typename Joint, typename... Inputs>
753 void
754 evaluateBatch(Y & y, Joint const & joint, Inputs const &... inputs) const
755 requires EvaluatesBatches
756 {
757 m_resolved.evaluateColumns(y, joint, inputs...);
758 }
759
763 template <typename Y, typename J, typename Joint, typename... Inputs>
764 void
765 evaluateBatchWithAffineJacobians(Y & y, J & jacobians, Joint const & joint, Inputs const &... inputs) const
766 requires EvaluatesBatches
767 {
768 m_resolved.evaluateColumnsWithAffineJacobians(y, jacobians, joint, inputs...);
769 }
770
771private:
772
773 R const & m_resolved;
774};
775
777template <typename Space, typename M>
778[[nodiscard]] ResolvedMeasurement<M, Space>
779resolveMeasurement(M const & measurement, Space const & space)
780{
781 return ResolvedMeasurement<M, Space>{measurement, space};
782}
783
789template <typename Scalar, typename R, typename Space>
790[[nodiscard]] typename Moments<Space, Scalar>::VectorType
791observedValue(R const & resolved, Space const & space)
792{
793 using Vector = typename Moments<Space, Scalar>::VectorType;
794 auto const & observation = resolved.observation();
795 EMRYS_FILTER_ASSERT(
796 observation.size() == valueDimension(space) && "the observation must be a value in the measurement space"
797 );
798 Vector result = observation.template cast<Scalar>();
799 EMRYS_FILTER_ASSERT(result.allFinite() && "the observation must be finite");
800 normalizeValues(space, result);
801 return result;
802}
803
807template <typename Scalar, typename R, typename Space, typename Predicted>
809observedResidual(R const & resolved, Space const & space, Eigen::MatrixBase<Predicted> const & predicted)
810{
811 using Difference = typename Moments<Space, Scalar>::DifferenceVectorType;
812 return Difference{Emrys::minus(space, observedValue<Scalar>(resolved, space), predicted)};
813}
814
818template <typename R, typename Method, typename F, typename... Args>
819[[nodiscard]] auto
820transformPrediction(R const & resolved, Method const & method, F const & f, Args const &... args)
821{
822 return transformBy(method, f, resolved.space(), args...);
823}
824
829template <typename R, typename Method, typename F, typename Read>
830[[nodiscard]] auto
831transformMeasurement(R const & resolved, Method const & method, F const & f, Read const & read)
832{
833 return std::apply(
834 [&](auto const &... inputs) {
835 if constexpr (isSigmaPointMethod<Method> && R::factorsAffine())
836 {
837 auto const factored = FactoredAt<R::affineSize(), R::affineMaxSize(), Method>{
838 method,
839 resolved.template affineCoordinates<false>()
840 };
841 return transformPrediction(resolved, factored, f, withCross(read), inputs...);
842 }
843 else
844 {
845 return transformPrediction(resolved, method, f, withCross(read), inputs...);
846 }
847 },
848 resolved.inputs()
849 );
850}
851
855template <typename R, typename Method, typename F, typename G, typename... Args>
856[[nodiscard]] decltype(auto)
857linearizePrediction(R const & resolved, Method const & method, F const & f, G const & g, Args const &... args)
858{
859 using Problem = Transform<F const, typename R::Out, Args...>;
860 auto const problem = Problem{f, resolved.space(), args...};
861 return g(problem, linearizeAt(method, problem));
862}
863
867template <typename R, typename Method, typename F, typename Read, typename G>
868[[nodiscard]] decltype(auto)
869linearizeMeasurement(R const & resolved, Method const & method, F const & f, Read const & read, G const & g)
870{
871 return std::apply(
872 [&](auto const &... inputs) -> decltype(auto) {
873 return linearizePrediction(resolved, method, f, g, withCross(read), inputs...);
874 },
875 resolved.inputs()
876 );
877}
878
884template <typename Space, typename Vector, typename Jacobian, typename Cross>
885class PredictionMoments
886{
887public:
888
892 PredictionMoments(Space const & space, Vector const & mean, Jacobian const & jacobian, Cross const & cross)
893 : m_space{space}
894 , m_mean{mean}
895 , m_jacobian{jacobian}
896 , m_cross{cross}
897 {}
898
900 [[nodiscard]] Space const &
901 space() const
902 {
903 return m_space;
904 }
905
907 [[nodiscard]] Vector const &
908 mean() const
909 {
910 return m_mean;
911 }
912
914 [[nodiscard]] auto
915 covarianceLower() const
916 {
917 return multiply(m_jacobian, m_cross);
918 }
919
923 template <typename Matrix, typename Noise>
924 [[nodiscard]] Matrix
925 covariancePlus(Noise const & noise) const
926 {
927 Matrix result = noise;
928 result.noalias() += multiply(m_jacobian, m_cross);
929 return result;
930 }
931
933 [[nodiscard]] Eigen::Index
934 valueDimension() const
935 {
936 return Emrys::valueDimension(m_space);
937 }
938
940 [[nodiscard]] Eigen::Index
941 differenceDimension() const
942 {
943 return Emrys::differenceDimension(m_space);
944 }
945
946private:
947
948 Space const & m_space;
949 Vector const & m_mean;
950 Jacobian const & m_jacobian;
951 Cross const & m_cross;
952};
953
958template <typename Matrix, typename Output, typename Noise>
959[[nodiscard]] Matrix
960innovationCovariancePlus(Output const & y, Noise const & noise)
961{
962 if constexpr (requires { y.template covariancePlus<Matrix>(noise); })
963 {
964 return y.template covariancePlus<Matrix>(noise);
965 }
966 else
967 {
968 Matrix result = noise;
969 result += y.covarianceLower();
970 return result;
971 }
972}
973
977struct NewPosteriors
978{};
979
986template <typename G, typename Accept = AcceptEvery>
987struct IntoPrior
988{
989 G & state;
990 Accept accept;
991};
992
994template <typename Sink>
995inline constexpr bool isIntoPrior = false;
996
998template <typename G, typename Accept>
999inline constexpr bool isIntoPrior<IntoPrior<G, Accept>>{true};
1000
1002
1010template <typename Update>
1011struct ReusedUpdate
1012{
1013 Update * update;
1014};
1015
1017template <typename Sink>
1018inline constexpr bool isReusedUpdate = false;
1019
1021template <typename Update>
1022inline constexpr bool isReusedUpdate<ReusedUpdate<Update>>{true};
1023
1025
1030template <typename Space, typename MeasurementSpace, typename Scalar, typename Sink>
1031[[nodiscard]] decltype(auto)
1032mixtureUpdateFor(Sink & sink)
1033{
1034 if constexpr (isReusedUpdate<Sink>)
1035 {
1036 using Update = std::remove_cvref_t<decltype(*sink.update)>;
1037 static_assert(
1038 std::is_same_v<typename Update::SpaceType, Space>
1039 && std::is_same_v<typename Update::MeasurementSpaceType, MeasurementSpace>
1040 && Update::BranchCount == std::dynamic_extent,
1041 "the update reused must be over the state and the innovation, with a run-time number of branches"
1042 );
1043 Update & result = *sink.update;
1044 result.branches.clear();
1045 return result;
1046 }
1047 else
1048 {
1049 return MeasurementUpdate<Space, MeasurementSpace, std::dynamic_extent, Scalar>{};
1050 }
1051}
1052
1057template <typename Sink, typename Update>
1058using MixtureOutcome =
1059 std::conditional_t<isReusedUpdate<Sink>, std::expected<void, GaussianError>, std::expected<Update, GaussianError>>;
1060
1064template <typename Sink, typename Update>
1065[[nodiscard]] MixtureOutcome<Sink, Update>
1066mixtureOutcome(Update & update)
1067{
1068 if constexpr (isReusedUpdate<Sink>)
1069 {
1070 return {};
1071 }
1072 else
1073 {
1074 return std::move(update);
1075 }
1076}
1077
1088template <typename Jacobian, typename Indices, typename Error>
1089struct ReadLinearization
1090{
1091 Jacobian jacobian;
1092 Indices const & indices;
1093 Error error;
1094};
1095
1097template <typename Jacobian, typename Indices, typename Error>
1098ReadLinearization(Jacobian, Indices const &, Error) -> ReadLinearization<Jacobian, Indices, Error>;
1099
1101
1106template <typename Matrix, typename R, typename Problem, typename Jacobian>
1107[[nodiscard]] Matrix
1108inputsCovariance(R const & resolved, Problem const & problem, Jacobian const & jacobian)
1109{
1110 using Scalar = typename Matrix::Scalar;
1111 Eigen::Index const m = jacobian.rows();
1112 Matrix result = Matrix::Zero(m, m);
1113 auto const inputs = resolved.inputs();
1114 [&]<std::size_t... P>(std::index_sequence<P...>) {
1115 [[maybe_unused]] auto const add = [&]<std::size_t K>(std::integral_constant<std::size_t, K>) {
1116 auto const & input = inputOf(std::get<K>(inputs));
1117 using Input = std::remove_cvref_t<decltype(input)>;
1118 using NoiseMatrix = MatrixOf<
1119 Scalar,
1120 Input::DifferenceDimensionAtCompileTime,
1121 Input::DifferenceDimensionAtCompileTime,
1122 Input::MaxDifferenceDimensionAtCompileTime,
1123 Input::MaxDifferenceDimensionAtCompileTime>;
1124 using InputJacobian = MatrixOf<
1125 Scalar,
1126 Matrix::RowsAtCompileTime,
1127 Input::DifferenceDimensionAtCompileTime,
1128 Matrix::MaxRowsAtCompileTime,
1129 Input::MaxDifferenceDimensionAtCompileTime>;
1130 Eigen::Index const d = input.differenceDimension();
1131 InputJacobian const block = jacobian.middleCols(problem.differenceOffset(K + 1), d);
1132 NoiseMatrix const cov = symmetricFromLower<NoiseMatrix>(covarianceLowerOf(input));
1133 result.noalias() += multiply(multiply(block, cov), block.transpose());
1134 };
1135 (add(std::integral_constant<std::size_t, P>{}), ...);
1136 }(std::make_index_sequence<R::InputCount>{});
1137 if constexpr (R::InputCount > 0)
1138 {
1139 result = (Scalar(0.5) * (result + result.transpose())).eval();
1140 }
1141 return result;
1142}
1143
1145template <typename P, typename... Qs>
1146inline constexpr bool liesInNoOther = ((std::is_same_v<P, Qs> || !pathLiesIn<P, Qs>) && ...);
1147
1149template <typename C>
1150struct ConsideredRowsOf;
1151
1153template <typename... Paths>
1154struct ConsideredRowsOf<Consider<Paths...>>
1155{
1157 template <typename Space>
1158 [[nodiscard]] static ConsideredRows<sizeof...(Paths)>
1159 resolve([[maybe_unused]] Space const & space)
1160 {
1161 static_assert(
1162 ([]<typename First, typename... Rest>(TagPath<First, Rest...> *) {
1163 return countTag<First, Space>() > 0;
1164 }(static_cast<Paths *>(nullptr))
1165 && ...),
1166 "a path of consider() names no block of the state"
1167 );
1168 static_assert((liesInNoOther<Paths, Paths...> && ...), "a block of consider() lies in another it names");
1169 return {differenceBlockRows<Paths>(space)...};
1170 }
1171};
1172
1174
1178template <typename R, typename Space>
1179[[nodiscard]] auto
1180consideredRows(Space const & space)
1181{
1182 return ConsideredRowsOf<typename R::ConsiderType>::resolve(space);
1183}
1184
1185template <
1186 bool Statistics,
1187 typename Prior,
1188 typename R,
1189 typename Output,
1190 typename Cross,
1191 typename Jacobian,
1192 typename Sink>
1193[[nodiscard]] decltype(auto)
1194conditionOnPrediction(
1195 Prior const & prior,
1196 R const & resolved,
1197 Output const & y,
1198 Cross const & cross,
1199 Jacobian const & readJacobian,
1200 Sink && sink
1201);
1202
1203} // namespace internal
1204
1205} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Batched functions (decisions.md [batched-functions]): batched(), the mark that a function takes every...
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 macros every header includes first.
Measurement models and their observations (decisions.md [model-policies], [sensors],...
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'...
The policies a model is built from, and the marks on a step's arguments (decisions....
Product: the product of independent densities, each on its own block of a space, built with on<Path>(...
How a model's reads() meet a state and a call, all internal (decisions.md [model-policies],...
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...
DifferenceVector< Space_, Scalar > DifferenceVectorType
a difference, such as a residual
Definition Moments.h:75
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
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 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
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
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
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
WithJacobian< internal::PlainResult< std::remove_cvref_t< Value > >, internal::PlainResult< std::remove_cvref_t< Jacobian > > > withJacobian(Value &&value, Jacobian &&jacobian)
Definition Methods.h:222