EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
MixtureFilter.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <algorithm>
13#include <atomic>
14#include <concepts>
15#include <cstddef>
16#include <expected>
17#include <functional>
18#include <numeric>
19#include <optional>
20#include <ranges>
21#include <type_traits>
22#include <utility>
23
43
44namespace Emrys
45{
46
69template <typename ComponentUpdate, typename Scalar = double, typename Allocator = DefaultAllocator>
71{
72 using ComponentUpdateType = ComponentUpdate;
73 using ComponentsType = internal::Buffer<ComponentUpdate, Allocator>;
74
75 MixtureFilterUpdate() = default;
76
78 explicit MixtureFilterUpdate(Allocator const & allocator)
79 : components(internal::ReBound<Allocator, ComponentUpdate>(allocator))
80 , m_posteriorEnds(internal::ReBound<Allocator, Eigen::Index>(allocator))
81 {}
82
84 [[nodiscard]] Allocator
86 {
87 return Allocator(components.get_allocator());
88 }
89
91 [[nodiscard]] Scalar
93 {
94 return m_logLikelihood;
95 }
96
105 [[nodiscard]] std::ranges::iota_view<Eigen::Index, Eigen::Index>
106 posteriorIndices(Eigen::Index i) const
107 {
108 EMRYS_FILTER_ASSERT(i >= 0 && i < Eigen::Index(m_posteriorEnds.size()) && "prior component index out of range");
109 Eigen::Index const first = i == 0 ? 0 : m_posteriorEnds[std::size_t(i - 1)];
110 return std::views::iota(first, m_posteriorEnds[std::size_t(i)]);
111 }
112
114 bool accepted = false;
115
116private:
117
118 friend struct internal::UpdateAccess;
119
120 Scalar m_logLikelihood = 0;
121
123 internal::Buffer<Eigen::Index, Allocator> m_posteriorEnds;
124};
125
144template <Space Space_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
146{
147 using Scalar = Scalar_;
148 using SpaceType = Space_;
150 using JointsType = internal::Buffer<JointType, Allocator_>;
151 using LogWeightsType = internal::Buffer<Scalar_, Allocator_>;
153
157};
158
249template <
250 Component Component_,
251 typename Model_,
252 typename Method_ = DefaultMethodFor<Model_>,
253 typename Allocator_ = DefaultAllocator,
254 RecursionOver<Component_> Recursion_ = internal::RecursionForType<Component_, Model_, Method_, Allocator_>,
255 typename Upkeep_ = upkeep::None>
256 requires std::same_as<typename Recursion_::ModelType, Model_>
257 && std::same_as<typename Recursion_::MethodType, Method_>
259 : public internal::HeldUpkeep<MixtureFilter<Component_, Model_, Method_, Allocator_, Recursion_, Upkeep_>, Upkeep_>
260{
261 using UpkeepBase = internal::HeldUpkeep<MixtureFilter, Upkeep_>;
262
263 static_assert(
264 internal::predictsBy<Model_, Method_>,
265 "a filter with a fixed-step model predicts by the model's method"
266 );
267 static_assert(internal::checkPredictionMethod<Method_>());
268
269public:
270
273 using ModelType = Model_;
274 using MethodType = Method_;
277 using allocator_type = Allocator_;
278
280 using RecursionType = Recursion_;
281
284
288 template <Measurement M>
290 internal::RebindUpdateAllocator<typename RecursionType::template UpdateType<M>::value_type, Allocator_>,
291 Scalar,
292 Allocator_>;
293
295 template <Measurement M>
296 using UpdateType = std::expected<ResultType<M>, NumericError>;
297
303 Model_ model,
306 std::type_identity_t<Upkeep_> upkeep = {}
307 )
308 : UpkeepBase{std::move(upkeep)}
309 , m_state{std::move(state)}
310 , m_recursion{makeRecursion(std::move(model), std::move(method), m_state)}
311 , m_posterior{m_state.space(), m_state.get_allocator()}
312 {
313 checkState();
314 }
315
319 MixtureFilter(Model_ model, StateType state, std::type_identity_t<Upkeep_> upkeep)
320 requires(!std::same_as<Upkeep_, Emrys::upkeep::None>)
321 : MixtureFilter{std::move(model), std::move(state), MethodFor<Model_, Method_>{}, std::move(upkeep)}
322 {}
323
329 MixtureFilter(Recursion_ recursion, StateType state, std::type_identity_t<Upkeep_> upkeep = {})
330 : UpkeepBase{std::move(upkeep)}
331 , m_state{std::move(state)}
332 , m_recursion{std::move(recursion)}
333 , m_posterior{m_state.space(), m_state.get_allocator()}
334 {
335 checkState();
336 }
337
339 [[nodiscard]] allocator_type
341 {
342 return m_state.get_allocator();
343 }
344
346 [[nodiscard]] StateType const &
347 state() const
348 {
349 return m_state;
350 }
351
357 [[nodiscard]] StateType &
359 {
360 return m_state;
361 }
362
369 void
371 {
372 EMRYS_FILTER_ASSERT(!state.empty() && "the mixture must have a component");
373 m_state = std::move(state);
374 if (m_posterior.get_allocator() != m_state.get_allocator() || m_posterior.space() != m_state.space()
375 || m_posterior.capacity() != m_state.capacity())
376 {
377 // The assignment carried these in, so the scratch is made again from them.
378 m_posterior = StateType{m_state.space(), m_state.get_allocator()};
379 }
380 if constexpr (requires { m_recursion.resetScratch(componentAllocator()); })
381 {
382 if (m_recursion.get_allocator() != componentAllocator())
383 {
384 m_recursion.resetScratch(componentAllocator());
385 }
386 }
387 checkState();
388 }
389
391 [[nodiscard]] RecursionType const &
392 recursion() const
393 {
394 return m_recursion;
395 }
396
398 [[nodiscard]] Model_ const &
399 model() const
400 {
401 return m_recursion.model();
402 }
403
405 [[nodiscard]] Model_ &
407 {
408 return m_recursion.model();
409 }
410
412 [[nodiscard]] Method_ const &
413 method() const
414 {
415 return m_recursion.method();
416 }
417
426 template <typename... Inputs>
427 requires(!isFixedStep<Model_>)
428 [[nodiscard]] auto
429 predict(TimeStep<Scalar> auto const & dt, Inputs const &... inputs)
430 {
431 return predict(execution::sequential, dt, inputs...);
432 }
433
438 template <ExecutionPolicy P, typename... Inputs>
439 requires(!isFixedStep<Model_>)
440 [[nodiscard]] auto
441 predict(P && policy, TimeStep<Scalar> auto const & dt, Inputs const &... inputs)
442 {
443 assertRunsUnder<P>();
444 EMRYS_FILTER_ASSERT(!m_state.empty() && "the mixture must have a component");
445 Scalar const step = internal::toScalarTime<Scalar>(dt);
446 return predictComponents(std::forward<P>(policy), [&](ComponentType & component) {
447 return m_recursion.predict(component, step, inputs...);
448 });
449 }
450
454 template <typename... Inputs>
455 requires(isFixedStep<Model_> && !internal::leadsWithPolicy<Inputs...> && !internal::leadsWithSteps<Inputs...>)
456 [[nodiscard]] auto
457 predict(Inputs const &... inputs)
458 {
459 internal::rejectTimeStep<Inputs...>();
460 return predict(execution::sequential, inputs...);
461 }
462
464 template <ExecutionPolicy P, typename... Inputs>
465 requires(isFixedStep<Model_> && !internal::leadsWithSteps<Inputs...>)
466 [[nodiscard]] auto
467 predict(P && policy, Inputs const &... inputs)
468 {
469 internal::rejectTimeStep<Inputs...>();
470 assertRunsUnder<P>();
471 EMRYS_FILTER_ASSERT(!m_state.empty() && "the mixture must have a component");
472 return predictComponents(std::forward<P>(policy), [&](ComponentType & component) {
473 return m_recursion.predict(component, inputs...);
474 });
475 }
476
480 template <typename... Inputs>
481 requires isFixedStep<Model_>
482 [[nodiscard]] auto
483 predict(Steps steps, Inputs const &... inputs)
484 {
485 return predict(execution::sequential, steps, inputs...);
486 }
487
491 template <ExecutionPolicy P, typename... Inputs>
492 requires isFixedStep<Model_>
493 [[nodiscard]] auto
494 predict(P && policy, Steps steps, Inputs const &... inputs)
495 {
496 EMRYS_FILTER_ASSERT(steps.count >= 0 && "the number of steps must be nonnegative");
497 using Result = decltype(predict(policy, inputs...));
498 if constexpr (std::is_void_v<Result>)
499 {
500 for (Eigen::Index i = 0; i < steps.count; ++i)
501 {
502 predict(policy, inputs...);
503 }
504 }
505 else
506 {
507 for (Eigen::Index i = 0; i < steps.count; ++i)
508 {
509 if (Result result = predict(policy, inputs...); !result)
510 {
511 return result;
512 }
513 }
514 return Result{};
515 }
516 }
517
525 template <typename... Inputs>
527 [[nodiscard]] JointType
528 predictJoint(TimeStep<Scalar> auto const & dt, Inputs const &... inputs)
529 {
530 auto joint = emptyJoint();
531 predictJoint(execution::sequential, dt, joint, inputs...);
532 return joint;
533 }
534
538 template <typename JointAllocator, typename... Inputs>
540 void
542 TimeStep<Scalar> auto const & dt,
544 Inputs const &... inputs
545 )
546 {
547 predictJoint(execution::sequential, dt, joint, inputs...);
548 }
549
553 template <ExecutionPolicy P, typename JointAllocator, typename... Inputs>
555 void
557 P && policy,
558 TimeStep<Scalar> auto const & dt,
560 Inputs const &... inputs
561 )
562 {
563 Scalar const step = internal::toScalarTime<Scalar>(dt);
564 recordJoint(std::forward<P>(policy), joint, [&](ComponentType & component) {
565 return m_recursion.predictJoint(component, step, inputs...);
566 });
567 }
568
574 template <typename... Inputs>
575 requires(
576 isFixedStep<Model_> && isGaussian<ComponentType> && !internal::leadsWithPolicy<Inputs...>
577 && !internal::leadsWithSteps<Inputs...>
578 )
579 [[nodiscard]] JointType
580 predictJoint(Inputs const &... inputs)
581 {
582 internal::rejectTimeStep<Inputs...>();
583 auto joint = emptyJoint();
584 predictJoint(execution::sequential, joint, inputs...);
585 return joint;
586 }
587
589 template <typename JointAllocator, typename... Inputs>
591 void
593 {
594 predictJoint(execution::sequential, joint, inputs...);
595 }
596
600 template <ExecutionPolicy P, typename JointAllocator, typename... Inputs>
602 void
604 P && policy,
606 Inputs const &... inputs
607 )
608 {
609 recordJoint(std::forward<P>(policy), joint, [&](ComponentType & component) {
610 return m_recursion.predictJoint(component, inputs...);
611 });
612 }
613
630 template <Measurement M, typename Policy = reductions::Collapse>
631 requires(
634 )
635 [[nodiscard]] UpdateType<M>
636 update(M const & measurement, Policy const & policy = {})
637 {
638 return update(execution::sequential, measurement, policy);
639 }
640
645 template <ExecutionPolicy P, Measurement M, typename Policy = reductions::Collapse>
646 requires(
648 || MixtureReductionPolicy<Policy, internal::MeasuredUpdate<ComponentType, M>, Component_>
649 )
650 [[nodiscard]] UpdateType<M>
651 update(P && executionPolicy, M const & measurement, Policy const & policy = {})
652 {
653 auto result = emptyResult<ResultType<M>>();
654 auto const updated = update(std::forward<P>(executionPolicy), measurement, policy, result);
655 if (!updated)
656 {
657 return std::unexpected{updated.error()};
658 }
659 return result;
660 }
661
668 template <Measurement M, typename Policy, typename ResultAllocator>
669 requires(
672 )
673 [[nodiscard]] std::expected<void, NumericError>
675 M const & measurement,
676 Policy const & policy,
677 MixtureFilterUpdate<typename ResultType<M>::ComponentUpdateType, Scalar, ResultAllocator> & result
678 )
679 {
680 return update(execution::sequential, measurement, policy, result);
681 }
682
686 template <ExecutionPolicy P, Measurement M, typename Policy, typename ResultAllocator>
687 requires(
690 )
691 [[nodiscard]] std::expected<void, NumericError>
693 P && executionPolicy,
694 M const & measurement,
695 Policy const & policy,
696 MixtureFilterUpdate<typename ResultType<M>::ComponentUpdateType, Scalar, ResultAllocator> & result
697 )
698 {
699 if constexpr (keepsBranches<M, Policy>())
700 {
701 return updateBranches(std::forward<P>(executionPolicy), measurement, policy, result);
702 }
703 else
704 {
705 // The reweighting reads each component's likelihood, so the recursion computes it
706 // from the gain's factorization (decisions.md [innovation-statistics]).
707 auto const reading = internal::WithStatistics<Policy>{policy};
708 return updateComponents(
709 std::forward<P>(executionPolicy),
710 result,
711 [&]<typename Update>(ComponentType & component, Update & slot) {
712 return m_recursion.update(component, measurement, reading, slot);
713 },
714 [&](ComponentType & component) {
715 return m_recursion.update(component, measurement, reading);
716 }
717 );
718 }
719 }
720
721private:
722
726 void
727 checkState()
728 {
729 EMRYS_FILTER_ASSERT(!m_state.empty() && "the initial mixture must have a component");
730 internal::assertPartitions(m_recursion.model(), m_state.space());
731 if (auto const capacity = m_state.capacity())
732 {
733 m_posterior.setCapacity(*capacity);
734 }
735 else
736 {
737 m_posterior.reserve(m_state.size());
738 }
739 }
740
742 template <typename P>
743 static constexpr void
744 assertRunsUnder()
745 {
746 static_assert(
747 internal::isSequential<P> || RecursionType::Reentrant,
748 "a recursion with scratch, such as the IMM's, runs the components in order: pass Sequential"
749 );
750 }
751
755 static RecursionType
756 makeRecursion(Model_ model, MethodFor<Model_, Method_> method, StateType const & state)
757 {
758 if constexpr (std::is_constructible_v<
759 RecursionType,
760 Model_,
762 decltype(componentAllocator(state))>)
763 {
764 return RecursionType{std::move(model), std::move(method), componentAllocator(state)};
765 }
766 else
767 {
768 return RecursionType{std::move(model), std::move(method)};
769 }
770 }
771
775 [[nodiscard]] static auto
776 componentAllocator(StateType const & state)
777 {
778 if constexpr (requires(ComponentType const & c) { c.get_allocator(); })
779 {
780 EMRYS_FILTER_ASSERT(!state.empty() && "the initial mixture must have a component");
781 return state.begin()->component.get_allocator();
782 }
783 else
784 {
785 return state.get_allocator();
786 }
787 }
788
790 [[nodiscard]] auto
791 componentAllocator() const
792 {
793 return componentAllocator(m_state);
794 }
795
801 template <Measurement M, typename Policy>
802 [[nodiscard]] static consteval bool
803 keepsBranches()
804 {
805 if constexpr (isGaussian<ComponentType>)
806 {
807 using Measured = typename decltype(internal::updateBranches<true>(
808 std::declval<ComponentType const &>(),
809 std::declval<M const &>()
810 ))::value_type;
811 return !internal::keepsOne<Policy, typename Measured::Container, ComponentType>;
812 }
813 else
814 {
815 return false;
816 }
817 }
818
826 template <ExecutionPolicy P, typename PredictOne>
827 [[nodiscard]] auto
828 predictComponents(P && policy, PredictOne const & predictOne)
829 {
830 using Moved = std::invoke_result_t<PredictOne const &, ComponentType &>;
831 if constexpr (std::is_void_v<Moved>)
832 {
833 m_state.forEach(std::forward<P>(policy), [&](auto & entry) {
834 entry.updateComponent(predictOne);
835 });
836 }
837 else
838 {
839 // The first error any component reports, written once under any policy.
840 auto failed = std::atomic_flag{};
841 auto error = typename Moved::error_type{};
842 m_state.forEach(std::forward<P>(policy), [&](auto & entry) {
843 Moved const moved = entry.updateComponent(predictOne);
844 if (!moved && !failed.test_and_set())
845 {
846 error = moved.error();
847 }
848 });
849 return failed.test() ? Moved{std::unexpected{error}} : Moved{};
850 }
851 }
852
854 [[nodiscard]] JointType
855 emptyJoint() const
856 {
857 return {
858 internal::bufferOf<Scalar>(get_allocator()),
859 internal::bufferOf<PredictedJoint<SpaceType, Scalar>>(get_allocator()),
860 typename JointType::LineageType(get_allocator())
861 };
862 }
863
868 template <ExecutionPolicy P, typename JointAllocator, typename PredictOne>
869 void
870 recordJoint(
871 P && policy,
873 PredictOne const & predictOne
874 )
875 {
876 assertRunsUnder<P>();
877 EMRYS_FILTER_ASSERT(!m_state.empty() && "the mixture must have a component");
878 std::size_t const count = std::size_t(m_state.size());
879 internal::MixtureLineageAccess::assign(joint.lineage, m_state.lineage());
880 joint.logWeights.clear();
881 for (auto const & entry : m_state)
882 {
883 joint.logWeights.push_back(entry.logWeight);
884 }
885 joint.joints.resize(count);
886 m_state.forEach(std::forward<P>(policy), [&](auto & entry) {
887 joint.joints[std::size_t(entry.index())] = entry.updateComponent([&](ComponentType & component) {
888 return predictOne(component);
889 });
890 });
891 m_state.startLineage();
892 }
893
895 template <typename Result>
896 [[nodiscard]] Result
897 emptyResult() const
898 {
899 auto result = Result{get_allocator()};
900 result.components.reserve(std::size_t(m_state.size()));
901 internal::UpdateAccess::posteriorEnds(result).reserve(std::size_t(m_state.size()));
902 return result;
903 }
904
909 template <typename Result>
910 [[nodiscard]] auto &
911 posteriorEndsFor(Result & result) const
912 {
913 auto & ends = internal::UpdateAccess::posteriorEnds(result);
914 if (auto const capacity = m_state.capacity())
915 {
916 ends.reserve(std::size_t(*capacity));
917 }
918 ends.resize(std::size_t(m_state.size()));
919 return ends;
920 }
921
925 template <typename Result>
926 void
927 keepPriorIndices(Result & result) const
928 {
929 auto & ends = internal::UpdateAccess::posteriorEnds(result);
930 std::iota(ends.begin(), ends.end(), Eigen::Index{1});
931 }
932
945 template <ExecutionPolicy P, typename Result, typename UpdateInto, typename UpdateNew>
946 [[nodiscard]] std::expected<void, NumericError>
947 updateComponents(P && executionPolicy, Result & result, UpdateInto const & updateInto, UpdateNew const & updateNew)
948 {
949 assertRunsUnder<P>();
950 EMRYS_FILTER_ASSERT(!m_state.empty() && "the mixture must have a component");
951 // Weights the caller left undefined fail the update before anything is written.
952 auto const logTotal = m_state.logTotalWeight();
953 if (!logTotal)
954 {
955 return std::unexpected{logTotal.error()};
956 }
957 Eigen::Index const count = m_state.size();
958 using Update = typename Result::ComponentUpdateType;
959
960 if constexpr (internal::isSequential<P>)
961 {
962 // The slots a result holds from the last update are reused, with the storage a
963 // recursion's result keeps; the components beyond them get new ones, from the filter's
964 // allocator.
965 Eigen::Index const held = std::min(Eigen::Index(result.components.size()), count);
966 result.components.erase(result.components.begin() + held, result.components.end());
967 auto failure = std::optional<GaussianError>{};
968 m_state.forEach(execution::sequential, [&](auto & entry) {
969 if (failure)
970 {
971 return;
972 }
973 auto const updated = [&]() -> std::expected<void, NumericError> {
974 if (entry.index() < held)
975 {
976 return entry.updateComponent([&](ComponentType & component) {
977 return updateInto(component, result.components[std::size_t(entry.index())]);
978 });
979 }
980 else if constexpr (internal::EmptyFromAllocator<Update, Allocator_>)
981 {
982 result.components.push_back(internal::emptyUpdate<Update>(get_allocator()));
983 return entry.updateComponent([&](ComponentType & component) {
984 return updateInto(component, result.components.back());
985 });
986 }
987 else
988 {
989 auto update = entry.updateComponent([&](ComponentType & component) {
990 return updateNew(component);
991 });
992 if (!update)
993 {
994 return std::unexpected{update.error()};
995 }
996 result.components.push_back(std::move(*update));
997 return {};
998 }
999 }();
1000 if (!updated)
1001 {
1002 failure = updated.error();
1003 }
1004 });
1005 if (failure)
1006 {
1007 return std::unexpected{*failure};
1008 }
1009 }
1010 else
1011 {
1012 using Updated = std::remove_cvref_t<decltype(updateNew(std::declval<ComponentType &>()))>;
1013 auto slots = internal::bufferOf<std::optional<Updated>>(get_allocator());
1014 slots.resize(std::size_t(count));
1015 m_state.forEach(std::forward<P>(executionPolicy), [&](auto & entry) {
1016 slots[std::size_t(entry.index())].emplace(entry.updateComponent([&](ComponentType & component) {
1017 return updateNew(component);
1018 }));
1019 });
1020 result.components.clear();
1021 for (auto & slot : slots)
1022 {
1023 if (!*slot)
1024 {
1025 return std::unexpected{slot->error()};
1026 }
1027 result.components.push_back(std::move(**slot));
1028 }
1029 }
1030
1031 // Component i's weight times its likelihood, and that of the branches its policy kept.
1032 auto const logWeightOf = [&](Eigen::Index i) {
1033 return m_state[i].logWeight - *logTotal + result.components[std::size_t(i)].logLikelihood();
1034 };
1035 auto const logKeptOf = [&](Eigen::Index i) {
1036 return logWeightOf(i) + logKeptWeightOf(result.components[std::size_t(i)]);
1037 };
1038 // Whether the measurement is accepted is known before a weight is written: some component
1039 // accepted it and keeps a nonzero weight (decisions.md [update-outcomes]).
1040 auto evidence = LogSumExp<Scalar>{};
1041 bool accepted = false;
1042 for (Eigen::Index i = 0; i < count; ++i)
1043 {
1044 evidence.add(logWeightOf(i));
1045 accepted =
1046 accepted
1047 || (result.components[std::size_t(i)].accepted && logKeptOf(i) > -Eigen::NumTraits<Scalar>::infinity());
1048 }
1049 internal::UpdateAccess::logLikelihood(result) = evidence.value();
1050 result.accepted = accepted;
1051 auto & ends = posteriorEndsFor(result);
1052 if (!accepted)
1053 {
1054 // Only components of weight zero accepted it, if any, so the mixture is as it was.
1055 for (auto & component : result.components)
1056 {
1057 component.accepted = false;
1058 }
1059 keepPriorIndices(result);
1060 return {};
1061 }
1062 // The accepted components keep their order, so prior i ends where the count of those up
1063 // to it does. The scan starts from an Index: without a start, libc++ sums in the
1064 // components' own type.
1065 std::transform_inclusive_scan(
1066 result.components.begin(),
1067 result.components.end(),
1068 ends.begin(),
1069 std::plus<>{},
1070 [](auto const & component) {
1071 return Eigen::Index(component.accepted);
1072 },
1073 Eigen::Index{0}
1074 );
1075 for (Eigen::Index i = 0; i < count; ++i)
1076 {
1077 if (result.components[std::size_t(i)].accepted)
1078 {
1079 m_state.setLogWeight(i, logKeptOf(i));
1080 }
1081 }
1082 m_state.removeIf([&](Eigen::Index i, auto const &) {
1083 return !result.components[std::size_t(i)].accepted;
1084 });
1085 [[maybe_unused]] auto const normalized = m_state.normalize();
1086 EMRYS_FILTER_ASSERT(normalized && "an accepted component keeps a nonzero weight");
1087 return {};
1088 }
1089
1099 template <ExecutionPolicy P, Measurement M, typename Policy, typename Result>
1100 [[nodiscard]] std::expected<void, NumericError>
1101 updateBranches(P && executionPolicy, M const & measurement, Policy const & policy, Result & result)
1102 {
1103 EMRYS_FILTER_ASSERT(!m_state.empty() && "the mixture must have a component");
1104 auto const logTotal = m_state.logTotalWeight();
1105 if (!logTotal)
1106 {
1107 return std::unexpected{logTotal.error()};
1108 }
1109 Eigen::Index const count = m_state.size();
1110 using Update = typename Result::ComponentUpdateType;
1111
1112 m_posterior.clear();
1113 internal::MixtureLineageAccess::startFrom(m_posterior, m_state);
1114 auto & ends = posteriorEndsFor(result);
1115 auto evidence = LogSumExp<Scalar>{};
1116 // Reduces component i's measured branches into the posterior, each kept one weighted by the
1117 // likelihood of the branches it stands for; the evidence counts them all.
1118 // \returns whether any was kept
1119 auto const reduce = [&](Eigen::Index i, auto & measured) {
1120 Scalar const logWeight = m_state[i].logWeight - *logTotal + measured.logLikelihood();
1121 evidence.add(logWeight);
1122 auto out = PosteriorOutput{*this, i, logWeight};
1123 internal::reduceInto(policy, std::move(measured.branches), out);
1124 // The components are reduced in order, each into the end of the posterior.
1125 ends[std::size_t(i)] = m_posterior.size();
1126 return out.added;
1127 };
1128 if constexpr (internal::isSequential<P>)
1129 {
1130 Eigen::Index const held = std::min(Eigen::Index(result.components.size()), count);
1131 result.components.erase(result.components.begin() + held, result.components.end());
1132 for (Eigen::Index i = 0; i < count; ++i)
1133 {
1134 // The reweighting reads each component's likelihood, so the update computes it with its gain.
1135 auto const updated = internal::updateSlot<true>(
1136 result.components,
1137 std::size_t(i),
1138 get_allocator(),
1139 m_state[i].component,
1140 measurement,
1141 [&](auto & measured) {
1142 return reduce(i, measured);
1143 }
1144 );
1145 if (!updated)
1146 {
1147 return updated;
1148 }
1149 }
1150 }
1151 else
1152 {
1153 using Measured =
1154 decltype(internal::updateBranches<true>(std::declval<ComponentType const &>(), measurement));
1155 auto measuredAll = internal::bufferOf<std::optional<Measured>>(get_allocator());
1156 measuredAll.resize(std::size_t(count));
1157 internal::forEachIndex(std::forward<P>(executionPolicy), count, [&](Eigen::Index i) {
1158 measuredAll[std::size_t(i)].emplace(internal::updateBranches<true>(m_state[i].component, measurement));
1159 });
1160 for (auto const & measured : measuredAll)
1161 {
1162 if (!*measured)
1163 {
1164 return std::unexpected{measured->error()};
1165 }
1166 }
1167 result.components.clear();
1168 for (Eigen::Index i = 0; i < count; ++i)
1169 {
1170 auto & measured = **measuredAll[std::size_t(i)];
1171 bool const accepted = reduce(i, measured);
1172 if constexpr (internal::EmptyFromAllocator<Update, Allocator_>)
1173 {
1174 result.components.push_back(internal::emptyUpdate<Update>(get_allocator()));
1175 internal::filterUpdateInto(measured, accepted, result.components.back());
1176 }
1177 else
1178 {
1179 result.components.push_back(internal::filterUpdate(std::move(measured), accepted));
1180 }
1181 }
1182 }
1183 internal::UpdateAccess::logLikelihood(result) = evidence.value();
1184 // The posterior is built apart, so a measurement that only components of weight zero accept
1185 // leaves the state as it was (decisions.md [update-outcomes]).
1186 result.accepted = !m_posterior.empty() && m_posterior.normalize().has_value();
1187 if (!result.accepted)
1188 {
1189 for (auto & component : result.components)
1190 {
1191 component.accepted = false;
1192 }
1193 keepPriorIndices(result);
1194 return {};
1195 }
1196 m_state.swap(m_posterior);
1197 return {};
1198 }
1199
1205 struct PosteriorOutput
1206 {
1207 using ComponentType = Params<Component_>;
1208 static constexpr bool HoldsSeveral = true;
1209
1210 MixtureFilter & filter;
1211 Eigen::Index prior;
1212 Scalar logWeight;
1213 bool added = false;
1214
1216 void
1217 add(Scalar weight, ComponentType && component)
1218 {
1219 filter.m_posterior.add(logWeight + weight, std::move(component));
1220 internal::MixtureLineageAccess::inherit(filter.m_posterior, filter.m_state, prior);
1221 added = true;
1222 }
1223
1225 void
1226 add(Scalar weight, ComponentType const & component)
1227 {
1228 add(weight, ComponentType(component));
1229 }
1230 };
1231
1236 template <typename Update>
1237 [[nodiscard]] static Scalar
1238 logKeptWeightOf(Update const & update)
1239 {
1240 if constexpr (internal::isFilterUpdate<Update>)
1241 {
1242 return Scalar(internal::MixtureEvidenceAccess::logKeptWeight(update));
1243 }
1244 else
1245 {
1246 return Scalar(0);
1247 }
1248 }
1249
1250 StateType m_state;
1251 RecursionType m_recursion;
1252 StateType m_posterior;
1253};
1254
1256template <Component C, typename Model, typename Method = DefaultMethodFor<Model>, typename Allocator>
1257 requires(!Recursion<Model> && !internal::UpkeepArgument<Method>)
1258MixtureFilter(Model, MixtureParams<C, Allocator>, Method = {}) -> MixtureFilter<
1259 C,
1260 internal::BoundStep<Model, typename Params<C>::SpaceType, typename Params<C>::Scalar>,
1261 Method,
1262 Allocator>;
1263
1264template <Component C, RecursionOver<C> R, typename Allocator>
1265MixtureFilter(R, MixtureParams<C, Allocator>)
1266 -> MixtureFilter<C, typename R::ModelType, typename R::MethodType, Allocator, R>;
1267
1268template <Component C, typename Model, typename Allocator, typename U>
1269 requires(!Recursion<Model> && internal::UpkeepArgument<U>)
1270MixtureFilter(Model, MixtureParams<C, Allocator>, U) -> MixtureFilter<
1271 C,
1272 internal::BoundStep<Model, typename Params<C>::SpaceType, typename Params<C>::Scalar>,
1273 DefaultMethodFor<Model>,
1274 Allocator,
1275 internal::RecursionForType<
1276 C,
1277 internal::BoundStep<Model, typename Params<C>::SpaceType, typename Params<C>::Scalar>,
1278 DefaultMethodFor<Model>,
1279 Allocator>,
1280 U>;
1281
1282template <Component C, typename Model, typename Method, typename Allocator, typename U>
1283 requires(!Recursion<Model>)
1284MixtureFilter(Model, MixtureParams<C, Allocator>, Method, U) -> MixtureFilter<
1285 C,
1286 internal::BoundStep<Model, typename Params<C>::SpaceType, typename Params<C>::Scalar>,
1287 Method,
1288 Allocator,
1289 internal::RecursionForType<
1290 C,
1291 internal::BoundStep<Model, typename Params<C>::SpaceType, typename Params<C>::Scalar>,
1292 Method,
1293 Allocator>,
1294 U>;
1295
1296template <Component C, RecursionOver<C> R, typename Allocator, typename U>
1297 requires internal::UpkeepArgument<U>
1298MixtureFilter(R, MixtureParams<C, Allocator>, U)
1299 -> MixtureFilter<C, typename R::ModelType, typename R::MethodType, Allocator, R, U>;
1300
1302
1321template <
1322 Space Space_,
1323 typename Model_,
1324 typename Method_ = DefaultMethodFor<Model_>,
1325 RealScalar Scalar_ = double,
1326 typename Allocator_ = DefaultAllocator,
1327 typename Upkeep_ = upkeep::None>
1329 : public MixtureFilter<
1330 Gaussian<Space_, Scalar_>,
1331 Model_,
1332 Method_,
1333 Allocator_,
1334 internal::RecursionForType<Gaussian<Space_, Scalar_>, Model_, Method_, Allocator_>,
1335 Upkeep_>
1336{
1337public:
1338
1342 Model_,
1343 Method_,
1344 Allocator_,
1345 internal::RecursionForType<Gaussian<Space_, Scalar_>, Model_, Method_, Allocator_>,
1346 Upkeep_>;
1347
1352 template <typename... Args>
1353 requires(
1354 std::constructible_from<Base, Args...>
1355 && !(sizeof...(Args) == 1 && (std::is_same_v<std::remove_cvref_t<Args>, GaussianMixtureFilter> && ...))
1356 )
1357 GaussianMixtureFilter(Args &&... args)
1358 : Base(std::forward<Args>(args)...)
1359 {}
1360};
1361
1363// A guide with a defaulted method, its parameters in the class's order, and two with an upkeep.
1364template <
1365 typename Space,
1366 typename Model,
1367 typename Method = DefaultMethodFor<Model>,
1368 typename Scalar,
1369 typename Allocator>
1370 requires(!internal::UpkeepArgument<Method>)
1371GaussianMixtureFilter(Model, MixtureParams<Gaussian<Space, Scalar>, Allocator>, Method = {})
1372 -> GaussianMixtureFilter<Space, internal::BoundStep<Model, Space, Scalar>, Method, Scalar, Allocator>;
1373
1374template <typename Space, typename Model, typename Scalar, typename Allocator, typename U>
1375 requires internal::UpkeepArgument<U>
1376GaussianMixtureFilter(Model, MixtureParams<Gaussian<Space, Scalar>, Allocator>, U) -> GaussianMixtureFilter<
1377 Space,
1378 internal::BoundStep<Model, Space, Scalar>,
1379 DefaultMethodFor<Model>,
1380 Scalar,
1381 Allocator,
1382 U>;
1383
1384template <typename Space, typename Model, typename Method, typename Scalar, typename Allocator, typename U>
1385GaussianMixtureFilter(Model, MixtureParams<Gaussian<Space, Scalar>, Allocator>, Method, U)
1386 -> GaussianMixtureFilter<Space, internal::BoundStep<Model, Space, Scalar>, Method, Scalar, Allocator, U>;
1388
1389} // namespace Emrys
1390
1391#ifndef EMRYS_FILTER_NO_IO
1392
1393namespace Emrys
1394{
1395
1396namespace internal
1397{
1398
1400template <
1401 typename Component_,
1402 typename Model_,
1403 typename Method_,
1404 typename Allocator_,
1405 typename Recursion_,
1406 typename Upkeep_>
1407void
1408describe(
1409 std::ostream & stream,
1410 MixtureFilter<Component_, Model_, Method_, Allocator_, Recursion_, Upkeep_> const & filter,
1411 Indent indent
1412)
1413{
1414 describeFilter(stream, "mixture filter", filter.state(), indent);
1415}
1416
1418template <typename Space_, typename Model_, typename Method_, typename Scalar_, typename Allocator_, typename Upkeep_>
1419void
1420describe(
1421 std::ostream & stream,
1422 GaussianMixtureFilter<Space_, Model_, Method_, Scalar_, Allocator_, Upkeep_> const & filter,
1423 Indent indent
1424)
1425{
1426 describeFilter(stream, "Gaussian mixture filter", filter.state(), indent);
1427}
1428
1429} // namespace internal
1430
1431} // namespace Emrys
1432
1433#endif // EMRYS_FILTER_NO_IO
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
What a container of several Gaussians needs of each one (decisions.md [component-first]): the Compone...
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The upkeep a filter holds (decisions.md [upkeep]): internal::HeldUpkeep, the base that gives every fi...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
ImmRecursion and ImmFilter (decisions.md [switching-models], [imm], [filters-and-recursions]): a Hybr...
KalmanRecursion and KalmanFilter (decisions.md [templated-measurements] and [filters-and-recursions])...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The update of a Gaussian by a measurement (decisions.md [templated-measurements], [weighted-branches]...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
Printing: operator<< and std::formatter for every distribution, every filter (which prints its name a...
Process models as functions with reads and writes (decisions.md [model-policies], [continuous-time-no...
The Recursion concept (decisions.md [filters-and-recursions], [user-extension-points]): what a Kalman...
Reducing an update's branches (decisions.md [weighted-branches], [reduction-policies],...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Propagating a distribution through a function (decisions.md [method-per-measurement],...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
MixtureFilter< Gaussian< Space_, Scalar_ >, Model_, Method_, Allocator_, internal::RecursionForType< Gaussian< Space_, Scalar_ >, Model_, Method_, Allocator_ >, Upkeep_ > Base
the filter this is
Definition MixtureFilter.h:1340
GaussianMixtureFilter(Args &&... args)
The filter MixtureFilter's constructor makes from args.
Definition MixtureFilter.h:1357
The density of a Gaussian whose covariance has been factorized: a read-only view.
Definition Gaussian.h:1011
Streaming log-sum-exp: accumulates without overflow.
Definition Weights.h:46
Scalar value() const
Definition Weights.h:133
MixtureFilter(Model_ model, StateType state, std::type_identity_t< Upkeep_ > upkeep)
A filter with process model model, initial state state, the default prediction method,...
Definition MixtureFilter.h:319
auto predict(Inputs const &... inputs)
Moves each component forward by the fixed-step model's step, with inputs passed as in Emrys::predict(...
Definition MixtureFilter.h:457
Method_ const & method() const
Definition MixtureFilter.h:413
Model_ & model()
Definition MixtureFilter.h:406
RecursionType const & recursion() const
Definition MixtureFilter.h:392
JointType predictJoint(Inputs const &... inputs)
Moves each Gaussian component forward as predict(inputs...) does with a fixed-step model,...
Definition MixtureFilter.h:580
StateType & state()
Definition MixtureFilter.h:358
std::expected< ResultType< M >, NumericError > UpdateType
What update() with M returns: the ResultType in a std::expected (decisions.md [innovation-cholesky]).
Definition MixtureFilter.h:296
typename Params< Component_ >::SpaceType SpaceType
the state space
Definition MixtureFilter.h:272
Recursion_ RecursionType
the steps each component runs (see Recursion)
Definition MixtureFilter.h:280
void predictJoint(P &&policy, GaussianMixturePredictedJoint< SpaceType, Scalar, JointAllocator > &joint, Inputs const &... inputs)
predictJoint(joint, inputs...) under policy, as predictJoint(policy, dt, joint, inputs....
Definition MixtureFilter.h:603
typename Params< Component_ >::Scalar Scalar
the scalar type
Definition MixtureFilter.h:271
void predictJoint(GaussianMixturePredictedJoint< SpaceType, Scalar, JointAllocator > &joint, Inputs const &... inputs)
As predictJoint(inputs...), but into joint, whose storage is reused.
Definition MixtureFilter.h:592
UpdateType< M > update(M const &measurement, Policy const &policy={})
Conditions each component on measurement, by the measurement's own method, reduces its branches with ...
Definition MixtureFilter.h:636
Allocator_ allocator_type
the state's allocator
Definition MixtureFilter.h:277
std::expected< void, NumericError > update(M const &measurement, Policy const &policy, MixtureFilterUpdate< typename ResultType< M >::ComponentUpdateType, Scalar, ResultAllocator > &result)
As update(measurement, policy), but into result, whose storage is reused: with the same result at eve...
Definition MixtureFilter.h:674
StateType const & state() const
Definition MixtureFilter.h:347
auto predict(TimeStep< Scalar > auto const &dt, Inputs const &... inputs)
Moves each component forward by dt, any TimeStep (see Time steps), through the process model,...
Definition MixtureFilter.h:429
void predictJoint(P &&policy, TimeStep< Scalar > auto const &dt, GaussianMixturePredictedJoint< SpaceType, Scalar, JointAllocator > &joint, Inputs const &... inputs)
predictJoint(dt, joint, inputs...) under policy, a for-each over the components into the joint's slot...
Definition MixtureFilter.h:556
auto predict(Steps steps, Inputs const &... inputs)
Moves each component forward by steps of the fixed-step model, as that many calls of predict(inputs....
Definition MixtureFilter.h:483
UpdateType< M > update(P &&executionPolicy, M const &measurement, Policy const &policy={})
update(measurement, policy) under executionPolicy: each component's update is a for-each into slots m...
Definition MixtureFilter.h:651
Model_ const & model() const
Definition MixtureFilter.h:399
Model_ ModelType
the process model
Definition MixtureFilter.h:273
auto predict(P &&policy, Inputs const &... inputs)
predict(inputs...) under policy, as predict(policy, dt, inputs...) runs.
Definition MixtureFilter.h:467
JointType predictJoint(TimeStep< Scalar > auto const &dt, Inputs const &... inputs)
Moves each Gaussian component forward as predict(dt, inputs...) does, and starts the state's lineage ...
Definition MixtureFilter.h:528
auto predict(P &&policy, Steps steps, Inputs const &... inputs)
predict(steps, inputs...) under policy, as that many calls of predict(policy, inputs....
Definition MixtureFilter.h:494
MixtureFilter(Recursion_ recursion, StateType state, std::type_identity_t< Upkeep_ > upkeep={})
A filter that runs recursion, the library's or one of the user's own (see Recursion),...
Definition MixtureFilter.h:329
MixtureFilter(Model_ model, StateType state, MethodFor< Model_, Method_ > method={}, std::type_identity_t< Upkeep_ > upkeep={})
A filter with process model model, initial state state, prediction method method, which a fixed-step ...
Definition MixtureFilter.h:302
auto predict(P &&policy, TimeStep< Scalar > auto const &dt, Inputs const &... inputs)
predict(dt, inputs...) under policy, a for-each over the components, so the result is Sequential's (s...
Definition MixtureFilter.h:441
void setState(StateType state)
Replaces the state estimate, as when reinitializing, with one that has at least one component and who...
Definition MixtureFilter.h:370
Method_ MethodType
the prediction method
Definition MixtureFilter.h:274
allocator_type get_allocator() const
Definition MixtureFilter.h:340
MixtureParams< Component_, Allocator_ > StateType
the state the filter owns
Definition MixtureFilter.h:275
GaussianMixturePredictedJoint< SpaceType, Scalar, Allocator_ > JointType
what predictJoint() returns, for Gaussian components
Definition MixtureFilter.h:283
void predictJoint(TimeStep< Scalar > auto const &dt, GaussianMixturePredictedJoint< SpaceType, Scalar, JointAllocator > &joint, Inputs const &... inputs)
As predictJoint(dt, inputs...), but into joint, whose storage is reused: with the same joint at every...
Definition MixtureFilter.h:541
std::expected< void, NumericError > update(P &&executionPolicy, M const &measurement, Policy const &policy, MixtureFilterUpdate< typename ResultType< M >::ComponentUpdateType, Scalar, ResultAllocator > &result)
update(measurement, policy, result) under executionPolicy, as update(executionPolicy,...
Definition MixtureFilter.h:692
Params< Component_ > ComponentType
one component of the state
Definition MixtureFilter.h:276
MixtureFilterUpdate< internal::RebindUpdateAllocator< typename RecursionType::template UpdateType< M >::value_type, Allocator_ >, Scalar, Allocator_ > ResultType
What update() with M fills when given a result: one component's update per component,...
Definition MixtureFilter.h:289
How each component of a Mixture descends from the components it had at its last startLineage(): each ...
Definition Mixture.h:127
A weighted sum of components of one type, all in one space.
Definition Mixture.h:579
The distribution that a container of several holds one of per element (decisions.md [component-first]...
Definition Component.h:299
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A reduction policy a MixtureFilter over Component_ takes for Update: one that reduces the branches in...
Definition Reduction.h:643
A Recursion whose steps act on the parameters of the component Component_, the constraint a filter ov...
Definition Recursion.h:87
A step of time that converts to Scalar: Scalar itself, or any type with a TimeTraits,...
Definition Time.h:128
std::allocator< std::byte > DefaultAllocator
The allocator a type with a run-time count uses unless given one.
Definition Allocation.h:47
NumericError
Reasons a computation from parameters the caller gave can fail: a matrix that does not factorize or a...
Definition NumericError.h:30
constexpr bool isGaussian
Whether C is a Gaussian in covariance form: the component for which a container offers the convenienc...
Definition Component.h:111
typename ParamsOf< D >::type Params
The parameters of the density D: GaussianParams for a Gaussian, and D itself for a distribution whose...
Definition Params.h:42
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
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
std::expected< CategoricalUpdate< Space, Scalar >, WeightError > update(CategoricalParams< Space, Scalar > const &prior, E const &emission)
Definition Emission.h:97
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
The upkeep operations, each made by its factory (decisions.md [policy-factories], [upkeep]): the acti...
Definition Upkeep.h:380
One prediction of a mixture of Gaussians as its smoother needs it: the weights the step started from,...
Definition MixtureFilter.h:146
internal::Buffer< Scalar_, Allocator_ > LogWeightsType
the components' log-weights
Definition MixtureFilter.h:151
Scalar_ Scalar
the scalar type
Definition MixtureFilter.h:147
PredictedJoint< Space_, Scalar_ > JointType
one component's joint
Definition MixtureFilter.h:149
LogWeightsType logWeights
Definition MixtureFilter.h:154
internal::Buffer< JointType, Allocator_ > JointsType
the components' joints
Definition MixtureFilter.h:150
Space_ SpaceType
the state space
Definition MixtureFilter.h:148
MixtureLineage< Scalar_, Allocator_ > LineageType
the components' descent
Definition MixtureFilter.h:152
What a mixture filter's update did: each prior component's update before reduction,...
Definition MixtureFilter.h:71
MixtureFilterUpdate(Allocator const &allocator)
An empty result whose components come from allocator.
Definition MixtureFilter.h:78
std::ranges::iota_view< Eigen::Index, Eigen::Index > posteriorIndices(Eigen::Index i) const
Definition MixtureFilter.h:106
ComponentUpdate ComponentUpdateType
one component's update
Definition MixtureFilter.h:72
internal::Buffer< ComponentUpdate, Allocator > ComponentsType
the components' container
Definition MixtureFilter.h:73
MixtureFilterUpdate()=default
an empty result, from a default-constructed allocator
bool accepted
whether any component accepted the measurement
Definition MixtureFilter.h:114
Allocator get_allocator() const
Definition MixtureFilter.h:85
ComponentsType components
one per prior component, in order
Definition MixtureFilter.h:113
Scalar logLikelihood() const
Definition MixtureFilter.h:92
One prediction step as a joint: the Gaussian the step started from, the Gaussian it predicted,...
Definition ProcessModel.h:2817
A count of a fixed-step model's steps, for predict(Steps{n}).
Definition Time.h:136
Eigen::Index count
the number of steps, at least zero
Definition Time.h:137
The upkeep a filter holds when it is given none: it takes any state, changes nothing,...
Definition Upkeep.h:889