EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
ProcessModel.h
Go to the documentation of this file.
1#pragma once
2
14
15#include <array>
16#include <cstddef>
17#include <optional>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21
22#include <Eigen/LU>
23
43
44namespace Emrys
45{
46
47namespace internal
48{
49
54template <typename Space>
55class RowCoverage
56{
57public:
58
60 explicit RowCoverage(Space const & space)
61 : m_counts{CountsType::Zero(valueDimension(space))}
62 {}
63
65 void
66 push_back(BlockRows const & rows)
67 {
68 if (rows.start < 0 || rows.size < 0 || rows.start + rows.size > m_counts.size())
69 {
70 m_inRange = false;
71 }
72 else
73 {
74 m_counts.segment(rows.start, rows.size) += 1;
75 }
76 }
77
79 [[nodiscard]] bool
80 partitioned() const
81 {
82 return m_inRange && (m_counts == 1).all();
83 }
84
85private:
86
88 using CountsType = Eigen::
89 Array<int, valueDimensionAtCompileTime<Space>, 1, Eigen::ColMajor, maxValueDimensionAtCompileTime<Space>, 1>;
90
91 CountsType m_counts;
92 bool m_inRange = true;
93};
94
99enum class StaticPartition
100{
101 unknown,
102 holds,
103 fails
104};
105
109template <typename Space, typename... Lists>
110consteval bool
111pathsPartition()
112{
113 auto counts = std::array<int, std::size_t(Space::FixedValueDimension)>{};
114 [[maybe_unused]] auto const count = [&]<typename... Paths>(TagPaths<Paths...> *) { // unused for no Lists
115 (
116 [&] {
117 constexpr ResolvedIndex rows = ResolvePath<Space, Paths>::value;
118 for (int i = rows.offset; i < rows.offset + rows.size; ++i)
119 {
120 ++counts[std::size_t(i)];
121 }
122 }(),
123 ...
124 );
125 };
126 (count(static_cast<Lists *>(nullptr)), ...);
127 for (int const owners : counts)
128 {
129 if (owners != 1)
130 {
131 return false;
132 }
133 }
134 return true;
135}
136
140template <typename Model, typename Space>
141consteval StaticPartition
142staticPartition()
143{
144 if constexpr (requires { Model::template staticPartition<Space>(); })
145 {
146 return Model::template staticPartition<Space>();
147 }
148 else
149 {
150 return StaticPartition::unknown;
151 }
152}
153
158template <typename Model, typename Space>
159void
160assertPartitions(
161 [[maybe_unused]] Model const & model,
162 [[maybe_unused]] Space const & space,
163 [[maybe_unused]] char const * message = "the process model must write every coordinate of the state space once"
164)
165{
166 constexpr StaticPartition known = staticPartition<Model, Space>();
167 static_assert(
168 known != StaticPartition::fails,
169 "the process model must write every coordinate of the state space exactly once: a block it writes twice, or "
170 "one no model writes, is not a partition"
171 );
172 if constexpr (known == StaticPartition::unknown)
173 {
174 EMRYS_FILTER_ASSERT(model.partitions(space) && message);
175 }
176}
177
181template <typename Space, typename Paths>
182consteval StaticPartition
183staticPartitionOfWrites()
184{
185 if constexpr (Space::ValueDimensionStep == 0)
186 {
187 return pathsPartition<Space, Paths>() ? StaticPartition::holds : StaticPartition::fails;
188 }
189 else
190 {
191 return StaticPartition::unknown;
192 }
193}
194
196template <typename Space, typename... Paths>
197[[nodiscard]] bool
198writesPartition(Space const & space, TagPaths<Paths...> *)
199{
200 auto coverage = RowCoverage<Space>{space};
201 (coverage.push_back(blockRows<Paths>(space)), ...);
202 return coverage.partitioned();
203}
204
206template <typename P>
207struct PathsOf;
208
210template <typename... Paths>
211struct PathsOf<TagPaths<Paths...>>
212{
213 using type = std::tuple<Paths...>;
214};
215
217
221template <typename Model>
222inline constexpr bool writesWholeState = !Model::template has<PolicyKind::writes>;
223
225template <typename Model>
226using WritePathsOf = typename Model::template Policy<PolicyKind::writes>::PathList;
227
231template <typename Model>
232using OutputSpaceOf = typename Model::template Policy<PolicyKind::writes>::OutputSpace;
233
235template <typename Model, std::size_t K>
236using WritePathAt = std::tuple_element_t<K, typename PathsOf<WritePathsOf<Model>>::type>;
237
239template <typename Model, std::size_t K>
240using WriteNameAt = std::tuple_element_t<K, CompositeBlocks<OutputSpaceOf<Model>>>;
241
243template <typename Model>
244inline constexpr std::size_t writeCount = std::tuple_size_v<typename PathsOf<WritePathsOf<Model>>::type>;
245
249template <typename Model, typename Space>
250using OutputSpaceIn = typename decltype([] {
251 if constexpr (writesWholeState<Model>)
252 {
253 return std::type_identity<Space>{};
254 }
255 else
256 {
257 return std::type_identity<OutputSpaceOf<Model>>{};
258 }
259}())::type;
260
265template <typename Model, typename Space>
266[[nodiscard]] OutputSpaceIn<Model, Space>
267outputOf(Space const & space)
268{
269 if constexpr (writesWholeState<Model>)
270 {
271 return space;
272 }
273 else
274 {
275 return [&]<std::size_t... K>(std::index_sequence<K...>) {
276 return OutputSpaceOf<Model>{nameOf<WriteNameAt<Model, K>>(pathBlock<WritePathAt<Model, K>>(space))...};
277 }(std::make_index_sequence<writeCount<Model>>{});
278 }
279}
280
285template <typename Target, typename Source>
286[[nodiscard]] Target
287renamedInstance(Source const & source)
288{
289 if constexpr (std::is_same_v<Target, Source>)
290 {
291 return source;
292 }
293 else
294 {
295 return []<typename... Names, typename... Blocks>(
296 Source const & from,
297 std::tuple<Names...> *,
298 std::tuple<Blocks...> *
299 ) {
300 return Target{nameOf<Names>(pathBlock<Blocks>(from))...};
301 }(source, static_cast<CompositeBlocks<Target> *>(nullptr), static_cast<CompositeBlocks<Source> *>(nullptr));
302 }
303}
304
306template <typename T>
307inline constexpr bool isOnPath = false;
308
310template <typename X, typename Path>
311inline constexpr bool isOnPath<OnPath<X, Path>>{true};
312
314
318template <typename Matrix, typename Value, typename Scalar>
319void
320addSpectralDensity(Matrix & q, BlockRows const & rows, Value const & value, Scalar dt)
321{
322 if constexpr (MatrixExpression<Value>)
323 {
324 EMRYS_FILTER_ASSERT(
325 value.rows() == rows.size && value.cols() == rows.size
326 && "a spectral density matrix is over the block's differences"
327 );
328 q.block(rows.start, rows.start, rows.size, rows.size) += dt * value.template cast<Scalar>();
329 }
330 else
331 {
332 q.block(rows.start, rows.start, rows.size, rows.size).diagonal().array() += dt * Scalar(value);
333 }
334}
335
340template <typename Out, typename Matrix, typename Part, typename Scalar>
341void
342addContinuousPart(Out const & out, Matrix & q, Part const & part, Scalar dt)
343{
344 if constexpr (isOnPath<Part>)
345 {
346 [&]<typename X, typename Path>(OnPath<X, Path> const *) {
347 addSpectralDensity(q, differenceBlockRows<Path>(out), part.value, dt);
348 }(static_cast<Part const *>(nullptr));
349 }
350 else if constexpr (isPlacedByPath<Part>)
351 {
352 static_assert(
353 std::is_void_v<Part>,
354 "continuousNoise() takes no onEach<Path>(q); place the model on each repetition with onEach<Path>(model), "
355 "and "
356 "give it continuousNoise(q) for one repetition"
357 );
358 }
359 else
360 {
361 addSpectralDensity(q, BlockRows{0, differenceDimension(out)}, part, dt);
362 }
363}
364
372template <typename Set>
373[[nodiscard]] consteval bool
374checkProcessPolicies()
375{
376 static_assert(
377 Set::template has<PolicyKind::reads> || !Set::template has<PolicyKind::writes>,
378 "a process model that writes blocks with writes() says what it reads with reads(); give neither to read and "
379 "write the whole state"
380 );
381 static_assert(
382 Set::template has<PolicyKind::writes> || !Set::template has<PolicyKind::reads>,
383 "a process model that reads blocks with reads() says which blocks of the state it writes with writes(); give "
384 "neither to read and write the whole state"
385 );
386 static_assert(
387 Set::template has<PolicyKind::reads> || !Set::template has<PolicyKind::affineIn>,
388 "affineIn() names items of reads(), and a model without reads() reads the whole state under no names"
389 );
390 static_assert(
391 !Set::template has<PolicyKind::logLikelihood>,
392 "logLikelihood(g) is a measurement model's; a process model has no observation to weight points by"
393 );
394 static_assert(
395 !Set::template has<PolicyKind::sample>,
396 "sample(g) is a measurement model's; a process model's draw is its step and its noise"
397 );
398 static_assert(
399 !Set::template has<PolicyKind::outputSpace>,
400 "a process model's output is the blocks of the state it writes, whose spaces the state has, so it takes no "
401 "outputSpace()"
402 );
403 static_assert(
404 !(Set::template has<PolicyKind::continuousNoise> && Set::template has<PolicyKind::additiveNoise>),
405 "a process model's noise is continuousNoise(), discretized over the step, or additiveNoise(), a fixed Q, not "
406 "both"
407 );
408 return true;
409}
410
415template <typename Matrix>
416class LinearStep
417{
418public:
419
420 static constexpr bool IsAffine = true;
421
423 explicit LinearStep(Matrix matrix)
424 : m_matrix{std::move(matrix)}
425 {}
426
428 [[nodiscard]] Matrix const &
429 matrix() const
430 {
431 return m_matrix;
432 }
433
437 template <typename In, typename Out, typename X, typename TimeScalar>
438 [[nodiscard]] auto
439 operator()([[maybe_unused]] In const & in, Out const &, X const & x, TimeScalar) const
440 {
441 static_assert(
442 blocksAddValues<In>,
443 "a matrix process model moves a state whose coordinates add their values; a state with a block such as a "
444 "unit quaternion takes a function"
445 );
446 static_assert(
447 Matrix::ColsAtCompileTime == Eigen::Dynamic || valueDimensionAtCompileTime<In> == Eigen::Dynamic
448 || (Matrix::ColsAtCompileTime == valueDimensionAtCompileTime<In>
449 && Matrix::RowsAtCompileTime == valueDimensionAtCompileTime<In>),
450 "a matrix process model's matrix is square, with a row and a column per coordinate of the state"
451 );
452 EMRYS_FILTER_ASSERT(
453 m_matrix.rows() == x.size() && m_matrix.cols() == x.size()
454 && "a matrix process model's matrix is square, with a row and a column per coordinate of the state"
455 );
456 using T = typename X::Scalar;
457 using Value = Eigen::Matrix<T, Matrix::RowsAtCompileTime, 1, Eigen::ColMajor, Matrix::MaxRowsAtCompileTime, 1>;
458 Value value = m_matrix.template cast<T>() * x;
459 return WithJacobian<Value, Matrix const &>{std::move(value), m_matrix};
460 }
461
462private:
463
464 Matrix m_matrix;
465};
466
471template <bool Whole, typename Writes>
472struct ProcessWrites
473{
475 using Paths = typename Writes::PathList;
477 using OutputSpace = typename Writes::OutputSpace;
478};
479
481template <typename Writes>
482struct ProcessWrites<true, Writes>
483{
485 using Paths = TagPaths<WholeSpace>;
487 using OutputSpace = WholeSpace;
488};
489
490} // namespace internal
491
516template <typename F, typename... Policies>
518{
520 using Set = internal::PolicySet<Policies...>;
521
522public:
523
525 using FunctionType = F;
526
527 static constexpr bool IsModel = true;
528 static constexpr bool HasFunction = true;
529
531 template <internal::PolicyKind K>
532 static constexpr bool has = Set::template has<K>;
533
535 template <internal::PolicyKind K>
536 using Policy = typename Set::template Type<K>;
537
538 // processModel() and the placements give both reads() and writes(), or neither.
540
542 static constexpr bool Whole = !has<internal::PolicyKind::writes>;
543
545 using WritePaths = typename internal::ProcessWrites<Whole, Policy<internal::PolicyKind::writes>>::Paths;
546
550 using OutputSpace = typename internal::ProcessWrites<Whole, Policy<internal::PolicyKind::writes>>::OutputSpace;
551
553 explicit ProcessFunction(F function, Policies... policies)
554 : m_function{std::move(function)}
555 , m_policies{std::move(policies)...}
556 {}
557
559 [[nodiscard]] F const &
560 function() const
561 {
562 return m_function;
563 }
564
566 template <internal::PolicyKind K>
567 [[nodiscard]] Policy<K> const &
568 policy() const
569 {
570 return Set::template get<K>(m_policies);
571 }
572
574 [[nodiscard]] std::tuple<Policies...> const &
575 policies() const
576 {
577 return m_policies;
578 }
579
584 template <typename Scalar, typename Out>
585 [[nodiscard]] typename Moments<Out, Scalar>::MatrixType
586 noiseOver(Out const & out, [[maybe_unused]] Scalar dt) const
587 {
588 using Matrix = typename Moments<Out, Scalar>::MatrixType;
589 Eigen::Index const n = differenceDimension(out);
590 Matrix q = Matrix::Zero(n, n);
591 if constexpr (requires { m_function.template noiseOver<Scalar>(out, dt); })
592 {
593 q = m_function.template noiseOver<Scalar>(out, dt);
594 }
596 {
597 q = policy<internal::PolicyKind::discreteNoise>().function(out, dt);
598 }
600 {
601 std::apply(
602 [&](auto const &... parts) {
603 (internal::addContinuousPart(out, q, parts, dt), ...);
604 },
606 );
607 }
609 {
610 auto const placed = internal::placeNoise(out, policy<internal::PolicyKind::additiveNoise>());
611 using Placed = std::remove_cvref_t<decltype(placed)>;
612 static_assert(
613 !internal::isGaussianMixture<typename Placed::Distribution>,
614 "a process model's additive noise is one Gaussian; a mixture over the step is a mixture argument of "
615 "predict() (roadmap 17z)"
616 );
617 auto const & distribution = placed.distribution();
618 q = internal::symmetricFromLower<Matrix>(distribution.covarianceLower().template cast<Scalar>());
619 }
620 return q;
621 }
622
626 template <typename Space>
627 [[nodiscard]] static consteval internal::StaticPartition
629 {
630 if constexpr (Whole)
631 {
632 return internal::StaticPartition::holds;
633 }
634 else
635 {
636 return internal::staticPartitionOfWrites<Space, WritePaths>();
637 }
638 }
639
643 template <typename Space>
644 [[nodiscard]] bool
645 partitions([[maybe_unused]] Space const & space) const
646 {
647 if constexpr (Whole)
648 {
649 return true;
650 }
651 else
652 {
653 return internal::writesPartition(space, static_cast<WritePaths *>(nullptr));
654 }
655 }
656
657private:
658
659 EMRYS_FILTER_NO_UNIQUE_ADDRESS F m_function;
660 std::tuple<Policies...> m_policies;
661};
662
699template <typename F, typename... Policies>
700 requires(!MatrixExpression<F> && !internal::ModelPolicy<F>)
701[[nodiscard]] auto
702processModel(F && function, Policies &&... policies)
703{
704 using Set = internal::PolicySet<std::remove_cvref_t<Policies>...>;
705 static_assert(internal::checkProcessPolicies<Set>());
706 return ProcessFunction<std::decay_t<F>, std::remove_cvref_t<Policies>...>{
707 std::forward<F>(function),
708 std::forward<Policies>(policies)...
709 };
710}
711
724template <MatrixExpression Matrix, typename... Policies>
725[[nodiscard]] auto
726processModel(Matrix && matrix, Policies &&... policies)
727{
728 using Plain = internal::PlainResult<std::remove_cvref_t<Matrix>>;
729 using Set = internal::PolicySet<std::remove_cvref_t<Policies>...>;
730 static_assert(
731 !Set::template has<internal::PolicyKind::reads> && !Set::template has<internal::PolicyKind::writes>,
732 "a matrix process model is over the whole state, so it takes no reads() or writes(); give a function for a "
733 "model of some blocks"
734 );
735 static_assert(internal::checkProcessPolicies<Set>());
736 return ProcessFunction<internal::LinearStep<Plain>, std::remove_cvref_t<Policies>...>{
737 internal::LinearStep<Plain>{Plain{std::forward<Matrix>(matrix)}},
738 std::forward<Policies>(policies)...
739 };
740}
741
742namespace internal
743{
744
746template <typename F, typename... Policies>
747std::true_type
748processFunctionBase(ProcessFunction<F, Policies...> const *);
749
751std::false_type
752processFunctionBase(void const *);
753
755template <typename Path>
756using ReadsOne = Reads<ReadItem<AsTagPath<Path>, PathTag<Path>>>;
757
759template <typename... Paths>
760using ReadsOf = Reads<ReadItem<AsTagPath<Paths>, PathTag<Paths>>...>;
761
763template <typename... Paths>
764using WritesOf = Writes<AsTagPath<Paths>...>;
765
769struct IdentityStep
770{
772 template <typename In, typename Out, typename X, typename TimeScalar>
773 [[nodiscard]] auto
774 operator()(In const &, Out const & out, X const & x, TimeScalar) const
775 {
776 using T = ScalarOf<X>;
777 using JacobianMatrix = typename Moments<Out, TimeScalar>::MatrixType;
778 Eigen::Index const n = differenceDimension(out);
779 return withJacobian(typename Moments<Out, T>::VectorType{x}, JacobianMatrix{JacobianMatrix::Identity(n, n)});
780 }
781};
782
784template <int Dimension, typename Scalar>
785struct RandomWalkNoise
786{
787 Eigen::Matrix<Scalar, Dimension, Dimension> spectralDensity;
788
790 template <typename Out, typename TimeScalar>
791 [[nodiscard]] typename Moments<Out, TimeScalar>::MatrixType
792 operator()(Out const &, TimeScalar dt) const
793 {
794 return typename Moments<Out, TimeScalar>::MatrixType{dt * spectralDensity.template cast<TimeScalar>()};
795 }
796};
797
799[[nodiscard]] constexpr int
800factorial(int n)
801{
802 return n <= 1 ? 1 : n * factorial(n - 1);
803}
804
808template <int N, typename TimeScalar>
809[[nodiscard]] std::array<TimeScalar, N>
810powersOf(TimeScalar dt)
811{
812 auto result = std::array<TimeScalar, N>{};
813 result[0] = TimeScalar(1);
814 for (int k = 1; k < N; ++k)
815 {
816 result[k] = result[k - 1] * dt;
817 }
818 return result;
819}
820
825template <int Dimension, typename... Tags>
826struct KinematicStep
827{
828 static constexpr int Order = int(sizeof...(Tags));
829
831 template <typename In, typename Out, typename X, typename TimeScalar>
832 [[nodiscard]] auto
833 operator()(In const & in, Out const & out, X const & x, TimeScalar dt) const
834 {
835 using T = ScalarOf<X>;
836 using Vector = typename Moments<Out, T>::VectorType;
837 using JacobianMatrix = typename Moments<Out, TimeScalar>::MatrixType;
838 auto const starts = std::array<Eigen::Index, Order>{blockRows<Tags>(in).start...};
839 auto const differenceStarts = std::array<Eigen::Index, Order>{differenceBlockRows<Tags>(out).start...};
840 auto const dtPowers = powersOf<Order>(dt);
841 Eigen::Index const n = differenceDimension(out);
842 Vector y{x};
843 JacobianMatrix jacobian = JacobianMatrix::Identity(n, n);
844 for (int i = 0; i < Order; ++i)
845 {
846 for (int j = i + 1; j < Order; ++j)
847 {
848 TimeScalar const scale = dtPowers[j - i] / TimeScalar(factorial(j - i));
849 y.template segment<Dimension>(starts[i]) += T(scale) * x.template segment<Dimension>(starts[j]);
850 jacobian.template block<Dimension, Dimension>(differenceStarts[i], differenceStarts[j])
851 .diagonal()
852 .setConstant(scale);
853 }
854 }
855 return withJacobian(std::move(y), std::move(jacobian));
856 }
857};
858
863template <int Dimension, typename Scalar, typename... Tags>
864struct KinematicNoise
865{
866 static constexpr int Order = int(sizeof...(Tags));
867
868 Eigen::Matrix<Scalar, Dimension, Dimension> spectralDensity;
869
873 [[nodiscard]] static constexpr int
874 denominator(int i, int j)
875 {
876 return (2 * Order - 1 - i - j) * factorial(Order - 1 - i) * factorial(Order - 1 - j);
877 }
878
880 template <typename Out, typename TimeScalar>
881 [[nodiscard]] typename Moments<Out, TimeScalar>::MatrixType
882 operator()(Out const & out, TimeScalar dt) const
883 {
884 using Matrix = typename Moments<Out, TimeScalar>::MatrixType;
885 auto const starts = std::array<Eigen::Index, Order>{differenceBlockRows<Tags>(out).start...};
886 auto const dtPowers = powersOf<2 * Order>(dt);
887 Eigen::Index const n = differenceDimension(out);
888 Matrix q = Matrix::Zero(n, n);
889 for (int i = 0; i < Order; ++i)
890 {
891 for (int j = 0; j < Order; ++j)
892 {
893 TimeScalar const scale = dtPowers[2 * Order - 1 - i - j] / TimeScalar(denominator(i, j));
894 q.template block<Dimension, Dimension>(starts[i], starts[j]) =
895 scale * spectralDensity.template cast<TimeScalar>();
896 }
897 }
898 return q;
899 }
900};
901
905template <typename Scalar>
906struct OrnsteinUhlenbeckStep
907{
908 Scalar timeConstant;
909
911 template <typename TimeScalar>
912 [[nodiscard]] TimeScalar
913 decay(TimeScalar dt) const
914 {
915 return Eigen::numext::exp(TimeScalar(-dt / TimeScalar(timeConstant)));
916 }
917
919 template <typename In, typename Out, typename X, typename TimeScalar>
920 [[nodiscard]] auto
921 operator()(In const &, Out const & out, X const & x, TimeScalar dt) const
922 {
923 using T = ScalarOf<X>;
924 using JacobianMatrix = typename Moments<Out, TimeScalar>::MatrixType;
925 TimeScalar const phi = decay(dt);
926 Eigen::Index const n = differenceDimension(out);
927 return withJacobian(
928 typename Moments<Out, T>::VectorType{T(phi) * x},
929 JacobianMatrix{phi * JacobianMatrix::Identity(n, n)}
930 );
931 }
932};
933
937template <typename Scalar>
938struct OrnsteinUhlenbeckNoise
939{
940 Scalar timeConstant;
941 Scalar variance;
942
944 template <typename Out, typename TimeScalar>
945 [[nodiscard]] typename Moments<Out, TimeScalar>::MatrixType
946 operator()(Out const & out, TimeScalar dt) const
947 {
948 using Matrix = typename Moments<Out, TimeScalar>::MatrixType;
949 TimeScalar const phi = Eigen::numext::exp(TimeScalar(-dt / TimeScalar(timeConstant)));
950 Eigen::Index const n = differenceDimension(out);
951 return Matrix{TimeScalar(variance) * (1 - phi * phi) * Matrix::Identity(n, n)};
952 }
953};
954
955} // namespace internal
956
971template <typename Path, RealScalar Scalar_ = double>
973 : public ProcessFunction<
974 internal::IdentityStep,
975 internal::ReadsOne<Path>,
976 internal::WritesOf<Path>,
977 AffineIn<internal::PathTag<Path>>,
978 internal::DiscreteNoise<internal::RandomWalkNoise<
979 internal::DifferenceSpaceOf<internal::PathTag<Path>>::type::FixedValueDimension,
980 Scalar_>>>
981{
983 using Differences = typename internal::DifferenceSpaceOf<internal::PathTag<Path>>::type;
984
985 static_assert(Differences::ValueDimensionStep == 0, "the block of a random walk must have a fixed size");
986
988 using Noise = internal::RandomWalkNoise<Differences::FixedValueDimension, Scalar_>;
989
991 using Base = ProcessFunction<
992 internal::IdentityStep,
993 internal::ReadsOne<Path>,
994 internal::WritesOf<Path>,
996 internal::DiscreteNoise<Noise>>;
997
998public:
999
1001 static constexpr int Dimension = Differences::FixedValueDimension;
1002
1004 using Scalar = Scalar_;
1005
1007 using SpectralDensityMatrix = Eigen::Matrix<Scalar_, Dimension, Dimension>;
1008
1013
1016 : Base{
1017 internal::IdentityStep{},
1018 internal::ReadsOne<Path>{},
1019 internal::WritesOf<Path>{},
1020 AffineIn<internal::PathTag<Path>>{},
1021 internal::DiscreteNoise<Noise>{Noise{spectralDensity}}
1022 }
1023 {}
1024
1026 [[nodiscard]] SpectralDensityMatrix const &
1028 {
1029 return this->template policy<internal::PolicyKind::discreteNoise>().function.spectralDensity;
1030 }
1031};
1032
1061template <RealScalar Scalar_, typename... Paths>
1063 : public ProcessFunction<
1064 internal::KinematicStep<
1065 internal::PathTag<std::tuple_element_t<0, std::tuple<Paths...>>>::FixedValueDimension,
1066 internal::PathTag<Paths>...>,
1067 internal::ReadsOf<Paths...>,
1068 internal::WritesOf<Paths...>,
1069 AffineIn<internal::PathTag<Paths>...>,
1070 internal::DiscreteNoise<internal::KinematicNoise<
1071 internal::PathTag<std::tuple_element_t<0, std::tuple<Paths...>>>::FixedValueDimension,
1072 Scalar_,
1073 internal::PathTag<Paths>...>>>
1074{
1075 static_assert(sizeof...(Paths) > 1, "a kinematic chain needs at least two blocks; one block is a RandomWalk");
1076
1078 using First = internal::PathTag<std::tuple_element_t<0, std::tuple<Paths...>>>;
1079
1080public:
1081
1083 static constexpr int Order = int(sizeof...(Paths));
1084
1086 static constexpr int Dimension = First::FixedValueDimension;
1087
1088 static_assert(
1089 ((internal::PathTag<Paths>::ValueDimensionStep == 0
1090 && internal::PathTag<Paths>::FixedValueDimension == Dimension)
1091 && ...),
1092 "the blocks of a kinematic chain must have the same fixed size"
1093 );
1094
1095 static_assert(
1096 (internal::hasEqualSizes<internal::PathTag<Paths>> && ...),
1097 "the blocks of a kinematic chain must have differences of the same size as their values"
1098 );
1099
1101 using Scalar = Scalar_;
1102
1104 using SpectralDensityMatrix = Eigen::Matrix<Scalar_, Dimension, Dimension>;
1105
1106private:
1107
1109 using Step = internal::KinematicStep<Dimension, internal::PathTag<Paths>...>;
1110
1112 using Noise = internal::KinematicNoise<Dimension, Scalar_, internal::PathTag<Paths>...>;
1113
1115 using Base = ProcessFunction<
1116 Step,
1117 internal::ReadsOf<Paths...>,
1118 internal::WritesOf<Paths...>,
1120 internal::DiscreteNoise<Noise>>;
1121
1122public:
1123
1128
1131 : Base{
1132 Step{},
1133 internal::ReadsOf<Paths...>{},
1134 internal::WritesOf<Paths...>{},
1135 AffineIn<internal::PathTag<Paths>...>{},
1136 internal::DiscreteNoise<Noise>{Noise{spectralDensity}}
1137 }
1138 {}
1139
1141 [[nodiscard]] SpectralDensityMatrix const &
1143 {
1144 return this->template policy<internal::PolicyKind::discreteNoise>().function.spectralDensity;
1145 }
1146};
1147
1151template <typename Position, typename Velocity, RealScalar Scalar = double>
1153
1157template <typename Position, typename Velocity, typename Acceleration, RealScalar Scalar = double>
1159
1164template <RealScalar Scalar = double>
1166{
1167 Scalar value;
1168};
1169
1174template <RealScalar Scalar = double>
1176{
1177 Scalar value;
1178};
1179
1181template <typename Value>
1182 requires std::is_arithmetic_v<Value>
1184
1185template <typename Value>
1186 requires std::is_arithmetic_v<Value>
1188
1190
1209template <typename Path, RealScalar Scalar_ = double>
1211 : public ProcessFunction<
1212 internal::OrnsteinUhlenbeckStep<Scalar_>,
1213 internal::ReadsOne<Path>,
1214 internal::WritesOf<Path>,
1215 AffineIn<internal::PathTag<Path>>,
1216 internal::DiscreteNoise<internal::OrnsteinUhlenbeckNoise<Scalar_>>>
1217{
1219 using Base = ProcessFunction<
1220 internal::OrnsteinUhlenbeckStep<Scalar_>,
1221 internal::ReadsOne<Path>,
1222 internal::WritesOf<Path>,
1224 internal::DiscreteNoise<internal::OrnsteinUhlenbeckNoise<Scalar_>>>;
1225
1226public:
1227
1229 using Scalar = Scalar_;
1230
1233 : Base{
1234 internal::OrnsteinUhlenbeckStep<Scalar_>{timeConstant.value},
1235 internal::ReadsOne<Path>{},
1236 internal::WritesOf<Path>{},
1237 AffineIn<internal::PathTag<Path>>{},
1238 internal::DiscreteNoise<internal::OrnsteinUhlenbeckNoise<Scalar_>>{
1239 internal::OrnsteinUhlenbeckNoise<Scalar_>{timeConstant.value, variance.value}
1240 }
1241 }
1242 {
1243 EMRYS_FILTER_ASSERT(timeConstant.value > 0 && "the time constant must be positive");
1244 EMRYS_FILTER_ASSERT(variance.value >= 0 && "the variance must be nonnegative");
1245 }
1246
1248 [[nodiscard]] Scalar_
1250 {
1251 return this->function().timeConstant;
1252 }
1253
1255 [[nodiscard]] Scalar_
1256 variance() const
1257 {
1258 return this->template policy<internal::PolicyKind::discreteNoise>().function.variance;
1259 }
1260};
1261
1267template <typename Path>
1269 : public ProcessFunction<
1270 internal::IdentityStep,
1271 internal::ReadsOne<Path>,
1272 internal::WritesOf<Path>,
1274{
1276 using Base = ProcessFunction<
1277 internal::IdentityStep,
1278 internal::ReadsOne<Path>,
1279 internal::WritesOf<Path>,
1281
1282public:
1283
1286 : Base{
1287 internal::IdentityStep{},
1288 internal::ReadsOne<Path>{},
1289 internal::WritesOf<Path>{},
1290 AffineIn<internal::PathTag<Path>>{}
1291 }
1292 {}
1293};
1294
1295namespace internal
1296{
1297
1299template <typename R>
1300struct ItemsOf;
1301
1303template <typename... Items>
1304struct ItemsOf<Reads<Items...>>
1305{
1306 using type = std::tuple<Items...>;
1307};
1308
1310
1314template <typename Acc, typename... Items>
1315struct ReadUnion
1316{
1317 using type = Acc;
1318};
1319
1321template <typename... Kept, typename First, typename... Rest>
1322struct ReadUnion<Reads<Kept...>, First, Rest...>
1323{
1324 static constexpr bool present = (std::is_same_v<typename Kept::NameType, typename First::NameType> || ...);
1325
1326 static_assert(
1327 ((!std::is_same_v<typename Kept::NameType, typename First::NameType>
1328 || std::is_same_v<typename Kept::PathType, typename First::PathType>)
1329 && ...),
1330 "two parts of a composition read different blocks under one name; read one under another name with "
1331 "on<Name>(path) in its reads()"
1332 );
1333
1334 using type = typename ReadUnion<std::conditional_t<present, Reads<Kept...>, Reads<Kept..., First>>, Rest...>::type;
1335};
1336
1338
1340template <typename Tuple>
1341struct ReadUnionOf;
1342
1344template <typename... Items>
1345struct ReadUnionOf<std::tuple<Items...>>
1346{
1347 using type = typename ReadUnion<Reads<>, Items...>::type;
1348};
1349
1351
1355template <typename Paths, typename Names>
1356struct WritesOfTuple;
1357
1359template <typename... Paths, typename... Names>
1360struct WritesOfTuple<std::tuple<Paths...>, std::tuple<Names...>>
1361{
1362 static_assert(distinctTypes<Paths...>(), "two parts of a composition write the same block");
1363 static_assert(
1364 distinctTypes<Names...>(),
1365 "two parts of a composition write blocks under one name, their paths' last tag; place each model on its "
1366 "block with on<Path>(model)"
1367 );
1368 using type = WritesAs<TagPaths<Paths...>, Composite<Names...>>;
1369};
1370
1372
1374template <typename Items, typename Name>
1375inline constexpr bool itemsName = false;
1376
1378// A partial specialization rather than a lambda invoked in the initializer, which MSVC 19.50
1379// fails on with an internal compiler error.
1380template <typename... Items, typename Name>
1381inline constexpr bool itemsName<std::tuple<Items...>, Name> = (std::is_same_v<typename Items::NameType, Name> || ...);
1382
1384
1386template <typename Model, typename Name>
1387inline constexpr bool modelReads =
1388 itemsName<typename ItemsOf<typename Model::template Policy<PolicyKind::reads>>::type, Name>;
1389
1391template <typename Model, typename Name>
1392inline constexpr bool modelAffineIn =
1393 !modelReads<Model, Name> || affineNames<typename Model::template Policy<PolicyKind::affineIn>, Name>;
1394
1398template <typename Acc, typename Models, typename... Names>
1399struct AffineUnion
1400{
1401 using type = Acc;
1402};
1403
1405template <typename... Kept, typename... Models, typename First, typename... Rest>
1406struct AffineUnion<AffineIn<Kept...>, std::tuple<Models...>, First, Rest...>
1407{
1408 static constexpr bool affine = (modelAffineIn<Models, First> && ...);
1409
1410 using type = typename AffineUnion<
1411 std::conditional_t<affine, AffineIn<Kept..., First>, AffineIn<Kept...>>,
1412 std::tuple<Models...>,
1413 Rest...>::type;
1414};
1415
1417
1419template <typename Union, typename Models>
1420struct AffineOf;
1421
1423template <typename... Items, typename Models>
1424struct AffineOf<Reads<Items...>, Models>
1425{
1426 using type = typename AffineUnion<AffineIn<>, Models, typename Items::NameType...>::type;
1427};
1428
1430
1435template <typename Model, typename Scalar, typename... Extra>
1436inline constexpr bool returnsJacobianWith = isWithJacobian<std::remove_cvref_t<decltype(callForm(
1437 std::declval<typename Model::FunctionType const &>(),
1438 std::declval<typename Model::template Policy<PolicyKind::reads>::InputSpace const &>(),
1439 std::declval<OutputSpaceOf<Model> const &>(),
1440 std::declval<
1441 typename Moments<typename Model::template Policy<PolicyKind::reads>::InputSpace, Scalar>::VectorType const &>(),
1442 std::declval<Extra>()...
1443))>>;
1444
1448template <typename Model, typename Scalar>
1449inline constexpr bool returnsStepJacobian = returnsJacobianWith<Model, Scalar, Scalar>;
1450
1454template <typename Model, typename Space, typename Scalar>
1455inline constexpr bool returnsStepJacobianIn = [] {
1456 if constexpr (readsWholeState<Model>)
1457 {
1458 return isWithJacobian<std::remove_cvref_t<decltype(callForm(
1459 std::declval<typename Model::FunctionType const &>(),
1460 std::declval<Space const &>(),
1461 std::declval<Space const &>(),
1462 std::declval<typename Moments<Space, Scalar>::VectorType const &>(),
1463 std::declval<Scalar>()
1464 ))>>;
1465 }
1466 else
1467 {
1468 return returnsStepJacobian<Model, Scalar>;
1469 }
1470}();
1471
1473template <typename Model>
1474using ReadItemsOf = typename ItemsOf<typename Model::template Policy<PolicyKind::reads>>::type;
1475
1479template <typename Part, typename In>
1480[[nodiscard]] auto
1481partIn(In const & in)
1482{
1483 using PartIn = typename Part::template Policy<PolicyKind::reads>::InputSpace;
1484 return [&]<typename... Items>(std::tuple<Items...> *) {
1485 return PartIn{pathBlock<typename Items::NameType>(in)...};
1486 }(static_cast<ReadItemsOf<Part> *>(nullptr));
1487}
1488
1492template <typename Part, typename In, typename PartIn, typename X>
1493[[nodiscard]] auto
1494partVector(In const & in, PartIn const & pin, X const & x)
1495{
1496 auto px = zeros<typename X::Scalar>(pin);
1497 [&]<typename... Items>(std::tuple<Items...> *) {
1498 ((block<typename Items::NameType>(pin, px) = block<typename Items::NameType>(in, x)), ...);
1499 }(static_cast<ReadItemsOf<Part> *>(nullptr));
1500 return px;
1501}
1502
1506template <typename Part, typename Out>
1507[[nodiscard]] OutputSpaceOf<Part>
1508partOut(Out const & out)
1509{
1510 return [&]<typename... Names>(std::tuple<Names...> *) {
1511 return OutputSpaceOf<Part>{pathBlock<Names>(out)...};
1512 }(static_cast<CompositeBlocks<OutputSpaceOf<Part>> *>(nullptr));
1513}
1514
1519template <
1520 typename Row,
1521 typename Out,
1522 typename In,
1523 typename J,
1524 typename POut,
1525 typename PIn,
1526 typename PJ,
1527 typename... Items>
1528void
1529partJacobianRow(
1530 Out const & out,
1531 In const & in,
1532 J & jacobian,
1533 POut const & pout,
1534 PIn const & pin,
1535 PJ const & partJacobian,
1536 std::tuple<Items...> *
1537)
1538{
1539 ((diffBlock<Row, typename Items::NameType>(out, in, jacobian) =
1540 diffBlock<Row, typename Items::NameType>(pout, pin, partJacobian)),
1541 ...);
1542}
1543
1548template <
1549 std::size_t I,
1550 typename Parts,
1551 typename In,
1552 typename Out,
1553 typename X,
1554 typename Y,
1555 typename J,
1556 typename... Extra>
1557void
1558stepPart(
1559 Parts const & parts,
1560 In const & in,
1561 Out const & out,
1562 X const & x,
1563 Y & y,
1564 [[maybe_unused]] J & jacobian,
1565 Extra const &... extra
1566)
1567{
1568 using Part = std::tuple_element_t<I, Parts>;
1569 auto const pin = partIn<Part>(in);
1570 auto const pout = partOut<Part>(out);
1571 auto const px = partVector<Part>(in, pin, x);
1572 auto result = vectorReturned(callForm(std::get<I>(parts).function(), pin, pout, px, extra...));
1573 auto const & value = valueOf(result);
1574 using Names = CompositeBlocks<OutputSpaceOf<Part>>;
1575 [&]<typename... Rows>(std::tuple<Rows...> *) {
1576 ((block<Rows>(out, y) = block<Rows>(pout, value)), ...);
1577 if constexpr (!std::is_same_v<J, NoPolicy>)
1578 {
1579 (partJacobianRow<
1580 Rows>(out, in, jacobian, pout, pin, result.jacobian, static_cast<ReadItemsOf<Part> *>(nullptr)),
1581 ...);
1582 }
1583 }(static_cast<Names *>(nullptr));
1584}
1585
1591template <typename JacobianScalar, typename... Models, typename In, typename Out, typename X, typename... Extra>
1592[[nodiscard]] auto
1593stepParts(std::tuple<Models...> const & parts, In const & in, Out const & out, X const & x, Extra const &... extra)
1594{
1595 constexpr bool AllJacobians = (returnsJacobianWith<Models, JacobianScalar, Extra...> && ...);
1596 auto y = zeros<typename X::Scalar>(out);
1597 auto jacobian = [&] {
1598 if constexpr (AllJacobians)
1599 {
1600 using JacobianMatrix = MatrixOf<
1601 JacobianScalar,
1602 valueDimensionAtCompileTime<DifferenceSpace<Out>>,
1603 valueDimensionAtCompileTime<DifferenceSpace<In>>,
1604 maxValueDimensionAtCompileTime<DifferenceSpace<Out>>,
1605 maxValueDimensionAtCompileTime<DifferenceSpace<In>>>;
1606 return JacobianMatrix{JacobianMatrix::Zero(differenceDimension(out), differenceDimension(in))};
1607 }
1608 else
1609 {
1610 return NoPolicy{};
1611 }
1612 }();
1613 [&]<std::size_t... I>(std::index_sequence<I...>) {
1614 (stepPart<I>(parts, in, out, x, y, jacobian, extra...), ...);
1615 }(std::index_sequence_for<Models...>{});
1616 if constexpr (AllJacobians)
1617 {
1618 return withJacobian(std::move(y), std::move(jacobian));
1619 }
1620 else
1621 {
1622 return y;
1623 }
1624}
1625
1629template <typename... Models>
1630inline constexpr bool anyTakesBatches =
1631 (takesBatches<std::remove_cvref_t<decltype(std::declval<Models const &>().function())>> || ...);
1632
1639template <typename... Models, typename In, typename Out, typename X, typename Y, typename... Extra>
1640void
1641stepPartsBatch(
1642 std::tuple<Models...> const & parts,
1643 In const & in,
1644 Out const & out,
1645 X const & x,
1646 Y & y,
1647 Extra const &... extra
1648)
1649{
1650 using T = typename X::Scalar;
1651 // Used in the parts' types only, which MSVC 19.51 takes for unused (C4189).
1652 [[maybe_unused]] constexpr int Cols = X::ColsAtCompileTime;
1653 [[maybe_unused]] constexpr int MaxCols = X::MaxColsAtCompileTime;
1654 Eigen::Index const count = x.cols();
1655 auto & values = y.values();
1656 values.setZero();
1657 [&]<std::size_t... I>(std::index_sequence<I...>) {
1658 (
1659 [&] {
1660 using Part = std::tuple_element_t<I, std::tuple<Models...>>;
1661 auto const pin = partIn<Part>(in);
1662 auto const pout = partOut<Part>(out);
1663 using PIn = std::remove_cvref_t<decltype(pin)>;
1664 using POut = OutputSpaceOf<Part>;
1665 using PX =
1666 MatrixOf<T, valueDimensionAtCompileTime<PIn>, Cols, maxValueDimensionAtCompileTime<PIn>, MaxCols>;
1667 using PY =
1668 MatrixOf<T, valueDimensionAtCompileTime<POut>, Cols, maxValueDimensionAtCompileTime<POut>, MaxCols>;
1669 auto px = PX{};
1670 px.resize(valueDimension(pin), count);
1671 for (Eigen::Index j = 0; j < count; ++j)
1672 {
1673 px.col(j) = partVector<Part>(in, pin, x.col(j));
1674 }
1675 auto py = PY{};
1676 py.resize(valueDimension(pout), count);
1677 auto const & function = std::get<I>(parts).function();
1678 if constexpr (takesBatches<std::remove_cvref_t<decltype(function)>>)
1679 {
1680 callBatched(function, pin, pout, px, py, extra...);
1681 }
1682 else
1683 {
1684 for (Eigen::Index j = 0; j < count; ++j)
1685 {
1686 typename Moments<PIn, T>::VectorType const column = px.col(j);
1687 py.col(j) = valueOf(vectorReturned(callForm(function, pin, pout, column, extra...)));
1688 }
1689 }
1690 [&]<typename... Rows>(std::tuple<Rows...> *) {
1691 ((block<Rows>(out, values) = block<Rows>(pout, py)), ...);
1692 }(static_cast<CompositeBlocks<POut> *>(nullptr));
1693 }(),
1694 ...
1695 );
1696 }(std::index_sequence_for<Models...>{});
1697}
1698
1699} // namespace internal
1700
1701template <typename... Models>
1702class Composition;
1703
1704namespace internal
1705{
1706
1708template <typename Model>
1709inline constexpr bool isComposition = false;
1710
1712
1713template <typename... Models>
1714inline constexpr bool isComposition<Composition<Models...>> = true;
1715
1717
1721template <typename Model>
1722struct DeclaredScalars
1723{
1724 using type = std::tuple<>;
1725};
1726
1728
1729template <typename Model>
1730 requires(!isComposition<Model> && requires { typename Model::Scalar; })
1731struct DeclaredScalars<Model>
1732{
1733 using type = std::tuple<typename Model::Scalar>;
1734};
1735
1737template <typename... Models>
1738struct DeclaredScalars<Composition<Models...>>
1739{
1740 using type = decltype(std::tuple_cat(std::declval<typename DeclaredScalars<Models>::type>()...));
1741};
1742
1744
1746template <typename... Models>
1747using DeclaredScalarsOf = decltype(std::tuple_cat(std::declval<typename DeclaredScalars<Models>::type>()...));
1748
1750template <typename... Models>
1751[[nodiscard]] consteval bool
1752oneDeclaredScalar()
1753{
1754 return []<typename... Scalars>(std::tuple<Scalars...> *) {
1755 if constexpr (sizeof...(Scalars) == 0)
1756 {
1757 return true;
1758 }
1759 else
1760 {
1761 using First = std::tuple_element_t<0, std::tuple<Scalars...>>;
1762 return (std::is_same_v<First, Scalars> && ...);
1763 }
1764 }(static_cast<DeclaredScalarsOf<Models...> *>(nullptr));
1765}
1766
1768template <typename Scalars>
1769struct FirstScalarOr
1770{
1771 using type = double;
1772};
1773
1775
1776template <typename First, typename... Rest>
1777struct FirstScalarOr<std::tuple<First, Rest...>>
1778{
1779 using type = First;
1780};
1781
1783
1784} // namespace internal
1785
1802template <typename... Models>
1804{
1805 static_assert(sizeof...(Models) > 0, "a composition needs a process model");
1806 static_assert(
1807 internal::oneDeclaredScalar<Models...>(),
1808 "the parts of a composition must have one scalar: give every built-in model the same Scalar"
1809 );
1810
1812 using AllItems = decltype(std::tuple_cat(
1813 std::declval<
1814 typename internal::ItemsOf<typename Models::template Policy<internal::PolicyKind::reads>>::type>()...
1815 ));
1816
1818 using AllPaths =
1819 decltype(std::tuple_cat(std::declval<typename internal::PathsOf<typename Models::WritePaths>::type>()...));
1820
1822 using AllNames =
1823 decltype(std::tuple_cat(std::declval<internal::CompositeBlocks<typename Models::OutputSpace>>()...));
1824
1825public:
1826
1828 using Scalar = typename internal::FirstScalarOr<internal::DeclaredScalarsOf<Models...>>::type;
1829
1831
1832 static constexpr bool IsModel = true;
1833 static constexpr bool HasFunction = true;
1834
1836 using ReadsPolicy = typename internal::ReadUnionOf<AllItems>::type;
1837
1839 using WritesPolicy = typename internal::WritesOfTuple<AllPaths, AllNames>::type;
1840
1842 using AffinePolicy = typename internal::AffineOf<ReadsPolicy, std::tuple<Models...>>::type;
1843
1845 template <internal::PolicyKind K>
1846 static constexpr bool has =
1847 K == internal::PolicyKind::reads || K == internal::PolicyKind::writes || K == internal::PolicyKind::affineIn;
1848
1850 template <internal::PolicyKind K>
1851 using Policy = std::conditional_t<
1852 K == internal::PolicyKind::reads,
1854 std::conditional_t<
1855 K == internal::PolicyKind::writes,
1857 std::conditional_t<K == internal::PolicyKind::affineIn, AffinePolicy, internal::NoPolicy>>>;
1858
1860 using WritePaths = typename WritesPolicy::PathList;
1861
1863 using OutputSpace = typename WritesPolicy::OutputSpace;
1864
1866 explicit Composition(Models... models)
1867 : m_parts{std::move(models)...}
1868 {}
1869
1871 [[nodiscard]] Composition const &
1872 function() const
1873 {
1874 return *this;
1875 }
1876
1878 template <internal::PolicyKind K>
1879 [[nodiscard]] Policy<K> const &
1880 policy() const
1881 {
1882 static_assert(has<K>, "the composition was not given this policy");
1883 static constexpr Policy<K> value{};
1884 return value;
1885 }
1886
1888 [[nodiscard]] std::tuple<Models...> const &
1889 parts() const
1890 {
1891 return m_parts;
1892 }
1893
1898 template <typename In, typename Out, typename X, typename TimeScalar>
1899 [[nodiscard]] auto
1900 operator()(In const & in, Out const & out, X const & x, TimeScalar dt) const
1901 requires MatrixExpression<X>
1902 {
1903 return internal::stepParts<TimeScalar>(m_parts, in, out, x, dt);
1904 }
1905
1907 static constexpr bool TakesBatches = internal::anyTakesBatches<Models...>;
1908
1910 using MarkType = internal::NoJacobians;
1911
1916 template <typename In, typename Out, typename X, typename TimeScalar, typename Y>
1917 void
1918 operator()(In const & in, Out const & out, X const & x, TimeScalar dt, Y & y) const
1919 requires TakesBatches
1920 {
1921 internal::stepPartsBatch(m_parts, in, out, x, y, dt);
1922 }
1923
1927 template <typename Scalar, typename Out>
1928 [[nodiscard]] typename Moments<Out, Scalar>::MatrixType
1929 noiseOver(Out const & out, Scalar dt) const
1930 {
1931 using Matrix = typename Moments<Out, Scalar>::MatrixType;
1932 Eigen::Index const n = differenceDimension(out);
1933 Matrix q = Matrix::Zero(n, n);
1934 [&]<std::size_t... I>(std::index_sequence<I...>) {
1935 (noisePart<I, Scalar>(out, dt, q), ...);
1936 }(std::index_sequence_for<Models...>{});
1937 return q;
1938 }
1939
1943 template <typename Space>
1944 [[nodiscard]] static consteval internal::StaticPartition
1946 {
1947 return internal::staticPartitionOfWrites<Space, WritePaths>();
1948 }
1949
1951 template <typename Space>
1952 [[nodiscard]] bool
1953 partitions(Space const & space) const
1954 {
1955 return internal::writesPartition(space, static_cast<WritePaths *>(nullptr));
1956 }
1957
1958private:
1959
1961 template <std::size_t I>
1962 using Part = std::tuple_element_t<I, std::tuple<Models...>>;
1963
1965 template <std::size_t I, typename Scalar, typename Out, typename Q>
1966 void
1967 noisePart(Out const & out, Scalar dt, Q & q) const
1968 {
1969 auto const pout = internal::partOut<Part<I>>(out);
1970 auto const partNoise = std::get<I>(m_parts).template noiseOver<Scalar>(pout, dt);
1971 using Names = internal::CompositeBlocks<typename Part<I>::OutputSpace>;
1972 [&]<typename... Rows>(std::tuple<Rows...> *) {
1973 (noiseRow<Rows>(out, q, pout, partNoise, static_cast<Names *>(nullptr)), ...);
1974 }(static_cast<Names *>(nullptr));
1975 }
1976
1980 template <typename Row, typename Out, typename Q, typename POut, typename PQ, typename... Columns>
1981 static void
1982 noiseRow(Out const & out, Q & q, POut const & pout, PQ const & partNoise, std::tuple<Columns...> *)
1983 {
1984 ((diffBlock<Row, Columns>(out, out, q) = diffBlock<Row, Columns>(pout, pout, partNoise)), ...);
1985 }
1986
1987 std::tuple<Models...> m_parts;
1988};
1989
1990namespace internal
1991{
1992
1994template <typename... Models>
1995std::true_type
1996compositionBase(Composition<Models...> const *);
1997
1999std::false_type
2000compositionBase(void const *);
2001
2002} // namespace internal
2003
2009template <typename M>
2010concept ProcessModel = decltype(internal::processFunctionBase(std::declval<std::remove_cvref_t<M> const *>()))::value
2011 || decltype(internal::compositionBase(std::declval<std::remove_cvref_t<M> const *>()))::value;
2012
2019{
2021 template <typename Space>
2022 [[nodiscard]] static consteval internal::StaticPartition
2024 {
2025 if constexpr (Space::ValueDimensionStep == 0)
2026 {
2027 return Space::FixedValueDimension == 0 ? internal::StaticPartition::holds
2028 : internal::StaticPartition::fails;
2029 }
2030 else
2031 {
2032 return internal::StaticPartition::unknown;
2033 }
2034 }
2035
2037 template <typename Space>
2038 [[nodiscard]] bool
2039 partitions(Space const & space) const
2040 {
2041 return valueDimension(space) == 0;
2042 }
2043};
2044
2053template <ProcessModel... Models>
2054[[nodiscard]] Composition<std::remove_cvref_t<Models>...>
2055compose(Models &&... models)
2056{
2057 static_assert((internal::wholeStateRefused<std::remove_cvref_t<Models>> && ...));
2058 return Composition<std::remove_cvref_t<Models>...>{std::forward<Models>(models)...};
2059}
2060
2061namespace internal
2062{
2063
2067template <typename Model, typename Space>
2068[[nodiscard]] consteval bool
2069readsOnlyState()
2070{
2071 if constexpr (readsWholeState<Model>)
2072 {
2073 return true;
2074 }
2075 else
2076 {
2077 return []<typename... Items>(std::tuple<Items...> *) {
2078 return (
2079 []<typename First, typename... Rest>(TagPath<First, Rest...> *) {
2080 return countTag<First, Space>() > 0;
2081 }(static_cast<typename Items::PathType *>(nullptr))
2082 && ... && true
2083 );
2084 }(static_cast<typename ItemsOf<typename Model::template Policy<PolicyKind::reads>>::type *>(nullptr));
2085 }
2086}
2087
2089template <typename Scalar, ProcessModel Model, Space S>
2090[[nodiscard]] typename Moments<S, Scalar>::MatrixType
2091processNoiseOfBlocks(Model const & model, S const & space, Scalar dt)
2092{
2093 using Matrix = typename Moments<S, Scalar>::MatrixType;
2094 auto const out = outputOf<Model>(space);
2095 auto const partial = model.template noiseOver<Scalar>(out, dt);
2096 Eigen::Index const n = differenceDimension(space);
2097 Matrix q = Matrix::Zero(n, n);
2098 [&]<std::size_t... K>(std::index_sequence<K...>) {
2099 std::array<BlockRows, sizeof...(K)> const rows{differenceBlockRows<WritePathAt<Model, K>>(space)...};
2100 std::array<BlockRows, sizeof...(K)> const outRows{differenceBlockRows<WriteNameAt<Model, K>>(out)...};
2101 for (std::size_t k = 0; k < sizeof...(K); ++k)
2102 {
2103 for (std::size_t l = 0; l < sizeof...(K); ++l)
2104 {
2105 q.block(rows[k].start, rows[l].start, rows[k].size, rows[l].size) =
2106 partial.block(outRows[k].start, outRows[l].start, outRows[k].size, outRows[l].size);
2107 }
2108 }
2109 }(std::make_index_sequence<writeCount<Model>>{});
2110 return q;
2111}
2112
2117template <typename Scalar, ProcessModel Model, Space S>
2118[[nodiscard]] typename Moments<S, Scalar>::MatrixType
2119processNoise(Model const & model, S const & space, Scalar dt)
2120{
2121 using Matrix = typename Moments<S, Scalar>::MatrixType;
2122 if constexpr (writesWholeState<Model>)
2123 {
2124 return Matrix{model.template noiseOver<Scalar>(space, dt)};
2125 }
2126 else
2127 {
2128 return processNoiseOfBlocks<Scalar>(model, space, dt);
2129 }
2130}
2131
2140template <typename Call, typename Space>
2141class ResolvedProcess : public ResolvedReads<typename Call::ModelType, typename Call::ArgumentsType, Space>
2142{
2144 using Base = ResolvedReads<typename Call::ModelType, typename Call::ArgumentsType, Space>;
2145
2146public:
2147
2148 using CallType = Call;
2149 using Model = typename Call::ModelType;
2150 using Function = typename Model::FunctionType;
2151
2152 using Base::InputCount;
2153 using Base::ItemCount;
2154 using Base::StateCount;
2155 using typename Base::In;
2156 using typename Base::Reads;
2157
2159 template <std::size_t J>
2160 using Argument = typename Base::template Argument<J>;
2161
2163 template <typename T>
2164 using InVector = typename Base::template InVector<T>;
2165
2167 using WritePaths = typename Model::WritePaths;
2168
2169 static constexpr std::size_t WriteCount = std::tuple_size_v<typename PathsOf<WritePaths>::type>;
2170
2172 static constexpr bool Whole = writesWholeState<Model>;
2173
2177 using Out = OutputSpaceIn<Model, Space>;
2178
2180 static constexpr bool Affine = affineInEverything<Model>();
2181
2183 static constexpr bool ReturnsJacobian = returnsStepJacobianIn<Model, Space, double>;
2184
2185private:
2186
2187 using Base::argumentOf;
2188 using Base::inputItems;
2189 using Base::m_differenceRows;
2190 using Base::m_in;
2191 using Base::m_reads;
2192 using Base::m_stateDifferenceSize;
2193 using Base::stateIndexOf;
2194 using Base::stateItems;
2195
2196public:
2197
2199 ResolvedProcess(Call const & call, Space const & space)
2200 : Base{call.arguments(), space}
2201 , m_call{call}
2202 , m_out{outputOf<Model>(space)}
2203 , m_stateSize{valueDimension(space)}
2204 {
2205 if constexpr (Whole)
2206 {
2207 m_writeRows[0] = m_outRows[0] = BlockRows{0, m_stateSize};
2208 m_writeDifferenceRows[0] = m_outDifferenceRows[0] = BlockRows{0, differenceDimension(space)};
2209 }
2210 else
2211 {
2212 [&]<std::size_t... K>(std::index_sequence<K...>) {
2213 ((m_writeRows[K] = blockRows<WritePathAt<Model, K>>(space),
2214 m_writeDifferenceRows[K] = differenceBlockRows<WritePathAt<Model, K>>(space),
2215 m_outRows[K] = blockRows<WriteNameAt<Model, K>>(m_out),
2216 m_outDifferenceRows[K] = differenceBlockRows<WriteNameAt<Model, K>>(m_out)),
2217 ...);
2218 }(std::make_index_sequence<WriteCount>{});
2219 }
2220 }
2221
2223 [[nodiscard]] Model const &
2224 model() const
2225 {
2226 return m_call.model();
2227 }
2228
2230 [[nodiscard]] Out const &
2231 out() const
2232 {
2233 return m_out;
2234 }
2235
2237 template <typename Scalar>
2238 [[nodiscard]] typename Moments<Space, Scalar>::MatrixType
2239 noise(Scalar dt) const
2240 {
2241 using Matrix = typename Moments<Space, Scalar>::MatrixType;
2242 auto const partial = model().template noiseOver<Scalar>(m_out, dt);
2243 Matrix q = Matrix::Zero(m_stateDifferenceSize, m_stateDifferenceSize);
2244 for (std::size_t k = 0; k < WriteCount; ++k)
2245 {
2246 for (std::size_t l = 0; l < WriteCount; ++l)
2247 {
2248 q.block(
2249 m_writeDifferenceRows[k].start,
2250 m_writeDifferenceRows[l].start,
2251 m_writeDifferenceRows[k].size,
2252 m_writeDifferenceRows[l].size
2253 ) =
2254 partial.block(
2255 m_outDifferenceRows[k].start,
2256 m_outDifferenceRows[l].start,
2257 m_outDifferenceRows[k].size,
2258 m_outDifferenceRows[l].size
2259 );
2260 }
2261 }
2262 return q;
2263 }
2264
2272 template <typename State, typename TimeScalar, typename... Inputs>
2273 [[nodiscard]] auto
2274 propagate(State const & state, TimeScalar dt, Inputs const &... inputs) const
2275 {
2276 static_assert(
2277 sizeof...(Inputs) == InputCount || sizeof...(Inputs) == 0,
2278 "the step takes one vector per uncertain input, or none to read each at its mean"
2279 );
2280 using T = ScalarOf<State>;
2281 using Vector = typename Moments<Space, T>::VectorType;
2282 EMRYS_FILTER_ASSERT(state.size() == m_stateSize && "the state does not fit the space");
2283 auto const x =
2284 this->template gatherFromRows<T>(state, Eigen::Index{0}, std::tuple<Inputs const &...>{inputs...});
2285 auto result = vectorReturned(callForm(model().function(), m_in, m_out, x, dt));
2286 using Result = std::remove_cvref_t<decltype(result)>;
2287 Vector y{state};
2288 scatter(y, valueOf(result));
2289 if constexpr (isWithJacobian<Result>)
2290 {
2291 return withJacobian(std::move(y), expand(result.jacobian));
2292 }
2293 else
2294 {
2295 return y;
2296 }
2297 }
2298
2300 static constexpr bool BatchedFunction = takesBatches<Function>;
2301
2303 template <typename S, typename T>
2304 using Columns =
2305 MatrixOf<T, valueDimensionAtCompileTime<S>, Eigen::Dynamic, maxValueDimensionAtCompileTime<S>, Eigen::Dynamic>;
2306
2310 template <typename T, typename Column, typename State, typename... Inputs>
2311 void
2312 gatherColumn(Column && column, State const & state, Inputs const &... inputs) const
2313 {
2314 static_assert(sizeof...(Inputs) == InputCount, "the step takes one vector per uncertain input");
2315 column = this->template gatherFromRows<T>(state, Eigen::Index{0}, std::tuple<Inputs const &...>{inputs...});
2316 }
2317
2322 template <typename X, typename TimeScalar, typename Y>
2323 void
2324 stepBatch(X const & x, TimeScalar dt, Y && y) const
2325 requires BatchedFunction
2326 {
2327 callBatched(model().function(), m_in, m_out, x, y, dt);
2328 }
2329
2335 template <typename Y, typename States, typename TimeScalar, typename... Inputs>
2336 void
2337 propagateColumns(Y & y, States const & states, TimeScalar dt, Inputs const &... inputs) const
2338 requires BatchedFunction
2339 {
2340 auto const x = gatherStates<Y>(states, inputs...);
2341 auto values = ValuesOf<Y, typename States::Scalar>{};
2342 values.resize(valueDimension(m_out), states.cols());
2343 stepBatch(x, dt, values);
2344 scatterColumns(y, states, values);
2345 }
2346
2353 template <typename Y, typename J, typename States, typename TimeScalar, typename... Inputs>
2354 void
2355 propagateColumnsWithAffineJacobians(
2356 Y & y,
2357 J & jacobians,
2358 States const & states,
2359 TimeScalar dt,
2360 Inputs const &... inputs
2361 ) const
2362 requires BatchedFunction
2363 {
2364 using T = typename States::Scalar;
2365 auto const x = gatherStates<Y>(states, inputs...);
2366 Eigen::Index const count = states.cols();
2367 auto values = ValuesOf<Y, T>{};
2368 values.resize(valueDimension(m_out), count);
2369 auto wanted = std::array<bool, std::tuple_size_v<InputBlocks<In>>>{};
2370 [&]<std::size_t... I>(std::index_sequence<I...>) {
2371 ((wanted[I] = Base::template itemFactored<I>()), ...);
2372 }(std::make_index_sequence<ItemCount>{});
2373 Eigen::Index const l = jacobians.cols() / std::max<Eigen::Index>(count, 1);
2374 using OutJacobians = MatrixOf<
2375 T,
2376 valueDimensionAtCompileTime<DifferenceSpace<Out>>,
2377 J::ColsAtCompileTime,
2378 maxValueDimensionAtCompileTime<DifferenceSpace<Out>>,
2379 J::MaxColsAtCompileTime>;
2380 auto outJacobians = OutJacobians{};
2381 outJacobians.resize(differenceDimension(m_out), l * count);
2382 callBatchedWithJacobians(model().function(), m_in, m_out, x, wanted, values, outJacobians, dt);
2383 scatterColumns(y, states, values);
2384 if constexpr (Whole)
2385 {
2386 jacobians = outJacobians;
2387 }
2388 else
2389 {
2390 jacobians.setZero();
2391 Eigen::Index offset = 0;
2392 [&]<std::size_t... I>(std::index_sequence<I...>) {
2393 (
2394 [&] {
2395 if constexpr (Base::template itemFactored<I>())
2396 {
2397 if constexpr (Base::inState(I))
2398 {
2399 BlockRows const rows = this->m_reads.differenceRows(Base::stateIndexOf(I));
2400 for (Eigen::Index j = 0; j < count; ++j)
2401 {
2402 for (Eigen::Index r = 0; r < rows.size; ++r)
2403 {
2404 jacobians(rows.start + r, j * l + offset + r) = T(1);
2405 }
2406 }
2407 }
2408 offset += this->m_differenceRows[I].size;
2409 }
2410 }(),
2411 ...
2412 );
2413 }(std::make_index_sequence<ItemCount>{});
2414 for (std::size_t k = 0; k < WriteCount; ++k)
2415 {
2416 BlockRows const & written = m_writeDifferenceRows[k];
2417 BlockRows const & rows = m_outDifferenceRows[k];
2418 jacobians.middleRows(written.start, written.size) = outJacobians.middleRows(rows.start, rows.size);
2419 }
2420 }
2421 }
2422
2427 template <typename Y, typename States, typename... Inputs>
2428 [[nodiscard]] auto
2429 gatherStates(States const & states, Inputs const &... inputs) const
2430 {
2431 static_assert(sizeof...(Inputs) == InputCount);
2432 using T = typename States::Scalar;
2433 using X = MatrixOf<
2434 T,
2435 valueDimensionAtCompileTime<In>,
2436 Y::ColsAtCompileTime,
2437 maxValueDimensionAtCompileTime<In>,
2438 Y::MaxColsAtCompileTime>;
2439 EMRYS_FILTER_ASSERT(states.rows() == m_stateSize && "the states do not fit the space");
2440 auto x = X{};
2441 x.resize(valueDimension(m_in), states.cols());
2442 for (Eigen::Index j = 0; j < states.cols(); ++j)
2443 {
2444 gatherColumn<T>(x.col(j), states.col(j), inputs.col(j)...);
2445 }
2446 return x;
2447 }
2448
2452 template <typename Y, typename T>
2453 using ValuesOf = MatrixOf<
2454 T,
2455 valueDimensionAtCompileTime<Out>,
2456 Y::ColsAtCompileTime,
2457 maxValueDimensionAtCompileTime<Out>,
2458 Y::MaxColsAtCompileTime>;
2459
2463 template <typename Y, typename States, typename Values>
2464 void
2465 scatterColumns(Y & y, States const & states, Values const & values) const
2466 {
2467 y = states;
2468 for (Eigen::Index j = 0; j < states.cols(); ++j)
2469 {
2470 auto column = y.col(j);
2471 scatter(column, values.col(j));
2472 }
2473 }
2474
2478 template <typename State, typename Value>
2479 [[nodiscard]] auto
2480 scattered(State const & state, Value const & value) const
2481 {
2482 using Vector = typename Moments<Space, ScalarOf<State>>::VectorType;
2483 Vector y{state};
2484 scatter(y, value);
2485 return y;
2486 }
2487
2492 template <typename Scalar>
2493 [[nodiscard]] typename Moments<Space, Scalar>::MatrixType
2494 transition(Scalar dt) const
2495 requires(Affine && ReturnsJacobian && InputCount == 0)
2496 {
2497 using Vector = typename Moments<Space, Scalar>::VectorType;
2498 auto const zero = Vector{Vector::Zero(m_stateSize)};
2499 return propagate(zero, dt).jacobian;
2500 }
2501
2502private:
2503
2507 template <typename Y, typename Value>
2508 void
2509 scatter(Y & y, Value const & value) const
2510 {
2511 EMRYS_FILTER_ASSERT(
2512 value.size() == valueDimension(m_out) && "a process model's function returns a vector in its output space"
2513 );
2514 if constexpr (Whole)
2515 {
2516 y = value;
2517 }
2518 else
2519 {
2520 scatterBlocks(y, value);
2521 }
2522 }
2523
2525 template <typename Y, typename Value>
2526 void
2527 scatterBlocks(Y & y, Value const & value) const
2528 {
2529 [&]<std::size_t... K>(std::index_sequence<K...>) {
2530 ((middleRowsOf<
2531 valueDimensionAtCompileTime<PathTag<std::tuple_element_t<K, typename PathsOf<WritePaths>::type>>>>(
2532 y,
2533 m_writeRows[K].start,
2534 m_writeRows[K].size
2535 ) =
2536 middleRowsOf<valueDimensionAtCompileTime<
2537 PathTag<std::tuple_element_t<K, typename PathsOf<WritePaths>::type>>>>(
2538 value,
2539 m_outRows[K].start,
2540 m_outRows[K].size
2541 )),
2542 ...);
2543 }(std::make_index_sequence<WriteCount>{});
2544 }
2545
2551 template <typename J>
2552 [[nodiscard]] auto
2553 expand(J const & jacobian) const
2554 {
2555 static_assert(
2556 MatrixExpression<J>,
2557 "a process model's function returns its Jacobian as one matrix over its input's differences, "
2558 "withJacobian(y, J)"
2559 );
2560 EMRYS_FILTER_ASSERT(
2561 jacobian.rows() == differenceDimension(m_out) && jacobian.cols() == differenceDimension(m_in)
2562 && "a returned Jacobian has a row per difference coordinate of the output and a column per one of the input"
2563 );
2564 using Scalar = typename J::Scalar;
2565 using StateMatrix = typename Moments<Space, Scalar>::MatrixType;
2566 StateMatrix f = StateMatrix::Identity(m_stateDifferenceSize, m_stateDifferenceSize);
2567 for (std::size_t k = 0; k < WriteCount; ++k)
2568 {
2569 f.middleRows(m_writeDifferenceRows[k].start, m_writeDifferenceRows[k].size).setZero();
2570 [&]<std::size_t... I>(std::index_sequence<I...>) {
2571 ((f.block(
2572 m_writeDifferenceRows[k].start,
2573 m_reads.differenceRows(I).start,
2574 m_writeDifferenceRows[k].size,
2575 m_reads.differenceRows(I).size
2576 ) =
2577 jacobian.block(
2578 m_outDifferenceRows[k].start,
2579 m_differenceRows[stateItems(I)].start,
2580 m_outDifferenceRows[k].size,
2581 m_differenceRows[stateItems(I)].size
2582 )),
2583 ...);
2584 }(std::make_index_sequence<StateCount>{});
2585 }
2586 if constexpr (InputCount == 0)
2587 {
2588 return f;
2589 }
2590 else
2591 {
2592 return [&]<std::size_t... P>(std::index_sequence<P...>) {
2593 return std::tuple{std::move(f), inputBlock<P>(jacobian)...};
2594 }(std::make_index_sequence<InputCount>{});
2595 }
2596 }
2597
2599 template <std::size_t P, typename J>
2600 [[nodiscard]] auto
2601 inputBlock(J const & jacobian) const
2602 {
2603 using Input =
2604 std::remove_cvref_t<decltype(inputOf(std::declval<Argument<argumentOf(inputItems(P))> const &>()))>;
2605 using Block = MatrixOf<
2606 typename J::Scalar,
2607 valueDimensionAtCompileTime<DifferenceSpace<Space>>,
2608 Input::DifferenceDimensionAtCompileTime,
2609 maxValueDimensionAtCompileTime<DifferenceSpace<Space>>,
2610 Input::MaxDifferenceDimensionAtCompileTime>;
2611 BlockRows const & columns = m_differenceRows[inputItems(P)];
2612 Block block = Block::Zero(m_stateDifferenceSize, columns.size);
2613 for (std::size_t k = 0; k < WriteCount; ++k)
2614 {
2615 block.middleRows(m_writeDifferenceRows[k].start, m_writeDifferenceRows[k].size) =
2616 jacobian.block(m_outDifferenceRows[k].start, columns.start, m_outDifferenceRows[k].size, columns.size);
2617 }
2618 return block;
2619 }
2620
2621 Call const & m_call;
2622 Out m_out;
2623 Eigen::Index m_stateSize;
2624 std::array<BlockRows, WriteCount> m_writeRows{};
2625 std::array<BlockRows, WriteCount> m_writeDifferenceRows{};
2626 std::array<BlockRows, WriteCount> m_outRows{};
2627 std::array<BlockRows, WriteCount> m_outDifferenceRows{};
2628};
2629
2635template <typename R, typename TimeScalar>
2636class ProcessStep
2637{
2638public:
2639
2641 static constexpr bool EvaluatesBatches = R::BatchedFunction;
2642
2644 ProcessStep(R const & resolved, TimeScalar dt)
2645 : m_resolved{resolved}
2646 , m_dt{dt}
2647 {}
2648
2652 template <typename X, typename... Inputs>
2653 [[nodiscard]] auto
2654 operator()(X const & x, Inputs const &... inputs) const
2655 {
2656 return m_resolved.propagate(x, m_dt, inputs...);
2657 }
2658
2662 template <typename Y, typename States, typename... Inputs>
2663 void
2664 evaluateBatch(Y & y, States const & states, Inputs const &... inputs) const
2665 requires EvaluatesBatches
2666 {
2667 m_resolved.propagateColumns(y, states, m_dt, inputs...);
2668 }
2669
2673 template <typename Y, typename J, typename States, typename... Inputs>
2674 void
2675 evaluateBatchWithAffineJacobians(Y & y, J & jacobians, States const & states, Inputs const &... inputs) const
2676 requires EvaluatesBatches
2677 {
2678 m_resolved.propagateColumnsWithAffineJacobians(y, jacobians, states, m_dt, inputs...);
2679 }
2680
2681private:
2682
2683 R const & m_resolved;
2684 TimeScalar m_dt;
2685};
2686
2691template <typename R, typename TimeScalar>
2692class ProcessStepValues : public ProcessStep<R, TimeScalar>
2693{
2694public:
2695
2696 using ProcessStep<R, TimeScalar>::ProcessStep;
2697
2699 template <typename X, typename... Inputs>
2700 [[nodiscard]] auto
2701 operator()(X const & x, Inputs const &... inputs) const
2702 {
2703 return valueOf(ProcessStep<R, TimeScalar>::operator()(x, inputs...));
2704 }
2705};
2706
2710template <typename Space, typename Call>
2711[[nodiscard]] ResolvedProcess<Call, Space>
2712resolveProcess(Call const & call, Space const & space)
2713{
2714 return ResolvedProcess<Call, Space>{call, space};
2715}
2716
2718template <typename... Args>
2719inline constexpr bool referenceAfterFirst = []<std::size_t... I>(std::index_sequence<I...>) {
2720 return ((I > 0 && isLinearizedAbout<std::tuple_element_t<I, std::tuple<Args...>>>) || ... || false);
2721}(std::index_sequence_for<Args...>{});
2722
2726template <typename F, typename... Args>
2727[[nodiscard]] decltype(auto)
2728withReference(F && f, Args const &... args)
2729{
2730 static_assert(
2731 !referenceAfterFirst<Args...>,
2732 "a prediction's linearizedAbout() reference comes first among the arguments after the step, once"
2733 );
2734 if constexpr (leadsWithReference<Args...>)
2735 {
2736 return std::forward<F>(f)(args...);
2737 }
2738 else
2739 {
2740 return std::forward<F>(f)(NoReference{}, args...);
2741 }
2742}
2743
2744} // namespace internal
2745
2752template <Space Space_, typename Derived, ProcessModel Model, typename... Args>
2753[[nodiscard]] typename Moments<Space_, typename Derived::Scalar>::VectorType
2755 Space_ const & space,
2756 Eigen::MatrixBase<Derived> const & x,
2757 Model const & model,
2758 TimeStep<typename Derived::Scalar> auto const & dt,
2759 Args const &... args
2760)
2761{
2762 using Scalar = typename Derived::Scalar;
2763 EMRYS_FILTER_ASSERT(x.size() == valueDimension(space) && "the state does not fit the space");
2764 auto const call = internal::modelWith(std::cref(model), args...);
2765 auto const resolved = internal::resolveProcess(call, space);
2766 return internal::valueOf(resolved.propagate(x.derived(), internal::toScalarTime<Scalar>(dt)));
2767}
2768
2769template <Space Space_, typename Model_, typename Method_, RealScalar Scalar_>
2770class FixedStepModel;
2771
2775template <typename Model>
2776inline constexpr bool isFixedStep = false;
2777
2781template <typename Space_, typename Model_, typename Method_, typename Scalar_>
2783
2784template <ProcessModel Model_, typename Method_, typename Time_>
2785class UnboundFixedStep;
2786
2791template <typename Model>
2792inline constexpr bool isUnboundStep = false;
2793
2797template <typename Model_, typename Method_, typename Time_>
2799
2815template <Space Space_, RealScalar Scalar_ = double>
2831
2832namespace internal
2833{
2834
2839template <typename Method, typename R>
2840inline constexpr bool predictsByJacobian =
2841 std::is_same_v<Method, methods::Linearized> && R::InputCount == 0 && R::ReturnsJacobian;
2842
2847template <typename Method, typename SpaceType, typename Prior, typename R, typename TimeScalar>
2848[[nodiscard]] auto
2849transformStep(Method const & method, SpaceType const & space, Prior const & prior, R const & resolved, TimeScalar dt)
2850{
2851 auto const f = ProcessStep{resolved, dt};
2852 return std::apply(
2853 [&](auto const &... inputs) {
2854 if constexpr (isSigmaPointMethod<Method> && R::factorsAffine())
2855 {
2856 auto const factored = FactoredAt<R::affineSize(), R::affineMaxSize(), Method>{
2857 method,
2858 resolved.template affineCoordinates<true>()
2859 };
2860 return transformBy(factored, f, space, prior, inputs...);
2861 }
2862 else
2863 {
2864 return transformBy(method, f, space, prior, inputs...);
2865 }
2866 },
2867 resolved.inputs()
2868 );
2869}
2870
2872template <UncertainInput Prior>
2873[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
2874gaussianOf(Prior const & prior)
2875{
2876 using Result = GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>;
2877 if constexpr (std::is_same_v<Prior, Result>)
2878 {
2879 return prior;
2880 }
2881 else
2882 {
2883 return {
2884 prior.space(),
2885 typename Result::VectorType{prior.mean()},
2886 typename Result::MatrixType{covarianceLowerOf(prior)}
2887 };
2888 }
2889}
2890
2894template <UncertainInput Prior, typename Form, typename Noise>
2895[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
2896jointOf(
2897 Prior const & prior,
2898 OutputWithCross<typename Prior::SpaceType, typename Prior::SpaceType, Form, typename Prior::Scalar> step,
2899 Noise const & noise
2900)
2901{
2902 using Matrix = typename PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>::MatrixType;
2903 Matrix cross = step.crossCovariance();
2904 auto predicted = std::move(step).output();
2905 predicted.setCovariance(predicted.covarianceLower() + noise);
2906 return {gaussianOf(prior), std::move(predicted), std::move(cross)};
2907}
2908
2910template <UncertainInput Prior, typename Form>
2911[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
2912jointOf(
2913 Prior const & prior,
2914 OutputWithCross<typename Prior::SpaceType, typename Prior::SpaceType, Form, typename Prior::Scalar> step
2915)
2916{
2917 using Matrix = typename PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>::MatrixType;
2918 Matrix cross = step.crossCovariance();
2919 return {gaussianOf(prior), std::move(step).output(), std::move(cross)};
2920}
2921
2926template <typename R, typename Derived, typename TimeScalar>
2927[[nodiscard]] auto
2928stepWithJacobian(R const & resolved, Eigen::MatrixBase<Derived> const & x, TimeScalar dt)
2929{
2930 return resolved.propagate(x.derived(), dt);
2931}
2932
2939template <typename Space, typename Scalar, typename R, typename AddNoise>
2940void
2941predictInPlaceByJacobianWithNoise(
2942 GaussianParams<Space, Scalar> & state,
2943 R const & resolved,
2944 Scalar dt,
2945 AddNoise const & addNoise
2946)
2947{
2948 using Matrix = typename GaussianParams<Space, Scalar>::MatrixType;
2949 auto const & space = state.space();
2950 auto step = stepWithJacobian(resolved, state.mean(), dt);
2951 auto & moments = GaussianAccess::moments(state);
2952 Matrix & cov = moments.covarianceLower();
2953 cov.template triangularView<Eigen::StrictlyUpper>() = cov.transpose();
2954 auto propagated = Matrix{};
2955 propagated.noalias() = multiply(step.jacobian, cov);
2956 cov.noalias() = multiply(propagated, step.jacobian.transpose());
2957 addNoise(cov);
2958 moments.mean() = std::move(step.value);
2959 normalizeValues(space, moments.mean());
2960}
2961
2965template <typename Space, typename Scalar, typename R>
2966void
2967predictInPlaceByJacobian(GaussianParams<Space, Scalar> & state, R const & resolved, Scalar dt)
2968{
2969 auto const addNoise = [&](typename GaussianParams<Space, Scalar>::MatrixType & cov) {
2970 cov += resolved.template noise<Scalar>(dt);
2971 };
2972 predictInPlaceByJacobianWithNoise(state, resolved, dt, addNoise);
2973}
2974
2981template <typename Method, typename R, typename Space, typename Base, typename Reference>
2982[[nodiscard]] LinearizationAbout<Space, Space, typename Base::Scalar>
2983linearizeStepAbout(
2984 Method const & method,
2985 R const & resolved,
2986 Space const & space,
2987 typename Base::Scalar dt,
2988 Eigen::MatrixBase<Base> const & base,
2989 LinearizedAbout<Reference> const & about
2990)
2991{
2992 using Scalar = typename Base::Scalar;
2993 using Result = LinearizationAbout<Space, Space, Scalar>;
2994 if constexpr (predictsByJacobian<Method, R>)
2995 {
2996 typename Moments<Space, Scalar>::VectorType const point = about.mean();
2997 auto step = stepWithJacobian(resolved, point, dt);
2998 Eigen::Index const n = differenceDimension(space);
2999 auto result = Result{
3000 space,
3001 typename Result::OutputVector{std::move(step.value)},
3002 typename Result::JacobianMatrix{std::move(step.jacobian)},
3003 Result::ErrorMatrix::Zero(n, n),
3004 Result::ErrorMatrix::Zero(n, n)
3005 };
3006 normalizeValues(space, result.value);
3007 toChartOf(space, base, point, result);
3008 return result;
3009 }
3010 else
3011 {
3012 auto const f = ProcessStep{resolved, dt};
3013 return std::apply(
3014 [&](auto const &... inputs) {
3015 if constexpr (isSigmaPointMethod<Method> && R::factorsAffine())
3016 {
3017 auto const factored = FactoredAt<R::affineSize(), R::affineMaxSize(), Method>{
3018 method,
3019 resolved.template affineCoordinates<true>()
3020 };
3021 return linearizeAbout(factored, f, space, space, base, about, inputs...);
3022 }
3023 else
3024 {
3025 return linearizeAbout(method, f, space, space, base, about, inputs...);
3026 }
3027 },
3028 resolved.inputs()
3029 );
3030 }
3031}
3032
3040template <typename Method, UncertainInput Prior, typename R, typename Derived, typename Reference>
3041[[nodiscard]] OutputWithCross<
3042 typename Prior::SpaceType,
3043 typename Prior::SpaceType,
3044 JacobianForm,
3045 typename Prior::Scalar>
3046predictAbout(
3047 Method const & method,
3048 Prior const & prior,
3049 R const & resolved,
3050 typename Prior::Scalar dt,
3051 Eigen::MatrixBase<Derived> const & noise,
3052 LinearizedAbout<Reference> const & about
3053)
3054{
3055 using Space = typename Prior::SpaceType;
3056 using Scalar = typename Prior::Scalar;
3057 using Result = OutputWithCross<Space, Space, JacobianForm, Scalar>;
3058 using Matrix = typename GaussianParams<Space, Scalar>::MatrixType;
3059 auto const & space = prior.space();
3060 auto linearization = linearizeStepAbout(method, resolved, space, dt, prior.mean(), about);
3061 Matrix const cov = symmetricFromLower<Matrix>(covarianceLowerOf(prior));
3062 typename Result::CrossMatrix cross = multiply(cov, linearization.jacobian.transpose());
3063 Matrix explained = multiply(linearization.jacobian, cross);
3064 explained = (Scalar(0.5) * (explained + explained.transpose())).eval();
3065 // The process noise is one more input the step does not explain.
3066 Matrix others = linearization.inputs + symmetricFromLower<Matrix>(noise);
3067 return Result{
3068 space,
3069 std::move(linearization.value),
3070 std::move(linearization.jacobian),
3071 std::move(cross),
3072 std::move(explained),
3073 std::move(others),
3074 std::move(linearization.unexplained)
3075 };
3076}
3077
3082template <typename Method, UncertainInput Prior, typename R, typename Derived, typename Reference = NoReference>
3083[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
3084predictWithNoise(
3085 Method const & method,
3086 Prior const & prior,
3087 R const & resolved,
3088 typename Prior::Scalar dt,
3089 Eigen::MatrixBase<Derived> const & noise,
3090 Reference const & reference = {}
3091)
3092{
3093 if constexpr (isLinearizedAbout<Reference>)
3094 {
3095 return predictAbout(method, prior, resolved, dt, noise, reference).output();
3096 }
3097 else
3098 {
3099 auto predicted = transformStep(method, prior.space(), prior, resolved, dt);
3100 predicted.setCovariance(predicted.covarianceLower() + noise);
3101 return predicted;
3102 }
3103}
3104
3106template <typename Method, UncertainInput Prior, typename R, typename Derived, typename Reference = NoReference>
3107[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
3108predictJointWithNoise(
3109 Method const & method,
3110 Prior const & prior,
3111 R const & resolved,
3112 typename Prior::Scalar dt,
3113 Eigen::MatrixBase<Derived> const & noise,
3114 Reference const & reference = {}
3115)
3116{
3117 if constexpr (isLinearizedAbout<Reference>)
3118 {
3119 return jointOf(prior, predictAbout(method, prior, resolved, dt, noise, reference));
3120 }
3121 else
3122 {
3123 return jointOf(prior, transformStep(method, prior.space(), withCross(prior), resolved, dt), noise);
3124 }
3125}
3126
3131template <typename Method, UncertainInput Prior, typename R, typename Derived, typename Reference>
3132[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
3133predictResolved(
3134 Method const & method,
3135 Prior const & prior,
3136 R const & resolved,
3137 typename Prior::Scalar dt,
3138 Eigen::MatrixBase<Derived> const & noise,
3139 Reference const & reference
3140)
3141{
3142 using Result = GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>;
3143 if constexpr (!isLinearizedAbout<Reference> && predictsByJacobian<Method, R>)
3144 {
3145 // The function gives its Jacobian at the mean, so the Linearized prediction needs no
3146 // differentiation: x' = f(x), P' = F P F^T + Q.
3147 auto result = Result{
3148 prior.space(),
3149 typename Result::VectorType{prior.mean()},
3150 typename Result::MatrixType{covarianceLowerOf(prior)}
3151 };
3152 predictInPlaceByJacobianWithNoise(result, resolved, dt, [&](typename Result::MatrixType & cov) {
3153 cov += noise;
3154 });
3155 return result;
3156 }
3157 else
3158 {
3159 return predictWithNoise(method, prior, resolved, dt, noise, reference);
3160 }
3161}
3162
3166template <typename Model, typename Method>
3167inline constexpr bool predictsBy = true;
3168
3170template <typename Space_, typename Model_, typename ModelMethod, typename Scalar_, typename Method>
3171inline constexpr bool predictsBy<FixedStepModel<Space_, Model_, ModelMethod, Scalar_>, Method>{
3172 std::is_same_v<ModelMethod, Method>
3173};
3174
3182template <typename Method>
3183[[nodiscard]] consteval bool
3184checkPredictionMethod()
3185{
3186 static_assert(
3187 !isIterated<Method>,
3188 "Iterated is a measurement update's method: a prediction has no posterior to relinearize at, so give the "
3189 "filter or the fixed-step model its inner method"
3190 );
3191 return true;
3192}
3193
3197template <typename Model>
3198struct DefaultMethodOf
3199{
3200 using type = methods::Linearized;
3201};
3202
3204template <typename Space_, typename Model_, typename Method_, typename Scalar_>
3205struct DefaultMethodOf<FixedStepModel<Space_, Model_, Method_, Scalar_>>
3206{
3207 using type = Method_;
3208};
3209
3210} // namespace internal
3211
3219template <typename Model>
3220using DefaultMethodFor = typename internal::DefaultMethodOf<Model>::type;
3221
3227{};
3228
3235template <typename Model, typename Method>
3236using MethodFor = std::conditional_t<isFixedStep<Model>, ModelsMethod, Method>;
3237
3238namespace internal
3239{
3240
3244template <typename Model, typename Method>
3245 requires(!std::is_same_v<Method, ModelsMethod>)
3246[[nodiscard]] Method
3247predictionMethod(Model const &, Method method)
3248{
3249 return method;
3250}
3251
3253template <typename Space_, typename Model_, typename Method_, typename Scalar_>
3254[[nodiscard]] Method_
3256{
3257 return model.method();
3258}
3259
3261template <typename Model>
3262inline constexpr bool cachesTransition = false;
3263
3265template <typename Space_, typename Model_, typename Method_, typename Scalar_>
3266inline constexpr bool cachesTransition<FixedStepModel<Space_, Model_, Method_, Scalar_>> =
3268
3270struct NoTransition
3271{};
3272
3273} // namespace internal
3274
3310template <typename Method, UncertainInput Prior, ProcessModel Model, typename... Args>
3311[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
3313 Method const & method,
3314 Prior const & prior,
3315 Model const & model,
3316 TimeStep<typename Prior::Scalar> auto const & time,
3317 Args const &... args
3318)
3319{
3320 if constexpr (!requires { prior.covarianceLower(); })
3321 {
3322 return predict(method, internal::withCovariance(prior), model, time, args...);
3323 }
3324 else
3325 {
3326 using Scalar = typename Prior::Scalar;
3327 auto const & space = prior.space();
3328 Scalar const dt = internal::toScalarTime<Scalar>(time);
3329 internal::assertPartitions(model, space);
3330 return internal::withReference(
3331 [&](auto const & reference, auto const &... inputs) {
3332 auto const call = internal::modelWith(std::cref(model), inputs...);
3333 auto const resolved = internal::resolveProcess(call, space);
3334 return internal::predictResolved(
3335 method,
3336 prior,
3337 resolved,
3338 dt,
3339 resolved.template noise<Scalar>(dt),
3340 reference
3341 );
3342 },
3343 args...
3344 );
3345 }
3346}
3347
3360template <typename Method, UncertainInput Prior, ProcessModel Model, typename... Args>
3361[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
3363 Method const & method,
3364 Prior const & prior,
3365 Model const & model,
3366 TimeStep<typename Prior::Scalar> auto const & time,
3367 Args const &... args
3368)
3369{
3370 if constexpr (!requires { prior.covarianceLower(); })
3371 {
3372 return predictJoint(method, internal::withCovariance(prior), model, time, args...);
3373 }
3374 else
3375 {
3376 using Scalar = typename Prior::Scalar;
3377 auto const & space = prior.space();
3378 Scalar const dt = internal::toScalarTime<Scalar>(time);
3379 internal::assertPartitions(model, space);
3380 return internal::withReference(
3381 [&](auto const & reference, auto const &... inputs) {
3382 auto const call = internal::modelWith(std::cref(model), inputs...);
3383 auto const resolved = internal::resolveProcess(call, space);
3384 return internal::predictJointWithNoise(
3385 method,
3386 prior,
3387 resolved,
3388 dt,
3389 resolved.template noise<Scalar>(dt),
3390 reference
3391 );
3392 },
3393 args...
3394 );
3395 }
3396}
3397
3413template <ProcessModel Model_, typename Method_, typename Time_>
3415{
3416public:
3417
3418 using ModelType = Model_;
3419 using MethodType = Method_;
3420 using TimeType = Time_;
3421
3423 UnboundFixedStep(Model_ model, Time_ dt, Method_ method)
3424 : m_model{std::move(model)}
3425 , m_method{std::move(method)}
3426 , m_timeStep{std::move(dt)}
3427 {}
3428
3430 [[nodiscard]] Model_ const &
3431 model() const
3432 {
3433 return m_model;
3434 }
3435
3437 [[nodiscard]] Method_ const &
3438 method() const
3439 {
3440 return m_method;
3441 }
3442
3444 [[nodiscard]] Time_ const &
3445 timeStep() const
3446 {
3447 return m_timeStep;
3448 }
3449
3451 template <typename Space>
3452 [[nodiscard]] bool
3453 partitions(Space const & space) const
3454 {
3455 return m_model.partitions(space);
3456 }
3457
3461 template <typename Space>
3462 [[nodiscard]] static consteval internal::StaticPartition
3464 {
3465 return internal::staticPartition<Model_, Space>();
3466 }
3467
3468private:
3469
3470 Model_ m_model;
3471 EMRYS_FILTER_NO_UNIQUE_ADDRESS Method_ m_method;
3472 Time_ m_timeStep;
3473};
3474
3475namespace internal
3476{
3477
3481template <typename Space_>
3482[[nodiscard]] Space_
3483boundSpace()
3484{
3485 static_assert(
3486 FixedSizeSpace<Space_> && std::default_initializable<Space_>,
3487 "fixedStep(model, dt) binds to a fixed-size state space; over a space sized at run time, give "
3488 "FixedStepModel{model, space, dt} the space"
3489 );
3490 return Space_{};
3491}
3492
3493} // namespace internal
3494
3530template <Space Space_, typename Model_, typename Method_ = methods::Linearized, RealScalar Scalar_ = double>
3532{
3533 static_assert(ProcessModel<Model_>, "a fixed-step model is a process model taken over one step");
3534 static_assert(internal::checkPredictionMethod<Method_>());
3535
3536public:
3537
3538 using Scalar = Scalar_;
3539 using SpaceType = Space_;
3540 using ModelType = Model_;
3541 using MethodType = Method_;
3543
3545 static constexpr bool ReadsOnlyState = internal::readsOnlyState<Model_, Space_>();
3546
3551 static constexpr bool CachesTransition = std::is_same_v<Method_, methods::Linearized> && ReadsOnlyState
3552 && internal::returnsStepJacobianIn<Model_, Space_, Scalar_>
3553 && internal::affineInEverything<Model_>();
3554
3558 FixedStepModel(Model_ model, Space_ const & space, TimeStep<Scalar_> auto const & dt, Method_ method = {})
3559 : m_model{std::move(model)}
3560 , m_method{std::move(method)}
3561 , m_timeStep{internal::toScalarTime<Scalar_>(dt)}
3562 , m_noise{internal::processNoise<Scalar_>(m_model, space, m_timeStep)}
3563 , m_noiseRoot{internal::defaultRoot(m_noise)}
3564 , m_transition{transitionOf(m_model, space, m_timeStep)}
3565 , m_transitionInverse{inverseOf(m_transition)}
3566 {
3567 internal::assertPartitions(m_model, space);
3568 }
3569
3574 template <TimeStep<Scalar_> Time>
3576 : FixedStepModel{step.model(), internal::boundSpace<Space_>(), step.timeStep(), step.method()}
3577 {}
3578
3582 template <typename Space>
3583 [[nodiscard]] static consteval internal::StaticPartition
3585 {
3586 return internal::staticPartition<Model_, Space>();
3587 }
3588
3590 [[nodiscard]] Model_ const &
3591 model() const
3592 {
3593 return m_model;
3594 }
3595
3597 [[nodiscard]] Method_ const &
3598 method() const
3599 {
3600 return m_method;
3601 }
3602
3604 [[nodiscard]] Scalar_
3605 timeStep() const
3606 {
3607 return m_timeStep;
3608 }
3609
3611 [[nodiscard]] MatrixType const &
3612 noise() const
3613 {
3614 return m_noise;
3615 }
3616
3618 [[nodiscard]] MatrixType const &
3620 requires CachesTransition
3621 {
3622 return m_transition;
3623 }
3624
3629 [[nodiscard]] MatrixType const &
3631 {
3632 return m_noiseRoot;
3633 }
3634
3638 [[nodiscard]] std::optional<MatrixType> const &
3640 requires CachesTransition
3641 {
3642 return m_transitionInverse;
3643 }
3644
3649 template <typename Derived>
3651 propagate(Space_ const & space, Eigen::MatrixBase<Derived> const & x) const
3652 requires ReadsOnlyState
3653 {
3654 return Emrys::propagate(space, x, m_model, m_timeStep);
3655 }
3656
3658 [[nodiscard]] bool
3659 partitions(Space_ const & space) const
3660 {
3661 return m_model.partitions(space) && m_noise.rows() == differenceDimension(space);
3662 }
3663
3664private:
3665
3669 [[nodiscard]] static auto
3670 transitionOf(
3671 [[maybe_unused]] Model_ const & model,
3672 [[maybe_unused]] Space_ const & space,
3673 [[maybe_unused]] Scalar_ dt
3674 )
3675 {
3676 if constexpr (CachesTransition)
3677 {
3678 auto const call = internal::modelWith(std::cref(model));
3679 auto const resolved = internal::resolveProcess(call, space);
3680 return MatrixType{resolved.template transition<Scalar_>(dt)};
3681 }
3682 else
3683 {
3684 return internal::NoTransition{};
3685 }
3686 }
3687
3689 [[nodiscard]] static auto
3690 inverseOf([[maybe_unused]] auto const & transition)
3691 {
3692 if constexpr (CachesTransition)
3693 {
3694 auto const lu = Eigen::FullPivLU<MatrixType>{transition};
3695 return lu.isInvertible() ? std::optional<MatrixType>{internal::inverseFrom<MatrixType>(lu)} : std::nullopt;
3696 }
3697 else
3698 {
3699 return internal::NoTransition{};
3700 }
3701 }
3702
3703 Model_ m_model;
3704 EMRYS_FILTER_NO_UNIQUE_ADDRESS Method_ m_method;
3705 Scalar_ m_timeStep;
3706 MatrixType m_noise;
3707 MatrixType m_noiseRoot;
3708
3710 EMRYS_FILTER_NO_UNIQUE_ADDRESS std::conditional_t<CachesTransition, MatrixType, internal::NoTransition>
3711 m_transition;
3712
3715 std::conditional_t<CachesTransition, std::optional<MatrixType>, internal::NoTransition>
3716 m_transitionInverse;
3717};
3718
3720template <typename Model, typename Space, typename Time>
3721FixedStepModel(Model, Space, Time) -> FixedStepModel<Space, Model, methods::Linearized, double>;
3722
3723template <typename Model, typename Space, typename Time, typename Method>
3724FixedStepModel(Model, Space, Time, Method) -> FixedStepModel<Space, Model, Method, double>;
3725
3727
3733template <ProcessModel Model, typename Time, typename Method = methods::Linearized>
3734 requires TimeStep<Time, double>
3735[[nodiscard]] UnboundFixedStep<Model, Method, Time>
3736fixedStep(Model model, Time dt, Method method = {})
3737{
3738 return UnboundFixedStep<Model, Method, Time>{std::move(model), std::move(dt), std::move(method)};
3739}
3740
3741namespace internal
3742{
3743
3745template <typename Model, typename Space, typename Scalar>
3746struct BoundStepOf
3747{
3748 using type = Model;
3749};
3750
3752template <typename Model_, typename Method_, typename Time_, typename Space, typename Scalar>
3753struct BoundStepOf<UnboundFixedStep<Model_, Method_, Time_>, Space, Scalar>
3754{
3755 using type = FixedStepModel<Space, Model_, Method_, Scalar>;
3756};
3757
3761template <typename Model, typename Space, typename Scalar>
3762using BoundStep = typename BoundStepOf<Model, Space, Scalar>::type;
3763
3765template <typename Model_, typename Method_, typename Time_>
3766struct DefaultMethodOf<UnboundFixedStep<Model_, Method_, Time_>>
3767{
3768 using type = Method_;
3769};
3770
3771} // namespace internal
3772
3773namespace internal
3774{
3775
3780template <UncertainInput Prior, typename Space_, typename Model_, typename Method_, typename Scalar_>
3781 requires FixedStepModel<Space_, Model_, Method_, Scalar_>::CachesTransition
3782[[nodiscard]] OutputWithCross<Space_, Space_, JacobianForm, Scalar_>
3783predictByTransition(Prior const & prior, FixedStepModel<Space_, Model_, Method_, Scalar_> const & model)
3784{
3785 using MatrixType = typename FixedStepModel<Space_, Model_, Method_, Scalar_>::MatrixType;
3786 auto const & space = prior.space();
3787 auto const & f = model.transition();
3788 typename Moments<Space_, Scalar_>::VectorType mean{model.propagate(space, prior.mean())};
3789 normalizeValues(space, mean);
3790 MatrixType cross = multiply(covarianceLowerOf(prior), f.transpose());
3791 MatrixType explained = multiply(f, cross);
3792 explained = (Scalar_(0.5) * (explained + explained.transpose())).eval();
3793 Eigen::Index const n = explained.rows();
3794 // The process noise is one more input the transition does not explain.
3795 return OutputWithCross<Space_, Space_, JacobianForm, Scalar_>{
3796 space,
3797 std::move(mean),
3798 MatrixType{f},
3799 std::move(cross),
3800 std::move(explained),
3801 MatrixType{model.noise()},
3802 MatrixType::Zero(n, n)
3803 };
3804}
3805
3806} // namespace internal
3807
3820template <UncertainInput Prior, typename Space_, typename Model_, typename Method_, typename Scalar_, typename... Args>
3821[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
3822predict(Prior const & prior, FixedStepModel<Space_, Model_, Method_, Scalar_> const & model, Args const &... args)
3823{
3824 static_assert(std::is_same_v<typename Prior::SpaceType, Space_>, "the model must be over the prior's space");
3825 static_assert(std::is_same_v<typename Prior::Scalar, Scalar_>, "the model's scalar must be the prior's");
3827 [[maybe_unused]] auto const & space = prior.space();
3828 internal::assertPartitions(model, space);
3829 if constexpr (Model::CachesTransition && sizeof...(Args) == 0)
3830 {
3831 return internal::predictByTransition(prior, model).output();
3832 }
3833 else
3834 {
3835 return internal::withReference(
3836 [&](auto const & reference, auto const &... inputs) {
3837 auto const call = internal::modelWith(std::cref(model.model()), inputs...);
3838 auto const resolved = internal::resolveProcess(call, space);
3839 return internal::predictResolved(
3840 model.method(),
3841 prior,
3842 resolved,
3843 model.timeStep(),
3844 model.noise(),
3845 reference
3846 );
3847 },
3848 args...
3849 );
3850 }
3851}
3852
3860template <UncertainInput Prior, typename Space_, typename Model_, typename Method_, typename Scalar_, typename... Args>
3861[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
3862predictJoint(Prior const & prior, FixedStepModel<Space_, Model_, Method_, Scalar_> const & model, Args const &... args)
3863{
3864 static_assert(std::is_same_v<typename Prior::SpaceType, Space_>, "the model must be over the prior's space");
3865 static_assert(std::is_same_v<typename Prior::Scalar, Scalar_>, "the model's scalar must be the prior's");
3867 [[maybe_unused]] auto const & space = prior.space();
3868 internal::assertPartitions(model, space);
3869 if constexpr (Model::CachesTransition && sizeof...(Args) == 0)
3870 {
3871 return internal::jointOf(prior, internal::predictByTransition(prior, model));
3872 }
3873 else
3874 {
3875 return internal::withReference(
3876 [&](auto const & reference, auto const &... inputs) {
3877 auto const call = internal::modelWith(std::cref(model.model()), inputs...);
3878 auto const resolved = internal::resolveProcess(call, space);
3879 return internal::predictJointWithNoise(
3880 model.method(),
3881 prior,
3882 resolved,
3883 model.timeStep(),
3884 model.noise(),
3885 reference
3886 );
3887 },
3888 args...
3889 );
3890 }
3891}
3892
3893namespace internal
3894{
3895
3897template <typename Prior, typename Model_, typename Method_, typename Time_>
3898[[nodiscard]] FixedStepModel<typename Prior::SpaceType, Model_, Method_, typename Prior::Scalar>
3899boundToPrior(Prior const & prior, UnboundFixedStep<Model_, Method_, Time_> const & step)
3900{
3901 return FixedStepModel<typename Prior::SpaceType, Model_, Method_, typename Prior::Scalar>{
3902 step.model(),
3903 prior.space(),
3904 step.timeStep(),
3905 step.method()
3906 };
3907}
3908
3909} // namespace internal
3910
3920template <UncertainInput Prior, typename Model_, typename Method_, typename Time_, typename... Args>
3921[[nodiscard]] GaussianParams<typename Prior::SpaceType, typename Prior::Scalar>
3922predict(Prior const & prior, UnboundFixedStep<Model_, Method_, Time_> const & step, Args const &... args)
3923{
3924 return Emrys::predict(prior, internal::boundToPrior(prior, step), args...);
3925}
3926
3933template <UncertainInput Prior, typename Model_, typename Method_, typename Time_, typename... Args>
3934[[nodiscard]] PredictedJoint<typename Prior::SpaceType, typename Prior::Scalar>
3935predictJoint(Prior const & prior, UnboundFixedStep<Model_, Method_, Time_> const & step, Args const &... args)
3936{
3937 return Emrys::predictJoint(prior, internal::boundToPrior(prior, step), args...);
3938}
3939
3940namespace internal
3941{
3942
3946template <typename Method, typename Space, typename Scalar, ProcessModel Model, typename... Args>
3947void
3948predictInPlace(
3949 Method const & method,
3950 GaussianParams<Space, Scalar> & state,
3951 Model const & model,
3952 Scalar dt,
3953 Args const &... args
3954)
3955{
3956 withReference(
3957 [&](auto const & reference, auto const &... inputs) {
3958 auto const call = modelWith(std::cref(model), inputs...);
3959 auto const resolved = resolveProcess(call, state.space());
3960 using R = std::remove_cvref_t<decltype(resolved)>;
3961 if constexpr (!isLinearizedAbout<std::remove_cvref_t<decltype(reference)>> && predictsByJacobian<Method, R>)
3962 {
3963 assertPartitions(model, state.space());
3964 predictInPlaceByJacobian(state, resolved, dt);
3965 }
3966 else
3967 {
3968 state = Emrys::predict(method, state, model, dt, args...);
3969 }
3970 },
3971 args...
3972 );
3973}
3974
3979template <
3980 typename Space,
3981 typename Scalar,
3982 typename Space_,
3983 typename Model_,
3984 typename Method_,
3985 typename Scalar_,
3986 typename... Args>
3987void
3988predictInPlace(
3989 GaussianParams<Space, Scalar> & state,
3990 FixedStepModel<Space_, Model_, Method_, Scalar_> const & model,
3991 Args const &... args
3992)
3993{
3994 using Model = FixedStepModel<Space_, Model_, Method_, Scalar_>;
3995 static_assert(std::is_same_v<Space, Space_>);
3996 static_assert(std::is_same_v<Scalar, Scalar_>);
3997 if constexpr (Model::CachesTransition && sizeof...(Args) == 0)
3998 {
3999 state = Emrys::predict(state, model);
4000 }
4001 else
4002 {
4003 withReference(
4004 [&](auto const & reference, auto const &... inputs) {
4005 auto const call = modelWith(std::cref(model.model()), inputs...);
4006 auto const resolved = resolveProcess(call, state.space());
4007 using R = std::remove_cvref_t<decltype(resolved)>;
4008 if constexpr (!isLinearizedAbout<std::remove_cvref_t<decltype(reference)>>
4009 && predictsByJacobian<Method_, R>)
4010 {
4011 assertPartitions(model, state.space());
4012 predictInPlaceByJacobianWithNoise(state, resolved, model.timeStep(), [&](auto & cov) {
4013 cov += model.noise();
4014 });
4015 }
4016 else
4017 {
4018 state = Emrys::predict(state, model, args...);
4019 }
4020 },
4021 args...
4022 );
4023 }
4024}
4025
4026} // namespace internal
4027
4028} // namespace Emrys
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...
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Measurement models and their observations (decisions.md [model-policies], [sensors],...
The transform methods, as policies made by their factories (decisions.md [policy-factories],...
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'...
The policies a model is built from, and the marks on a step's arguments (decisions....
How a model's reads() meet a state and a call, all internal (decisions.md [model-policies],...
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...
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...
Time steps (decisions.md [time-steps]): TimeTraits, which converts a time type to a scalar,...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
Process models side by side, as compose() puts them (decisions.md [model-policies],...
Definition ProcessModel.h:1804
typename WritesPolicy::OutputSpace OutputSpace
The output space: the Composite of the blocks written, in order.
Definition ProcessModel.h:1863
auto operator()(In const &in, Out const &out, X const &x, TimeScalar dt) const
Definition ProcessModel.h:1900
Policy< K > const & policy() const
Definition ProcessModel.h:1880
Composition const & function() const
Definition ProcessModel.h:1872
std::tuple< Models... > const & parts() const
Definition ProcessModel.h:1889
static constexpr bool HasFunction
a process model always has a function
Definition ProcessModel.h:1833
typename internal::AffineOf< ReadsPolicy, std::tuple< Models... > >::type AffinePolicy
The affineIn() of the whole.
Definition ProcessModel.h:1842
Moments< Out, Scalar >::MatrixType noiseOver(Out const &out, Scalar dt) const
Definition ProcessModel.h:1929
static consteval internal::StaticPartition staticPartition()
Definition ProcessModel.h:1945
std::conditional_t< K==internal::PolicyKind::reads, ReadsPolicy, std::conditional_t< K==internal::PolicyKind::writes, WritesPolicy, std::conditional_t< K==internal::PolicyKind::affineIn, AffinePolicy, internal::NoPolicy > > > Policy
The policy of kind K, or internal::NoPolicy.
Definition ProcessModel.h:1851
bool partitions(Space const &space) const
Definition ProcessModel.h:1953
internal::NoJacobians MarkType
The whole writes no Jacobian in a batch: a method that needs one differentiates it.
Definition ProcessModel.h:1910
typename WritesPolicy::PathList WritePaths
The paths of the blocks written, a TagPaths.
Definition ProcessModel.h:1860
static constexpr bool has
Whether the policy of kind K is one the whole has: reads(), writes(), and affineIn().
Definition ProcessModel.h:1846
typename internal::FirstScalarOr< internal::DeclaredScalarsOf< Models... > >::type Scalar
the scalar type: the one its parts state, or double where none states one
Definition ProcessModel.h:1828
static constexpr bool TakesBatches
whether the whole takes a batch: a part's function does (decisions.md [batched-functions])
Definition ProcessModel.h:1907
typename internal::ReadUnionOf< AllItems >::type ReadsPolicy
The reads() of the whole: the union of the parts', in order of first appearance.
Definition ProcessModel.h:1836
Composition(Models... models)
The composition of models.
Definition ProcessModel.h:1866
static constexpr bool IsModel
a model, for internal::isModel
Definition ProcessModel.h:1832
typename internal::WritesOfTuple< AllPaths, AllNames >::type WritesPolicy
The writes() of the whole: the parts', in order.
Definition ProcessModel.h:1839
void operator()(In const &in, Out const &out, X const &x, TimeScalar dt, Y &y) const
Writes into y the whole's step of a batch x, a column per point in the input space in,...
Definition ProcessModel.h:1918
Composition FunctionType
the function: the composition itself
Definition ProcessModel.h:1830
Constant()
The model.
Definition ProcessModel.h:1285
A process model over one fixed time step, with the work that depends only on the step done once.
Definition ProcessModel.h:3532
Space_ SpaceType
the state space
Definition ProcessModel.h:3539
Model_ const & model() const
Definition ProcessModel.h:3591
typename Moments< Space_, Scalar_ >::MatrixType MatrixType
and , over differences
Definition ProcessModel.h:3542
Model_ ModelType
the process model taken over the step
Definition ProcessModel.h:3540
MatrixType const & noise() const
Definition ProcessModel.h:3612
static consteval internal::StaticPartition staticPartition()
Definition ProcessModel.h:3584
FixedStepModel(UnboundFixedStep< Model_, Method_, Time > const &step)
step bound to the space Space_, which is fixed-size and so has one instance: how a filter given fixed...
Definition ProcessModel.h:3575
MatrixType const & noiseRoot() const
Definition ProcessModel.h:3630
FixedStepModel(Model_ model, Space_ const &space, TimeStep< Scalar_ > auto const &dt, Method_ method={})
model taken over the step dt in space, predicting by method.
Definition ProcessModel.h:3558
bool partitions(Space_ const &space) const
Definition ProcessModel.h:3659
Scalar_ timeStep() const
Definition ProcessModel.h:3605
static constexpr bool ReadsOnlyState
Definition ProcessModel.h:3545
Method_ const & method() const
Definition ProcessModel.h:3598
std::optional< MatrixType > const & transitionInverse() const
Definition ProcessModel.h:3639
Scalar_ Scalar
the scalar type
Definition ProcessModel.h:3538
Method_ MethodType
the prediction method
Definition ProcessModel.h:3541
MatrixType const & transition() const
Definition ProcessModel.h:3619
static constexpr bool CachesTransition
Whether the model caches the transition: under Linearized, for a model that is affine in everything i...
Definition ProcessModel.h:3551
Moments< Space_, typenameDerived::Scalar >::VectorType propagate(Space_ const &space, Eigen::MatrixBase< Derived > const &x) const
Definition ProcessModel.h:3651
A Gaussian distribution: a mean and covariance in a space.
Definition Gaussian.h:259
typename MomentsType::MatrixType MatrixType
Definition Gaussian.h:267
A chain of two or more derivatives whose last one is driven by white noise: constant velocity and con...
Definition ProcessModel.h:1074
Kinematic(Scalar_ spectralDensity)
Spectral density spectralDensity on every axis, independently.
Definition ProcessModel.h:1125
Eigen::Matrix< Scalar_, Dimension, Dimension > SpectralDensityMatrix
The spectral density's type: over the last derivative's coordinates.
Definition ProcessModel.h:1104
SpectralDensityMatrix const & spectralDensity() const
Definition ProcessModel.h:1142
Scalar_ Scalar
the scalar type of the spectral density
Definition ProcessModel.h:1101
static constexpr int Dimension
Definition ProcessModel.h:1086
Kinematic(SpectralDensityMatrix const &spectralDensity)
Spectral density matrix spectralDensity.
Definition ProcessModel.h:1130
static constexpr int Order
Definition ProcessModel.h:1083
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
Scalar_ Scalar
the scalar type of the parameters
Definition ProcessModel.h:1229
Scalar_ timeConstant() const
Definition ProcessModel.h:1249
Scalar_ variance() const
Definition ProcessModel.h:1256
OrnsteinUhlenbeck(TimeConstant< Scalar_ > timeConstant, StationaryVariance< Scalar_ > variance)
Time constant timeConstant and stationary variance variance.
Definition ProcessModel.h:1232
A process model: a function of its input and the step, with the policies that say what it reads,...
Definition ProcessModel.h:518
Policy< K > const & policy() const
Definition ProcessModel.h:568
std::tuple< Policies... > const & policies() const
Definition ProcessModel.h:575
ProcessFunction(F function, Policies... policies)
The model of function with policies.
Definition ProcessModel.h:553
F FunctionType
the function
Definition ProcessModel.h:525
bool partitions(Space const &space) const
Definition ProcessModel.h:645
typename internal::ProcessWrites< Whole, Policy< internal::PolicyKind::writes > >::OutputSpace OutputSpace
The output space: the Composite of the blocks written, in order; for a whole-state model,...
Definition ProcessModel.h:550
static constexpr bool Whole
whether the model reads and writes the whole state (decisions.md [whole-state-models])
Definition ProcessModel.h:542
static constexpr bool HasFunction
a process model always has a function
Definition ProcessModel.h:528
F const & function() const
Definition ProcessModel.h:560
typename Set::template Type< K > Policy
The policy of kind K, or internal::NoPolicy.
Definition ProcessModel.h:536
static constexpr bool has
Whether the policy of kind K was given.
Definition ProcessModel.h:532
typename internal::ProcessWrites< Whole, Policy< internal::PolicyKind::writes > >::Paths WritePaths
The paths of the blocks written, a TagPaths; the whole space for a whole-state model.
Definition ProcessModel.h:545
static constexpr bool IsModel
a model, for internal::isModel
Definition ProcessModel.h:527
static consteval internal::StaticPartition staticPartition()
Definition ProcessModel.h:628
Moments< Out, Scalar >::MatrixType noiseOver(Out const &out, Scalar dt) const
Definition ProcessModel.h:586
SpectralDensityMatrix const & spectralDensity() const
Definition ProcessModel.h:1027
Eigen::Matrix< Scalar_, Dimension, Dimension > SpectralDensityMatrix
The spectral density's type: over the block's differences.
Definition ProcessModel.h:1007
RandomWalk(SpectralDensityMatrix const &spectralDensity)
Spectral density matrix spectralDensity.
Definition ProcessModel.h:1015
static constexpr int Dimension
Dimension of the block's differences.
Definition ProcessModel.h:1001
RandomWalk(Scalar_ spectralDensity)
Spectral density spectralDensity on every axis, independently.
Definition ProcessModel.h:1010
Scalar_ Scalar
the scalar type of the spectral density
Definition ProcessModel.h:1004
A process model taken over one fixed time step, not yet bound to a state space: what fixedStep() retu...
Definition ProcessModel.h:3415
Time_ TimeType
the time step's type
Definition ProcessModel.h:3420
Method_ const & method() const
Definition ProcessModel.h:3438
UnboundFixedStep(Model_ model, Time_ dt, Method_ method)
model taken over the step dt, predicting by method
Definition ProcessModel.h:3423
Time_ const & timeStep() const
Definition ProcessModel.h:3445
static consteval internal::StaticPartition staticPartition()
Definition ProcessModel.h:3463
Model_ const & model() const
Definition ProcessModel.h:3431
bool partitions(Space const &space) const
Definition ProcessModel.h:3453
Method_ MethodType
the prediction method
Definition ProcessModel.h:3419
Model_ ModelType
the process model taken over the step
Definition ProcessModel.h:3418
A dense matrix or matrix expression, possibly cv- or reference-qualified: what a const Eigen::MatrixB...
Definition Forwarding.h:39
A process model: what processModel() returns, a built-in model, or a composition of process models (d...
Definition ProcessModel.h:2010
A type usable as the space of a distribution.
Definition Space.h:186
A step of time that converts to Scalar: Scalar itself, or any type with a TimeTraits,...
Definition Time.h:128
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
CategoricalParams< Space, Scalar > predict(CategoricalParams< Space, Scalar > const &prior, MarkovChain< Space, Scalar > const &chain)
Definition Categorical.h:1049
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
UnboundFixedStep< Model, Method, Time > fixedStep(Model model, Time dt, Method method={})
Definition ProcessModel.h:3736
typename internal::DefaultMethodOf< Model >::type DefaultMethodFor
The prediction method of a filter with process model Model, unless it is given one: Linearized,...
Definition ProcessModel.h:3220
constexpr bool isFixedStep
Whether Model is a FixedStepModel, whose predictions take no time step.
Definition ProcessModel.h:2776
Kinematic< Scalar, Position, Velocity > ConstantVelocity
Constant velocity (white-noise acceleration): position moves by velocity times .
Definition ProcessModel.h:1152
Moments< Space_, typenameDerived::Scalar >::VectorType propagate(Space_ const &space, Eigen::MatrixBase< Derived > const &x, Model const &model, TimeStep< typename Derived::Scalar > auto const &dt, Args const &... args)
Definition ProcessModel.h:2754
PredictedJoint< typename Prior::SpaceType, typename Prior::Scalar > predictJoint(Method const &method, Prior const &prior, Model const &model, TimeStep< typename Prior::Scalar > auto const &time, Args const &... args)
Definition ProcessModel.h:3362
auto processModel(F &&function, Policies &&... policies)
Definition ProcessModel.h:702
auto compose(Models &&... models)
Definition Placement.h:1243
std::conditional_t< isFixedStep< Model >, ModelsMethod, Method > MethodFor
The type of a filter constructor's method argument: Method, or ModelsMethod for a fixed-step Model,...
Definition ProcessModel.h:3236
Kinematic< Scalar, Position, Velocity, Acceleration > ConstantAcceleration
Constant acceleration (white-noise jerk).
Definition ProcessModel.h:1158
constexpr bool isUnboundStep
Whether Model is a fixed step not yet bound to a state space: an UnboundFixedStep,...
Definition ProcessModel.h:2792
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto block(Space auto const &space, X &&x)
Definition TagPath.h:979
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
The input names a model's function is affine in, given the rest, as affineIn() declares them (decisio...
Definition Policies.h:278
What a filter with a fixed-step model takes in place of a prediction method: nothing,...
Definition ProcessModel.h:3227
The process model of a state with nothing to move: what a PointMassFilter, whose grid covers the whol...
Definition ProcessModel.h:2019
static consteval internal::StaticPartition staticPartition()
Definition ProcessModel.h:2023
bool partitions(Space const &space) const
Definition ProcessModel.h:2039
One prediction step as a joint: the Gaussian the step started from, the Gaussian it predicted,...
Definition ProcessModel.h:2817
MatrixType cross
Definition ProcessModel.h:2829
typename GaussianType::MatrixType MatrixType
Definition ProcessModel.h:2821
GaussianType predicted
Definition ProcessModel.h:2824
GaussianParams< Space_, Scalar_ > GaussianType
Definition ProcessModel.h:2820
GaussianType filtered
Definition ProcessModel.h:2823
Scalar_ Scalar
Definition ProcessModel.h:2818
Space_ SpaceType
Definition ProcessModel.h:2819
The stationary variance of an OrnsteinUhlenbeck model, named so that it cannot be given in the place...
Definition ProcessModel.h:1176
Scalar value
, nonnegative
Definition ProcessModel.h:1177
The time constant of an OrnsteinUhlenbeck model, named so that it cannot be given in the place of it...
Definition ProcessModel.h:1166
Scalar value
, positive, in the unit of the time steps
Definition ProcessModel.h:1167
Composite< internal::PathTag< Paths >... > OutputSpace
The output space: the Composite of the blocks, in order.
Definition Policies.h:227
TagPaths< Paths... > PathList
The paths, as a TagPaths.
Definition Policies.h:230