45template <
typename Scalar =
double>
54 return m_logLikelihood;
61 friend struct internal::UpdateAccess;
63 Scalar m_logLikelihood = 0;
72template <
typename Input>
76 DrawnInput(Input
const & input, Eigen::Index & )
80 static constexpr int RowsAtCompileTime = 0;
81 static constexpr int MaxRowsAtCompileTime = 0;
86 template <
typename Derived,
typename G>
88 fill(Derived && , G & )
const
92 template <
typename Derived>
93 [[nodiscard]] Input
const &
94 at(Eigen::MatrixBase<Derived>
const & )
const
103template <UncertainInput Input>
104struct DrawnInput<Input>
106 using Space =
typename Input::SpaceType;
107 using Scalar =
typename Input::Scalar;
112 DrawnInput(Input
const & input, Eigen::Index & row)
114 , factor{defaultRoot(covarianceLowerOf(input))}
117 row += input.differenceDimension();
121 static constexpr int RowsAtCompileTime = rowsAtCompileTime<true, Space>;
131 Eigen::Index firstRow;
136 template <
typename Derived,
typename G>
138 fill(Derived && draws, G & rng, std::normal_distribution<Scalar> & normal)
const
140 fillStandardNormal(middleRowsOf<RowsAtCompileTime>(draws, firstRow, input.differenceDimension()), rng, normal);
146 template <
typename Derived>
148 at(Eigen::MatrixBase<Derived>
const & draws)
const
151 middleRowsOf<RowsAtCompileTime>(draws.derived(), firstRow, input.differenceDimension());
152 return Emrys::plus(input.space(), input.mean(), multiply(factor, z));
159template <
typename Input>
160[[nodiscard]] Eigen::Index
161drawnRows([[maybe_unused]] Input
const & input)
163 if constexpr (UncertainInput<Input>)
165 return input.differenceDimension();
248 :
public internal::HeldUpkeep<ParticleFilter<Space_, Model_, Scalar_, Allocator_, Upkeep_>, Upkeep_>
250 using UpkeepBase = internal::HeldUpkeep<ParticleFilter, Upkeep_>;
267 : UpkeepBase{std::move(
upkeep)}
268 , m_model{std::move(
model)}
269 , m_state{std::move(
state)}
274 EMRYS_FILTER_ASSERT(!m_state.
empty() &&
"the initial particle set must have a particle");
275 internal::assertPartitions(m_model, m_state.
space());
283 return m_state.get_allocator();
310 EMRYS_FILTER_ASSERT(!
state.empty() &&
"the particle set must have a particle");
311 internal::assertPartitions(m_model,
state.space());
312 m_state = std::move(
state);
313 if (m_logLikelihoods.get_allocator() != ScalarAllocator(m_state.get_allocator()))
316 m_logLikelihoods = internal::Buffer<Scalar_, Allocator_>(ScalarAllocator(m_state.get_allocator()));
317 m_draws = internal::Buffer<Scalar_, Allocator_>(ScalarAllocator(m_state.get_allocator()));
318 m_batch = internal::Buffer<Scalar_, Allocator_>(ScalarAllocator(m_state.get_allocator()));
324 [[nodiscard]] Model_
const &
331 [[nodiscard]] Model_ &
346 template <std::uniform_random_bit_generator G,
typename... Inputs>
359 template <
ExecutionPolicy P, std::uniform_random_bit_generator G,
typename... Inputs>
364 auto const & space = m_state.space();
365 Scalar_
const step = internal::toScalarTime<Scalar_>(dt);
366 auto const call = internal::modelWith(std::cref(m_model), inputs...);
367 auto const resolved = internal::resolveProcess(call, space);
369 [&](
auto const &... uncertain) {
370 if constexpr (std::remove_cvref_t<
decltype(resolved)>::BatchedFunction)
373 std::forward<P>(policy),
375 internal::defaultRoot(resolved.template noise<Scalar_>(step)),
378 internal::inputOf(uncertain)...
384 std::forward<P>(policy),
386 internal::defaultRoot(resolved.template noise<Scalar_>(step)),
387 [&](
auto const & x,
auto const &... in) {
388 return internal::valueOf(resolved.propagate(x, step, in...));
390 internal::inputOf(uncertain)...
401 template <std::uniform_random_bit_generator G,
typename... Inputs>
406 internal::rejectTimeStep<Inputs...>();
413 template <
ExecutionPolicy P, std::uniform_random_bit_generator G,
typename... Inputs>
416 predict(P && policy, G & rng, Inputs
const &... inputs)
418 internal::rejectTimeStep<Inputs...>();
419 auto const & space = m_state.space();
420 auto const call = internal::modelWith(std::cref(m_model.model()), inputs...);
421 auto const resolved = internal::resolveProcess(call, space);
423 [&](
auto const &... uncertain) {
424 if constexpr (std::remove_cvref_t<
decltype(resolved)>::BatchedFunction)
427 std::forward<P>(policy),
432 internal::inputOf(uncertain)...
438 std::forward<P>(policy),
441 [&](
auto const & x,
auto const &... in) {
442 return internal::valueOf(resolved.propagate(x, m_model.timeStep(), in...));
444 internal::inputOf(uncertain)...
455 template <std::uniform_random_bit_generator G,
typename... Inputs>
466 template <
ExecutionPolicy P, std::uniform_random_bit_generator G,
typename... Inputs>
471 EMRYS_FILTER_ASSERT(steps.
count >= 0 &&
"the number of steps must be nonnegative");
472 for (Eigen::Index i = 0; i < steps.
count; ++i)
474 predict(policy, rng, inputs...);
487 template <
internal::WeighsPo
ints M>
488 [[nodiscard]] std::expected<ParticleFilterUpdate<Scalar_>,
WeightError>
498 template <ExecutionPolicy P,
internal::WeighsPo
ints M>
499 [[nodiscard]] std::expected<ParticleFilterUpdate<Scalar_>,
WeightError>
500 update(P && policy, M
const & measurement)
502 auto const logTotal = m_state.logTotalWeight(policy);
505 return std::unexpected{logTotal.error()};
507 m_logLikelihoods.resize(std::size_t(m_state.size()));
508 auto logWeights = Eigen::Map<typename StateType::WeightArrayType>{m_logLikelihoods.data(), m_state.size()};
509 internal::fillLogLikelihoods(policy, measurement, m_state.space(), m_state.states(), logWeights, m_batch);
510 logWeights += m_state.logWeights() - *logTotal;
513 EMRYS_FILTER_ASSERT(!
isNaN(
logLikelihood) &&
"the measurement's log-likelihood must not be NaN");
515 internal::UpdateAccess::logLikelihood(result) =
logLikelihood;
516 result.accepted =
logLikelihood > -Eigen::NumTraits<Scalar_>::infinity();
519 internal::ParticleSetAccess::setNormalizedLogWeights(m_state, logWeights -
logLikelihood);
526 using ScalarAllocator = internal::ReBound<Allocator_, Scalar_>;
532 auto const count = std::size_t(m_state.
capacity().value_or(m_state.
size()));
533 m_logLikelihoods.reserve(count);
551 template <
typename P,
typename G,
typename Derived,
typename Propagate,
typename... Inputs>
556 Eigen::MatrixBase<Derived>
const & noiseFactor,
558 Inputs
const &... inputs
563 constexpr int NoiseRows = internal::rowsAtCompileTime<true, Space_>;
564 constexpr int Rows = internal::sizeSum<NoiseRows, internal::DrawnInput<Inputs>::RowsAtCompileTime...>;
565 constexpr int MaxRows = internal::sizeSum<
567 internal::DrawnInput<Inputs>::MaxRowsAtCompileTime...>;
568 auto const & space = m_state.
space();
569 Eigen::Index
const count = m_state.
size();
570 EMRYS_FILTER_ASSERT(count > 0 &&
"the particle set must have a particle");
573 Eigen::Index row = noiseRows;
574 auto const drawn = std::tuple{internal::DrawnInput<Inputs>{inputs, row}...};
575 Eigen::Index
const rows = noiseRows + (Eigen::Index{0} + ... + internal::drawnRows(inputs));
576 EMRYS_FILTER_ASSERT(row == rows);
579 auto normal = std::normal_distribution<Scalar_>{};
580 auto const draw = [&](
auto && column) {
581 internal::fillStandardNormal(internal::middleRowsOf<NoiseRows>(column, 0, noiseRows), rng, normal);
583 [&](
auto const &... inputDraws) {
584 (inputDraws.fill(column, rng, normal), ...);
589 auto const move = [&](Eigen::Index i,
auto const & column) {
590 Difference
const z = internal::middleRowsOf<NoiseRows>(column, 0, noiseRows);
591 Vector
const moved = std::apply(
592 [&](
auto const &... inputDraws) {
593 return Vector{
propagate(m_state.state(i), inputDraws.at(column)...)};
597 m_state.setState(i,
Emrys::plus(space, moved, Difference{internal::multiply(noiseFactor, z)}));
599 if constexpr (internal::isSequential<P>)
601 auto column = internal::MatrixOf<Scalar_, Rows, 1, MaxRows, 1>{};
603 for (Eigen::Index i = 0; i < count; ++i)
611 using Draws = internal::MatrixOf<Scalar_, Rows, Eigen::Dynamic, MaxRows, Eigen::Dynamic>;
612 m_draws.resize(std::size_t(rows * count));
613 auto draws = Eigen::Map<Draws>{m_draws.data(), rows, count};
614 for (Eigen::Index i = 0; i < count; ++i)
618 internal::forEachIndex(std::forward<P>(policy), count, [&](Eigen::Index i) {
619 move(i, draws.col(i));
630 template <
typename P,
typename G,
typename Derived,
typename R,
typename TimeScalar,
typename... Inputs>
635 Eigen::MatrixBase<Derived>
const & noiseFactor,
638 Inputs
const &... inputs
642 constexpr int NoiseRows = internal::rowsAtCompileTime<true, Space_>;
643 constexpr int Rows = internal::sizeSum<NoiseRows, internal::DrawnInput<Inputs>::RowsAtCompileTime...>;
644 constexpr int MaxRows = internal::sizeSum<
646 internal::DrawnInput<Inputs>::MaxRowsAtCompileTime...>;
647 using Draws = internal::MatrixOf<Scalar_, Rows, Eigen::Dynamic, MaxRows, Eigen::Dynamic>;
648 using X =
typename R::template Columns<typename R::In, Scalar_>;
649 using Y =
typename R::template Columns<typename R::Out, Scalar_>;
650 auto const & space = m_state.space();
651 Eigen::Index
const count = m_state.size();
652 EMRYS_FILTER_ASSERT(count > 0 &&
"the particle set must have a particle");
654 Eigen::Index row = noiseRows;
655 auto const drawn = std::tuple{internal::DrawnInput<Inputs>{inputs, row}...};
656 Eigen::Index
const rows = noiseRows + (Eigen::Index{0} + ... + internal::drawnRows(inputs));
657 EMRYS_FILTER_ASSERT(row == rows);
658 auto normal = std::normal_distribution<Scalar_>{};
659 m_draws.resize(std::size_t(rows * count));
660 auto draws = Eigen::Map<Draws>{m_draws.data(), rows, count};
661 for (Eigen::Index i = 0; i < count; ++i)
663 auto column = draws.col(i);
664 internal::fillStandardNormal(internal::middleRowsOf<NoiseRows>(column, 0, noiseRows), rng, normal);
666 [&](
auto const &... inputDraws) {
667 (inputDraws.fill(column, rng, normal), ...);
674 m_batch.resize(std::size_t((inRows + outRows) * count));
675 internal::forEachChunk(std::forward<P>(policy), count, [&](Eigen::Index begin, Eigen::Index length) {
676 auto x = Eigen::Map<X>{m_batch.data() + inRows * begin, inRows, length};
677 auto y = Eigen::Map<Y>{m_batch.data() + inRows * count + outRows * begin, outRows, length};
678 for (Eigen::Index j = 0; j < length; ++j)
681 [&](
auto const &... inputDraws) {
682 resolved.template gatherColumn<Scalar_>(
684 m_state.state(begin + j),
685 inputDraws.at(draws.col(begin + j))...
691 resolved.stepBatch(Eigen::Map<X const>{x.data(), inRows, length}, dt, y);
692 for (Eigen::Index j = 0; j < length; ++j)
694 Eigen::Index
const i = begin + j;
695 auto const column = draws.col(i);
696 Difference
const z = internal::middleRowsOf<NoiseRows>(column, 0, noiseRows);
697 auto const moved = resolved.scattered(m_state.state(i), y.col(j));
698 m_state.setState(i,
Emrys::plus(space, moved, Difference{internal::multiply(noiseFactor, z)}));
705 internal::Buffer<Scalar_, Allocator_> m_logLikelihoods;
706 internal::Buffer<Scalar_, Allocator_> m_draws;
707 internal::Buffer<Scalar_, Allocator_> m_batch;
711template <
typename Space,
typename Model,
typename Scalar,
typename Allocator>
715template <
typename Space,
typename Model,
typename Scalar,
typename Allocator,
typename U>
722#ifndef EMRYS_FILTER_NO_IO
731template <
typename Space_,
typename Model_,
typename Scalar_,
typename Allocator_,
typename Upkeep_>
734 std::ostream & stream,
735 ParticleFilter<Space_, Model_, Scalar_, Allocator_, Upkeep_>
const & filter,
739 describeFilter(stream,
"particle filter", filter.state(), indent);
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
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 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]...
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 particle set: ParticleSet, the weighted samples, with resampling, upkeep of weights and particles...
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...
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Drawing samples: SamplesWithLogDensity and SampleWithLogDensity, the Gaussian draws drawSamples(),...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Log-weights: the WeightError a normalization can fail with (NumericError.h), and the streaming log-su...
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
a matrix over differences, such as the covariance
Definition Moments.h:76
DifferenceVector< Space_, Scalar > DifferenceVectorType
a difference, such as a residual
Definition Moments.h:75
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
What a particle filter's update did: the observation's log-likelihood under the particles,...
Definition ParticleFilter.h:47
bool accepted
whether any particle could explain the observation
Definition ParticleFilter.h:57
Scalar logLikelihood() const
Definition ParticleFilter.h:52
A particle filter: a weighted set of state points, moved by sampling the process model and weighted b...
Definition ParticleFilter.h:249
void predict(G &rng, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
Moves each particle forward by dt, any TimeStep (see Time steps), through the process model and adds ...
Definition ParticleFilter.h:349
Model_ & model()
Definition ParticleFilter.h:332
StateType const & state() const
Definition ParticleFilter.h:288
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > update(P &&policy, M const &measurement)
update() under policy: the likelihoods are a for-each over the particles and the evidence and the tot...
Definition ParticleFilter.h:500
ParticleSet< Space_, Scalar_, Allocator_ > StateType
the state the filter owns
Definition ParticleFilter.h:257
void setState(StateType state)
Replaces the particles, as when reinitializing, with a set that has at least one and whose space the ...
Definition ParticleFilter.h:308
void predict(G &rng, Inputs const &... inputs)
Moves each particle forward by the fixed-step model's step, as predict(rng, dt, inputs....
Definition ParticleFilter.h:404
void predict(P &&policy, G &rng, Inputs const &... inputs)
predict(rng, inputs...) with a fixed-step model, under policy, as predict(policy, rng,...
Definition ParticleFilter.h:416
void predict(P &&policy, G &rng, TimeStep< Scalar_ > auto const &dt, Inputs const &... inputs)
predict(rng, dt, inputs...) under policy: the draws are taken first, as the sequential prediction tak...
Definition ParticleFilter.h:362
Model_ ModelType
the process model
Definition ParticleFilter.h:256
ParticleFilter(std::type_identity_t< Model_ > model, StateType state, std::type_identity_t< Upkeep_ > upkeep={})
A filter with process model model, initial particles state, which must not be empty,...
Definition ParticleFilter.h:266
void predict(G &rng, Steps steps, Inputs const &... inputs)
Moves each particle forward by steps of the fixed-step model, as that many calls of predict(rng,...
Definition ParticleFilter.h:458
Allocator_ allocator_type
the state's allocator
Definition ParticleFilter.h:258
allocator_type get_allocator() const
Definition ParticleFilter.h:281
std::expected< ParticleFilterUpdate< Scalar_ >, WeightError > update(M const &measurement)
Multiplies each particle's weight by the observation's likelihood there, , and normalizes the weights...
Definition ParticleFilter.h:489
void predict(P &&policy, G &rng, Steps steps, Inputs const &... inputs)
predict(rng, steps, inputs...) under policy, as that many calls of predict(policy,...
Definition ParticleFilter.h:469
Scalar_ Scalar
the scalar type
Definition ParticleFilter.h:254
StateType & state()
Definition ParticleFilter.h:298
Space_ SpaceType
the state space
Definition ParticleFilter.h:255
Model_ const & model() const
Definition ParticleFilter.h:325
A set of weighted samples: the empirical distribution.
Definition ParticleSet.h:279
Index size() const
Definition ParticleSet.h:606
bool empty() const
Definition ParticleSet.h:613
std::optional< Index > capacity() const
Definition ParticleSet.h:620
Space_ const & space() const
Definition ParticleSet.h:652
Index differenceDimension() const
Definition ParticleSet.h:645
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A type usable as the scalar of a distribution: a real number type such as float, double,...
Definition Math.h:80
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
std::allocator< std::byte > DefaultAllocator
The allocator a type with a run-time count uses unless given one.
Definition Allocation.h:47
Scalar logSumExp(P &&policy, Eigen::Index count, LogAt const &logAt)
Definition Weights.h:151
bool isNaN(T const &x)
Definition Math.h:329
NumericError WeightError
Reasons a collection of log-weights cannot be normalized: Empty, ZeroSum, or NonFinite of NumericErro...
Definition NumericError.h:53
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
Derived::Scalar logLikelihood(M const &measurement, Space const &space, Eigen::MatrixBase< Derived > const &x)
Definition Likelihood.h:433
constexpr bool isFixedStep
Whether Model is a FixedStepModel, whose predictions take no time step.
Definition ProcessModel.h:2776
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
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
ValueVector< DifferenceSpace< S >, Scalar > DifferenceVector
A difference in S as an Eigen column vector of Scalar, the type of minus() and of the offset plus() t...
Definition Space.h:650
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
constexpr int maxValueDimensionAtCompileTime
The largest valid value dimension of Space, or Eigen::Dynamic if it is unbounded.
Definition Space.h:258
Eigen::Matrix< Scalar, valueDimensionAtCompileTime< S >, 1, Eigen::ColMajor, maxValueDimensionAtCompileTime< S >, 1 > ValueVector
A value in S as an Eigen column vector of Scalar: a mean, a sample, or a true state.
Definition Space.h:640
The upkeep operations, each made by its factory (decisions.md [policy-factories], [upkeep]): the acti...
Definition Upkeep.h:380
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