EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
GaussianMixtureHistory.h
Go to the documentation of this file.
1#pragma once
2
10
11#include <cstddef>
12#include <list>
13#include <optional>
14#include <utility>
15
27
28namespace Emrys
29{
30
31namespace internal
32{
33
38template <typename GaussianType, typename Allocator>
39struct MixtureSmoothingScratch
40{
41 using Scalar = typename GaussianType::Scalar;
42
43 Buffer<GaussianType, Allocator> pairs;
44 Buffer<Scalar, Allocator> pairLogWeights;
45 Buffer<Eigen::Index, Allocator> offsets;
46 Buffer<Scalar, Allocator> logWeights;
47
49 explicit MixtureSmoothingScratch(Allocator const & allocator = {})
50 : pairs(ReBound<Allocator, GaussianType>(allocator))
51 , pairLogWeights(ReBound<Allocator, Scalar>(allocator))
52 , offsets(ReBound<Allocator, Eigen::Index>(allocator))
53 , logWeights(ReBound<Allocator, Scalar>(allocator))
54 {}
55};
56
63template <
64 ExecutionPolicy P,
65 typename Joint,
66 typename Gains,
67 typename Next,
68 typename NextLineage,
69 typename Smoothed,
70 typename Scratch>
71void
72smoothMixtureStep(
73 P && policy,
74 Joint const & joint,
75 Gains const & gains,
76 Next const & next,
77 NextLineage const & nextLineage,
78 Smoothed & smoothed,
79 Scratch & scratch
80)
81{
82 using Scalar = typename Joint::Scalar;
83 using GaussianType = typename Joint::JointType::GaussianType;
84 using Index = Eigen::Index;
85 Scalar const minusInfinity = -Eigen::NumTraits<Scalar>::infinity();
86 Index const parents = Index(joint.joints.size());
87 Index const children = next.size();
88 EMRYS_FILTER_ASSERT(smoothed.size() == parents && "the smoothed mixture must have a component per parent");
89 EMRYS_FILTER_ASSERT(Index(gains.size()) == parents && "the gains must have one per parent");
90 EMRYS_FILTER_ASSERT(
91 nextLineage.parentCount() == parents && nextLineage.size() == children
92 && "the lineage must have a row per component at the step's end and a share per component at its start"
93 );
94 auto const logTotal = next.logTotalWeight();
95 EMRYS_FILTER_ASSERT(logTotal && "the smoothed mixture at the step's end must have valid weights");
96 auto const logChildWeight = [&](Index p) {
97 return next[p].logWeight - *logTotal;
98 };
99 auto const paired = [&](Index p, Index n) {
100 return logChildWeight(p) > minusInfinity && nextLineage.logShare(p, n) > minusInfinity;
101 };
102
103 // The pairs (p, n) of child p at the end and its parent n at the start, grouped by parent.
104 scratch.offsets.assign(std::size_t(parents + 1), 0);
105 for (Index n = 0; n < parents; ++n)
106 {
107 Index count = 0;
108 for (Index p = 0; p < children; ++p)
109 {
110 count += paired(p, n) ? 1 : 0;
111 }
112 scratch.offsets[std::size_t(n + 1)] = scratch.offsets[std::size_t(n)] + count;
113 }
114 std::size_t const total = std::size_t(scratch.offsets.back());
115 scratch.pairs.resize(total);
116 scratch.pairLogWeights.resize(total);
117 scratch.logWeights.resize(std::size_t(parents));
118
119 internal::forEachIndex(policy, parents, [&](Index n) {
120 auto const & parentJoint = joint.joints[std::size_t(n)];
121 Index const first = scratch.offsets[std::size_t(n)];
122 Index slot = first;
123 for (Index p = 0; p < children; ++p)
124 {
125 if (paired(p, n))
126 {
127 // An RTS step from parent n to child p, weighted as (11) with the normalized share of (15).
128 scratch.pairs[std::size_t(slot)] = smoothStep(parentJoint, gains[std::size_t(n)], next[p].component);
129 scratch.pairLogWeights[std::size_t(slot)] = logChildWeight(p) + nextLineage.logShare(p, n);
130 ++slot;
131 }
132 }
133 auto weight = LogSumExp<Scalar>{};
134 for (Index i = first; i < slot; ++i)
135 {
136 weight.add(scratch.pairLogWeights[std::size_t(i)]);
137 }
138 scratch.logWeights[std::size_t(n)] = weight.value();
139 });
140 smoothed.forEach(policy, [&](auto & entry) {
141 Index const n = entry.index();
142 Index const first = scratch.offsets[std::size_t(n)];
143 Index const count = scratch.offsets[std::size_t(n + 1)] - first;
144 if (count == 0)
145 {
146 // No child at the end descends from it: it keeps its filtered Gaussian, with no weight.
147 entry.setComponent(joint.joints[std::size_t(n)].filtered);
148 return;
149 }
150 // The pairs that share parent n are merged into one, the group merge of Algorithm 2.
151 entry.setComponent(
152 ComponentTraits<GaussianType>::collapse(
153 count,
154 [&](Index i) -> GaussianType const & {
155 return scratch.pairs[std::size_t(first + i)];
156 },
157 [&](Index i) {
158 return scratch.pairLogWeights[std::size_t(first + i)];
159 }
160 )
161 );
162 });
163 for (Index n = 0; n < parents; ++n)
164 {
165 smoothed.setLogWeight(n, scratch.logWeights[std::size_t(n)]);
166 }
167 // A child with weight descends from some parent with a share, so some parent has weight.
168 [[maybe_unused]] auto const normalized = smoothed.normalize();
169 EMRYS_FILTER_ASSERT(normalized && "nothing at the step's end descends from a component at its start");
170}
171
172} // namespace internal
173
246template <Space Space_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
248{
249public:
250
251 using Scalar = Scalar_;
252 using SpaceType = Space_;
255 using allocator_type = Allocator_;
256 using Index = Eigen::Index;
257
259 class Step
260 {
261 public:
262
263 using allocator_type = Allocator_;
264
267
269 using GainAllocator = internal::ReBound<Allocator_, GainType>;
270
274 explicit Step(JointType const & recorded, Allocator_ const & allocator = {})
275 : joint{copyJoint(recorded, allocator)}
276 , smoothed{joint.joints.front().filtered.space(), allocator}
277 , gains(GainAllocator(allocator))
278 {
281 computeGains();
282 }
283
287 explicit Step(JointType && recorded, Allocator_ const & allocator = {})
288 : joint{moveJoint(std::move(recorded), allocator)}
289 , smoothed{joint.joints.front().filtered.space(), allocator}
290 , gains(GainAllocator(allocator))
291 {
294 computeGains();
295 }
296
298 Step(Step const & other, Allocator_ const & allocator)
299 : joint{copyJoint(other.joint, allocator)}
300 , smoothed{other.smoothed, allocator}
301 , gains(other.gains, GainAllocator(allocator))
302 {}
303
307 Step(Step && other, Allocator_ const & allocator)
308 : joint{moveJoint(std::move(other.joint), allocator)}
309 , smoothed{std::move(other.smoothed), allocator}
310 , gains(std::move(other.gains), GainAllocator(allocator))
311 {}
312
313 Step(Step const &) = default;
314 Step(Step &&) = default;
315
317 Step &
318 operator=(Step const &) = default;
319
321 Step &
322 operator=(Step &&) = default;
323
324 ~Step() = default;
325
329 void
331 {
332 smoothed.clear();
333 for (std::size_t n = 0; n < joint.joints.size(); ++n)
334 {
335 static_cast<void>(smoothed.add(joint.logWeights[n], joint.joints[n].filtered));
336 }
337 }
338
340 void
342 {
343 gains.resize(joint.joints.size());
344 for (std::size_t n = 0; n < gains.size(); ++n)
345 {
346 gains[n] = internal::rtsGain(joint.joints[n]);
347 }
348 }
349
351
356
358 internal::Buffer<GainType, Allocator_> gains;
359
360 private:
361
363 [[nodiscard]] static JointType
364 copyJoint(JointType const & recorded, Allocator_ const & allocator)
365 {
366 return {
367 typename JointType::LogWeightsType(recorded.logWeights, allocator),
368 typename JointType::JointsType(recorded.joints, allocator),
369 typename JointType::LineageType(recorded.lineage, allocator)
370 };
371 }
372
374 [[nodiscard]] static JointType
375 moveJoint(JointType && recorded, Allocator_ const & allocator)
376 {
377 return {
378 typename JointType::LogWeightsType(std::move(recorded.logWeights), allocator),
379 typename JointType::JointsType(std::move(recorded.joints), allocator),
380 typename JointType::LineageType(std::move(recorded.lineage), allocator)
381 };
382 }
383 };
384
385private:
386
387 using StepAllocator = internal::ReBound<Allocator_, Step>;
388 using ListType = std::list<Step, StepAllocator>;
389
391 using ScratchType = internal::MixtureSmoothingScratch<GaussianParams<Space_, Scalar_>, Allocator_>;
392
393public:
394
395 using const_iterator = typename ListType::const_iterator;
396
399
401 explicit GaussianMixtureHistory(Allocator_ const & allocator)
402 : m_steps(StepAllocator(allocator))
403 , m_scratch{allocator}
404 {}
405
407 explicit GaussianMixtureHistory(Lag lag, Allocator_ const & allocator = {})
408 : m_steps(StepAllocator(allocator))
409 , m_scratch{allocator}
410 , m_lag{lag.steps}
411 {
412 EMRYS_FILTER_ASSERT(lag.steps > 0 && "a fixed-lag history keeps at least one step");
413 }
414
419
421 GaussianMixtureHistory(GaussianMixtureHistory const & other, Allocator_ const & allocator)
422 : m_steps(other.m_steps, StepAllocator(allocator))
423 , m_scratch{allocator}
424 , m_lag{other.m_lag}
425 {}
426
429
433 GaussianMixtureHistory(GaussianMixtureHistory && other, Allocator_ const & allocator)
434 : m_steps(std::move(other.m_steps), StepAllocator(allocator))
435 , m_scratch{allocator}
436 , m_lag{other.m_lag}
437 {}
438
441 operator=(GaussianMixtureHistory const & other) = default;
442
446
447 ~GaussianMixtureHistory() = default;
448
450 [[nodiscard]] allocator_type
452 {
453 return allocator_type(m_steps.get_allocator());
454 }
455
457 [[nodiscard]] std::optional<Index>
458 lag() const
459 {
460 return m_lag > 0 ? std::optional<Index>{m_lag} : std::nullopt;
461 }
462
464 [[nodiscard]] Index
465 size() const
466 {
467 return Index(m_steps.size());
468 }
469
471 [[nodiscard]] bool
472 empty() const
473 {
474 return m_steps.empty();
475 }
476
478 [[nodiscard]] Step const &
479 front() const
480 {
481 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
482 return m_steps.front();
483 }
484
486 [[nodiscard]] Step const &
487 back() const
488 {
489 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
490 return m_steps.back();
491 }
492
494 [[nodiscard]] const_iterator
495 begin() const
496 {
497 return m_steps.begin();
498 }
499
501 [[nodiscard]] const_iterator
502 end() const
503 {
504 return m_steps.end();
505 }
506
511 void
512 push(JointType const & joint)
513 {
514 record(joint);
515 }
516
520 void
521 push(JointType && joint)
522 {
523 record(std::move(joint));
524 }
525
531 template <typename LatestAllocator>
532 void
537
541 template <ExecutionPolicy P, typename LatestAllocator>
542 void
544 {
545 auto step = m_steps.rbegin();
546 if (step == m_steps.rend())
547 {
548 return;
549 }
550 assertFollows(latest.lineage(), *step);
551 internal::smoothMixtureStep(
552 policy,
553 step->joint,
554 step->gains,
555 latest,
556 latest.lineage(),
557 step->smoothed,
558 m_scratch
559 );
560 for (auto next = step++; step != m_steps.rend(); next = step++)
561 {
562 internal::smoothMixtureStep(
563 policy,
564 step->joint,
565 step->gains,
566 next->smoothed,
567 next->joint.lineage,
568 step->smoothed,
569 m_scratch
570 );
571 }
572 }
573
575 void
577 {
578 m_steps.clear();
579 }
580
581private:
582
584 template <typename Joint>
585 void
586 record(Joint && joint)
587 {
588 EMRYS_FILTER_ASSERT(
589 !joint.joints.empty() && joint.logWeights.size() == joint.joints.size() && "the joint has no components"
590 );
591 if (!empty())
592 {
593 assertFollows(joint.lineage, back());
594 }
595 if (m_lag > 0 && size() == m_lag)
596 {
597 m_steps.splice(m_steps.end(), m_steps, m_steps.begin());
598 Step & step = m_steps.back();
599 step.joint.logWeights.assign(joint.logWeights.begin(), joint.logWeights.end());
600 step.joint.joints.assign(joint.joints.begin(), joint.joints.end());
601 step.joint.lineage = joint.lineage;
602 step.resetSmoothed();
603 step.computeGains();
604 }
605 else
606 {
607 m_steps.emplace_back(std::forward<Joint>(joint));
608 }
609 }
610
615 template <typename Lineage>
616 static void
617 assertFollows([[maybe_unused]] Lineage const & lineage, [[maybe_unused]] Step const & step)
618 {
619 EMRYS_FILTER_ASSERT(
620 lineage.count() == step.joint.lineage.count() + 1
621 && lineage.parentCount() == Index(step.joint.joints.size())
622 && "the lineage must start at the newest record's predictJoint(): one predictJoint() apart, and no copy of "
623 "the state between"
624 );
625 }
626
627 ListType m_steps;
628 ScratchType m_scratch;
629 Index m_lag = 0;
630};
631
632} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
Smoothing the Kalman family (decisions.md [smoothing]): smoothStep(), the Rauch-Tung-Striebel backwar...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
Lag, the number of steps a fixed-lag history keeps, which every history of the Smoothers layer takes ...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
MixtureFilter (decisions.md [mixture-filter], [filters-and-recursions]): a Mixture state whose compon...
The mixture: the parameters MixtureParams of weighted components of one type (decisions....
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...
One recorded prediction and its smoothed mixture, built from the history's allocator.
Definition GaussianMixtureHistory.h:260
SmoothedType smoothed
, a component per component of the joint, as of the last smooth(); the filtered mixture until then
Definition GaussianMixtureHistory.h:355
Step(Step const &)=default
copies the joint and the smoothed mixture
internal::Buffer< GainType, Allocator_ > gains
Each component's Rauch-Tung-Striebel gain, made once when the joint is recorded.
Definition GaussianMixtureHistory.h:358
typename GaussianParams< Space_, Scalar_ >::MatrixType GainType
One component's Rauch-Tung-Striebel gain.
Definition GaussianMixtureHistory.h:266
Step(Step &&other, Allocator_ const &allocator)
Moves other into storage from allocator, taking its storage when the allocators are equal.
Definition GaussianMixtureHistory.h:307
Step(JointType const &recorded, Allocator_ const &allocator={})
A copy of recorded in storage from allocator, with the filtered mixture as its smoothed one until smo...
Definition GaussianMixtureHistory.h:274
Step & operator=(Step const &)=default
Copies into this step's storage.
internal::ReBound< Allocator_, GainType > GainAllocator
The allocator of the gains.
Definition GaussianMixtureHistory.h:269
Step(Step &&)=default
moves the joint and the smoothed mixture
Step(JointType &&recorded, Allocator_ const &allocator={})
recorded moved into storage from allocator, with the filtered mixture as its smoothed one until smoot...
Definition GaussianMixtureHistory.h:287
Step(Step const &other, Allocator_ const &allocator)
Copies other into storage from allocator.
Definition GaussianMixtureHistory.h:298
void resetSmoothed()
Replaces the smoothed mixture with the filtered one: the joint's weights and each component's filtere...
Definition GaussianMixtureHistory.h:330
void computeGains()
Computes each component's gain from its joint (internal::rtsGain()), once per recording.
Definition GaussianMixtureHistory.h:341
Allocator_ allocator_type
the allocator its joint and mixture come from
Definition GaussianMixtureHistory.h:263
Step & operator=(Step &&)=default
Takes the other step's storage, as the members' do.
JointType joint
the prediction as predictJoint() recorded it
Definition GaussianMixtureHistory.h:350
The record of a Gaussian mixture filter's forward pass that its smoother runs backward over: one Gaus...
Definition GaussianMixtureHistory.h:248
GaussianMixtureHistory(Allocator_ const &allocator)
An empty history that keeps every step, with storage from allocator.
Definition GaussianMixtureHistory.h:401
GaussianMixtureHistory(GaussianMixtureHistory const &other, Allocator_ const &allocator)
Copies other's steps into storage from allocator.
Definition GaussianMixtureHistory.h:421
GaussianMixtureHistory & operator=(GaussianMixtureHistory const &other)=default
Copies other's steps and lag.
void smooth(P &&policy, GaussianMixtureParams< Space_, Scalar_, LatestAllocator > const &latest)
smooth() under policy: each backward step's pairs and merges are a for-each over the step's component...
Definition GaussianMixtureHistory.h:543
void push(JointType &&joint)
Records joint as the newest step, taking its storage unless the history is a full fixed-lag one,...
Definition GaussianMixtureHistory.h:521
Space_ SpaceType
the state space
Definition GaussianMixtureHistory.h:252
Eigen::Index Index
Eigen's index type.
Definition GaussianMixtureHistory.h:256
typename ListType::const_iterator const_iterator
iterates the steps, oldest first
Definition GaussianMixtureHistory.h:395
void push(JointType const &joint)
Records a copy of joint as the newest step; it must come from the predictJoint() after the newest ste...
Definition GaussianMixtureHistory.h:512
GaussianMixturePredictedJoint< Space_, Scalar_, Allocator_ > JointType
what each step records
Definition GaussianMixtureHistory.h:253
const_iterator begin() const
Definition GaussianMixtureHistory.h:495
Scalar_ Scalar
the scalar type
Definition GaussianMixtureHistory.h:251
std::optional< Index > lag() const
Definition GaussianMixtureHistory.h:458
GaussianMixtureHistory()=default
An empty history that keeps every step.
GaussianMixtureParams< Space_, Scalar_, Allocator_ > SmoothedType
the smoothed mixture's type
Definition GaussianMixtureHistory.h:254
GaussianMixtureHistory(GaussianMixtureHistory &&other, Allocator_ const &allocator)
Moves other's steps into storage from allocator, taking its storage when the allocators are equal.
Definition GaussianMixtureHistory.h:433
GaussianMixtureHistory & operator=(GaussianMixtureHistory &&other)=default
Takes other's steps and lag, as std::list's move assignment does.
Step const & back() const
Definition GaussianMixtureHistory.h:487
Index size() const
Definition GaussianMixtureHistory.h:465
Allocator_ allocator_type
the allocator the steps come from
Definition GaussianMixtureHistory.h:255
void clear()
Drops every step, keeping the lag.
Definition GaussianMixtureHistory.h:576
const_iterator end() const
Definition GaussianMixtureHistory.h:502
Step const & front() const
Definition GaussianMixtureHistory.h:479
GaussianMixtureHistory(GaussianMixtureHistory const &other)
Copies the steps into nodes from other's allocator (see Allocation).
Definition GaussianMixtureHistory.h:416
allocator_type get_allocator() const
Definition GaussianMixtureHistory.h:451
bool empty() const
Definition GaussianMixtureHistory.h:472
void smooth(GaussianMixtureParams< Space_, Scalar_, LatestAllocator > const &latest)
Fills every step's smoothed mixture by the backward step above, from the newest back,...
Definition GaussianMixtureHistory.h:533
GaussianMixtureHistory(GaussianMixtureHistory &&other)=default
Takes other's steps.
GaussianMixtureHistory(Lag lag, Allocator_ const &allocator={})
An empty history that keeps the newest lag steps, with storage from allocator.
Definition GaussianMixtureHistory.h:407
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Gaussian.h:267
LineageType const & lineage() const
Definition Mixture.h:1119
void reserve(Index n)
Reserves room for n components without setting a capacity.
Definition Mixture.h:1080
MixtureParams< Gaussian< Space_, Scalar_ >, Allocator_ > GaussianMixtureParams
A Mixture of Gaussians in covariance form, the spelling for the common case (decisions....
Definition Mixture.h:2072
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128
CategoricalParams< Space, Scalar > smoothStep(CategoricalPredictedJoint< Space, Scalar > const &joint, CategoricalParams< Space, Scalar > const &smoothedNext)
Definition CategoricalHistory.h:114
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
Definition MixtureFilter.h:151
LineageType lineage
how the components descend from those of the previous record
Definition MixtureFilter.h:156
LogWeightsType logWeights
each component's log-weight before the step, as the state had it
Definition MixtureFilter.h:154
internal::Buffer< JointType, Allocator_ > JointsType
Definition MixtureFilter.h:150
MixtureLineage< Scalar_, Allocator_ > LineageType
Definition MixtureFilter.h:152
JointsType joints
one per component, in the state's order
Definition MixtureFilter.h:155
The number of steps a fixed-lag history keeps.
Definition Lag.h:17