45inline constexpr bool isUpdateBranch =
false;
48template <
typename Space,
typename MeasurementSpace,
typename Scalar>
49inline constexpr bool isUpdateBranch<UpdateBranch<Space, MeasurementSpace, Scalar>> =
true;
57concept BranchRange = std::ranges::random_access_range<R> && std::ranges::sized_range<R>
58 && isUpdateBranch<std::remove_cvref_t<std::ranges::range_reference_t<R>>>;
62using BranchOf = std::remove_cvref_t<std::ranges::range_reference_t<R>>;
66using BranchScalarOf =
decltype(BranchOf<R>::logWeight);
70using PosteriorOf =
decltype(BranchOf<R>::posterior);
73template <BranchRange R>
74[[nodiscard]] BranchScalarOf<R>
75logTotalWeight(R
const & branches)
77 auto total = LogSumExp<BranchScalarOf<R>>{};
78 for (
auto const & branch : branches)
80 total.add(branch.logWeight);
88template <
typename Out>
89inline constexpr bool holdsSeveral =
false;
92template <
typename Out>
93 requires requires { Out::HoldsSeveral; }
94inline constexpr bool holdsSeveral<Out> = Out::HoldsSeveral;
104template <
typename Component,
typename Scalar,
typename Target = Component>
105struct OneComponentOutput
108 using ComponentType = Component;
111 Scalar logWeight = 0;
116 add(Scalar weight, Component && component)
118 EMRYS_FILTER_ASSERT(!added &&
"this filter keeps one component per update, so a policy adds at most one");
119 target = std::move(component);
126 add(Scalar weight, Component
const & component)
128 add(weight, Component(component));
135template <
typename Component,
typename Scalar>
136struct SeveralComponentOutput
139 using ComponentType = Component;
141 static constexpr bool HoldsSeveral =
true;
145 add(Scalar, Component &&)
150 add(Scalar, Component
const &)
157template <
typename P,
typename Branches,
typename Component>
158concept ReturnsComponent =
requires(P
const & policy, Branches && branches) {
159 { policy(
static_cast<Branches &&
>(branches)) } -> std::same_as<std::optional<Component>>;
165template <
typename P,
typename Branches,
typename Out>
167 std::invocable<P const &, Branches, Out &> || ReturnsComponent<P, Branches, typename Out::ComponentType>;
172template <
typename P, BranchRange Branches,
typename Out>
173 requires ReducesInto<P, Branches, Out>
175reduceInto(P
const & policy, Branches && branches, Out & out)
177 if constexpr (std::invocable<P const &, Branches, Out &>)
179 policy(std::forward<Branches>(branches), out);
183 auto const logTotal = logTotalWeight(branches);
184 if (
auto reduced = policy(std::forward<Branches>(branches)))
186 out.add(logTotal, std::move(*reduced));
194template <
typename Iterator>
200 [[nodiscard]]
decltype(
auto)
201 operator()(std::size_t i)
const
203 return first[std::iter_difference_t<Iterator>(i)];
211using PassingBranches =
212 ForwardedLike<R, std::ranges::transform_view<std::span<std::size_t const>, BranchAt<std::ranges::iterator_t<R>>>>;
245 template <
typename Branches>
246 requires internal::BranchRange<Branches>
247 [[nodiscard]] std::optional<internal::PosteriorOf<Branches>>
250 using Posterior = internal::PosteriorOf<Branches>;
251 using Scalar = internal::BranchScalarOf<Branches>;
252 using MomentsType =
typename Posterior::MomentsType;
253 EMRYS_FILTER_ASSERT(!std::ranges::empty(branches) &&
"an update has at least one branch");
254 auto const at = std::ranges::begin(branches);
255 auto && first =
at[0].posterior;
256 Eigen::Index
const count = Eigen::Index(std::ranges::size(branches));
259 return internal::forwardLike<Branches>(first);
262 Scalar
const logTotal = internal::logTotalWeight(branches);
263 EMRYS_FILTER_ASSERT(
isFinite(logTotal) &&
"the branches' weights must be valid");
264 auto const branch = [&](Eigen::Index i) ->
auto const & {
269 internal::mixtureMomentsIn<MomentsType>(
272 [&](Eigen::Index i) ->
decltype(
auto) {
273 return branch(i).posterior.mean();
275 [&](Eigen::Index i) ->
decltype(
auto) {
276 return branch(i).posterior.covarianceLower();
278 [&](Eigen::Index i) {
279 return branch(i).logWeight;
290[[nodiscard]]
constexpr Collapse
310 template <
typename Branches>
311 requires internal::BranchRange<Branches>
312 [[nodiscard]] std::optional<internal::PosteriorOf<Branches>>
315 EMRYS_FILTER_ASSERT(!std::ranges::empty(branches) &&
"an update has at least one branch");
316 auto const at = std::ranges::begin(branches);
317 std::ptrdiff_t
const count = std::ptrdiff_t(std::ranges::size(branches));
318 std::ptrdiff_t best = 0;
319 for (std::ptrdiff_t i = 1; i < count; ++i)
321 if (
at[i].logWeight >
at[best].logWeight)
326 return internal::forwardLike<Branches>(
at[best].posterior);
333[[nodiscard]]
constexpr Select
355 template <
typename Branches,
typename Out>
356 requires internal::BranchRange<Branches> && internal::holdsSeveral<Out>
360 for (
auto && branch : branches)
362 out.add(branch.logWeight, internal::forwardLike<Branches>(branch.posterior));
370[[nodiscard]]
constexpr Keep
384template <RealScalar Scalar =
double>
391template <
typename Value>
392 requires std::is_arithmetic_v<Value>
437template <
typename Inner = Collapse, RealScalar Scalar_ =
double>
447 : m_probability{probability}
448 , m_inner{std::move(
inner)}
450 EMRYS_FILTER_ASSERT(probability >
Scalar(0) && probability <
Scalar(1) &&
"the probability must be in (0, 1)");
456 , m_inner{std::move(
inner)}
458 EMRYS_FILTER_ASSERT(
threshold.value >=
Scalar(0) &&
"the threshold must be nonnegative");
464 template <RealScalar Result = Scalar>
472 return Result(m_threshold);
476 [[nodiscard]] Inner
const &
488 template <
typename Branches,
typename Out>
489 requires internal::BranchRange<Branches> && internal::ReducesInto<Inner, Branches, Out>
490 && internal::ReducesInto<Inner, internal::PassingBranches<Branches>, Out>
494 using UpdateScalar = internal::BranchScalarOf<Branches>;
495 EMRYS_FILTER_ASSERT(!std::ranges::empty(branches) &&
"an update has at least one branch");
496 auto const at = std::ranges::begin(branches);
497 std::size_t
const count = std::size_t(std::ranges::size(branches));
499 std::size_t passed = 0;
500 for (
auto const & branch : branches)
502 passed += branch.nis() <= limit ? 1 : 0;
510 internal::reduceInto(m_inner, std::forward<Branches>(branches), out);
514 auto const reducePassing = [&](
auto & indices) {
516 for (std::size_t k = 0; k < count; ++k)
518 if (
at[std::ptrdiff_t(k)].nis() <= limit)
523 auto passing = std::ranges::transform_view{
524 std::span<std::size_t const>{indices.data(), passed},
525 internal::BranchAt<std::ranges::iterator_t<Branches>>{
at}
527 internal::reduceInto(m_inner, internal::forwardLike<Branches>(passing), out);
529 using B = std::remove_cvref_t<Branches>;
530 if constexpr (
requires { std::tuple_size<B>::value; })
532 auto indices = std::array<std::size_t, std::tuple_size_v<B>>{};
533 reducePassing(indices);
537 auto indices = std::array<std::size_t, InlinePassing>{};
538 reducePassing(indices);
542 auto indices = internal::bufferOf<std::size_t>(allocatorOf(branches));
543 indices.resize(passed);
544 reducePassing(indices);
554 template <
typename Branches>
555 [[nodiscard]]
static auto
556 allocatorOf(Branches
const & branches)
558 if constexpr (
requires { branches.get_allocator(); })
560 return branches.get_allocator();
568 std::optional<Scalar> m_probability;
579template <
typename Value,
typename Inner = Collapse>
580 requires std::is_arithmetic_v<Value>
581[[nodiscard]] Gate<Inner, internal::DeducedScalar<Value>>
582gate(Value probability, Inner inner = {})
584 using Scalar = internal::DeducedScalar<Value>;
585 return Gate<Inner, Scalar>{Scalar(probability), std::move(inner)};
592template <RealScalar Scalar,
typename Inner = Collapse>
596 return Gate<Inner, Scalar>{threshold, std::move(inner)};
631template <
typename P,
typename Update,
typename State>
634 typename std::remove_cvref_t<Update>::Container,
635 internal::OneComponentOutput<State, typename std::remove_cvref_t<Update>::Scalar>>;
642template <
typename P,
typename Update,
typename Component_>
645 typename std::remove_cvref_t<Update>::Container,
646 internal::SeveralComponentOutput<Params<Component_>,
typename std::remove_cvref_t<Update>::Scalar>>;
654template <
typename State,
typename Policy,
typename Branches>
655 requires internal::BranchRange<Branches>
656 && internal::ReducesInto<
659 internal::OneComponentOutput<State, internal::BranchScalarOf<Branches>, std::optional<State>>>
660[[nodiscard]] std::optional<State>
663 auto kept = std::optional<State>{};
664 auto out = internal::OneComponentOutput<State, internal::BranchScalarOf<Branches>, std::optional<State>>{kept};
665 internal::reduceInto(policy, std::forward<Branches>(branches), out);
676template <
typename Policy,
typename Branches,
typename Component>
677inline constexpr bool keepsOne = ReturnsComponent<Policy, Branches, Component>;
680template <
typename Inner,
typename Scalar,
typename Branches,
typename Component>
681inline constexpr bool keepsOne<reductions::Gate<Inner, Scalar>, Branches, Component> =
682 keepsOne<Inner, Branches, Component>;
690template <
typename Policy>
691inline constexpr bool policyReadsStatistics =
true;
695inline constexpr bool policyReadsStatistics<reductions::Collapse> =
false;
698inline constexpr bool policyReadsStatistics<reductions::Select> =
false;
701inline constexpr bool policyReadsStatistics<reductions::Keep> =
false;
710template <
typename Policy>
713 Policy
const & policy;
716 template <
typename Branches,
typename Out>
717 requires BranchRange<Branches> && ReducesInto<Policy, Branches, Out>
719 operator()(Branches && branches, Out & out)
const
721 reduceInto(policy, std::forward<Branches>(branches), out);
726template <
typename Policy>
727inline constexpr bool policyReadsStatistics<WithStatistics<Policy>> =
true;
734template <
typename Policy>
735inline constexpr bool isCollapsePolicy =
false;
739inline constexpr bool isCollapsePolicy<reductions::Collapse> =
true;
741template <
typename Policy>
742inline constexpr bool isCollapsePolicy<WithStatistics<Policy>> = isCollapsePolicy<Policy>;
751template <
typename Policy>
752inline constexpr bool isOneBranchInPlacePolicy =
false;
756inline constexpr bool isOneBranchInPlacePolicy<reductions::Collapse> =
true;
759inline constexpr bool isOneBranchInPlacePolicy<reductions::Select> =
true;
761template <
typename Scalar>
762inline constexpr bool isOneBranchInPlacePolicy<reductions::Gate<reductions::Collapse, Scalar>> =
true;
764template <
typename Scalar>
765inline constexpr bool isOneBranchInPlacePolicy<reductions::Gate<reductions::Select, Scalar>> =
true;
767template <
typename Policy>
768inline constexpr bool isOneBranchInPlacePolicy<WithStatistics<Policy>> = isOneBranchInPlacePolicy<Policy>;
776template <
typename Gate>
782 template <
typename Scalar>
784 operator()(Scalar nis, Eigen::Index dimension)
const
786 return nis <= gate.template threshold<Scalar>(dimension);
794template <
typename Policy>
796oneBranchAcceptor(Policy
const & policy)
798 static_assert(isOneBranchInPlacePolicy<Policy>);
799 if constexpr (
requires { policy.policy; })
801 return oneBranchAcceptor(policy.policy);
803 else if constexpr (
requires { policy.inner(); })
805 return AcceptByGate<Policy>{policy};
809 return AcceptEvery{};
816template <
typename Scalar,
typename Space,
typename MeasurementSpace, std::
size_t Count,
typename Allocator>
817[[nodiscard]] FilterUpdate<MeasurementSpace, Count, Scalar, Allocator>
818filterUpdate(MeasurementUpdate<Space, MeasurementSpace, Count, Scalar, Allocator> && measured,
bool accepted)
820 using Result = FilterUpdate<MeasurementSpace, Count, Scalar, Allocator>;
821 using Branch =
typename Result::BranchType;
822 auto const strip = [](
auto & branch) {
825 std::move(branch.innovation),
832 if constexpr (Count == std::dynamic_extent)
834 auto result = Result{
835 typename Result::Container(measured.get_allocator()),
836 MixtureEvidenceAccess::mixtureLogLikelihood(measured),
839 result.branches.reserve(measured.branches.size());
840 for (
auto & branch : measured.branches)
842 result.branches.push_back(strip(branch));
848 return [&]<std::size_t... I>(std::index_sequence<I...>) {
850 {strip(measured.branches[I])...},
851 MixtureEvidenceAccess::mixtureLogLikelihood(measured),
854 }(std::make_index_sequence<Count>{});
865 typename MeasurementSpace,
867 typename MeasuredAllocator,
868 typename ResultAllocator>
871 MeasurementUpdate<Space, MeasurementSpace, Count, Scalar, MeasuredAllocator> & measured,
873 FilterUpdate<MeasurementSpace, Count, Scalar, ResultAllocator> & result
876 if constexpr (Count == std::dynamic_extent)
879 result.branches.clear();
880 result.branches.reserve(measured.branches.size());
881 for (
auto & branch : measured.branches)
883 result.branches.push_back(
886 std::move(branch.innovation),
894 MixtureEvidenceAccess::setMixtureLogLikelihood(result, MixtureEvidenceAccess::mixtureLogLikelihood(measured));
895 MixtureEvidenceAccess::setLogKeptWeight(result, Scalar(0));
896 result.accepted = accepted;
900 result = filterUpdate(std::move(measured), accepted);
913template <
bool Statistics,
typename Slots,
typename Allocator, UncertainInput Prior, Measurement M,
typename Reduce>
914[[nodiscard]] std::expected<void, GaussianError>
918 Allocator
const & allocator,
920 M
const & measurement,
921 Reduce
const & reduce
924 using Update =
typename Slots::value_type;
925 EMRYS_FILTER_ASSERT(i <= slots.size() &&
"the slots before this one must exist");
926 if constexpr (
requires(Update &
update) {
update.measured; })
928 if (i == slots.size())
930 slots.push_back(Update(allocator));
932 auto & slot = slots[i];
933 auto const measured = updateBranchesInto<Statistics>(prior, measurement, slot.measured);
938 bool const accepted = reduce(slot.measured);
939 filterUpdateInto(slot.measured, accepted, slot);
943 auto measured = updateBranches<Statistics>(prior, measurement);
946 return std::unexpected{measured.error()};
948 bool const accepted = reduce(*measured);
949 auto update = filterUpdate(std::move(*measured), accepted);
950 if (i == slots.size())
952 slots.push_back(std::move(
update));
956 slots[i] = std::move(
update);
972 UncertainInput Prior,
973 GivenMeasurementLike G,
975[[nodiscard]] std::expected<void, GaussianError>
979 Allocator
const & allocator,
982 Reduce
const & reduce
985 using Update =
typename Slots::value_type;
986 EMRYS_FILTER_ASSERT(i <= slots.size() &&
"the slots before this one must exist");
987 auto measured = updateBranchesWith<Statistics>(prior,
given.pinned,
given.value);
990 return std::unexpected{measured.error()};
992 bool const accepted = reduce(*measured);
993 if constexpr (
requires(Update &
update) {
update.measured; })
995 if (i == slots.size())
997 slots.push_back(Update(allocator));
999 filterUpdateInto(*measured, accepted, slots[i]);
1003 auto update = filterUpdate(std::move(*measured), accepted);
1004 if (i == slots.size())
1006 slots.push_back(std::move(
update));
1010 slots[i] = std::move(
update);
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Consistency checks (decisions.md [composed-features]): NisMonitor, a windowed chi-squared test on the...
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,...
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....
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Weighted statistics over points, all in internal, which the containers of weighted elements (Gaussian...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
Reduction policy that rejects branches by a chi-squared test on their normalized innovation squared,...
Definition Reduction.h:439
static constexpr std::size_t InlinePassing
The most branches of a run-time number whose passing indices a gate holds on the stack.
Definition Reduction.h:549
Result threshold(Eigen::Index dimension) const
Definition Reduction.h:466
Gate(NisThreshold< Scalar > threshold, Inner inner={})
Passes branches whose NIS is at most threshold.
Definition Reduction.h:454
Inner const & inner() const
Definition Reduction.h:477
void operator()(Branches &&branches, Out &out) const
Reduces the branches of branches that pass the gate into out by Inner, or adds nothing when none pass...
Definition Reduction.h:492
Scalar_ Scalar
The type of the probability or threshold it holds.
Definition Reduction.h:443
Gate(Scalar probability, Inner inner={})
Passes branches whose NIS is within the probability chi-squared quantile.
Definition Reduction.h:446
A reduction policy a MixtureFilter over Component_ takes for Update: one that reduces the branches in...
Definition Reduction.h:643
A reduction policy for a filter that keeps a State per update: it reduces an update's branches to at ...
Definition Reduction.h:632
std::allocator< std::byte > DefaultAllocator
The allocator a type with a run-time count uses unless given one.
Definition Allocation.h:47
Scalar chiSquaredQuantile(std::type_identity_t< Scalar > const &probability, std::type_identity_t< Scalar > const &dof)
Definition ChiSquared.h:148
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
bool isFinite(T const &x)
Definition Math.h:305
constexpr AxisIndex< Path, sizeof...(I)> at(I... indices)
Definition Discrete.h:1071
constexpr Select select()
Definition Reduction.h:334
std::optional< State > reduceToOne(Policy const &policy, Branches &&branches)
Definition Reduction.h:661
Gate< Inner, internal::DeducedScalar< Value > > gate(Value probability, Inner inner={})
Definition Reduction.h:582
constexpr Collapse collapse()
Definition Reduction.h:291
constexpr Keep keep()
Definition Reduction.h:371
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
std::expected< CategoricalUpdate< Space, Scalar >, WeightError > update(CategoricalParams< Space, Scalar > const &prior, E const &emission)
Definition Emission.h:97
The reduction policies, each made by its factory (decisions.md [policy-factories],...
Definition Reduction.h:222
FilterBranch< MeasurementSpace, Scalar > BranchType
one branch
Definition MeasurementUpdate.h:254
A Gate's threshold given directly as a normalized innovation squared, rather than as a probability,...
Definition Reduction.h:386
Scalar value
the largest NIS that passes
Definition Reduction.h:387
Reduction policy that moment-matches an update's branches into one Gaussian: soft outlier rejection,...
Definition Reduction.h:241
std::optional< internal::PosteriorOf< Branches > > operator()(Branches &&branches) const
Definition Reduction.h:248
Reduction policy that keeps every branch, each added to the filter's output with its own posterior pr...
Definition Reduction.h:351
void operator()(Branches &&branches, Out &out) const
Adds each of branches to out with its log-weight, its posterior moved out of branches given as an rva...
Definition Reduction.h:358
Reduction policy that keeps the most probable branch: hard rejection by comparing the noise component...
Definition Reduction.h:306
std::optional< internal::PosteriorOf< Branches > > operator()(Branches &&branches) const
Definition Reduction.h:313