EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
HybridGaussianHistory.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <cstddef>
11#include <list>
12#include <optional>
13#include <utility>
14
26
27namespace Emrys
28{
29
30namespace internal
31{
32
37template <typename Joint, typename Gains, typename Next, typename Smoothed>
38void
39smoothHybridStep(Joint const & joint, Gains const & gains, Next const & next, Smoothed & smoothed)
40{
41 using Scalar = typename Joint::Scalar;
42 using ModesType = typename Joint::ModesType;
43 using ArrayType = typename ModesType::ArrayType;
44 using GaussianType = GaussianParams<typename Joint::SpaceType, Scalar>;
45 Scalar const minusInfinity = -Eigen::NumTraits<Scalar>::infinity();
46 Eigen::Index const count = joint.modes.cardinality();
47 EMRYS_FILTER_ASSERT(
48 Eigen::Index(joint.joints.size()) == count && Eigen::Index(gains.size()) == count && next.size() == count
49 && smoothed.size() == count
50 && "the joint, its gains, the next state, and the smoothed state must have one Gaussian per mode"
51 );
52 auto const & pi = joint.transitions;
53 auto const logFiltered = logProbabilitiesOf(joint.modes);
54 auto const logNext = logProbabilitiesOf(next.modes());
55 auto const logTransition = [&](Eigen::Index from, Eigen::Index to) {
56 return pi(from, to) > Scalar(0) ? Scalar(Eigen::numext::log(pi(from, to))) : minusInfinity;
57 };
58
59 // e_i, the predicted probability of mode i at t + 1, normalizes the backward transition
60 // b_ij = p_ji mu_t^j / e_i (8.11).
61 auto logPredicted = ArrayType{};
62 logPredicted.resize(count);
63 for (Eigen::Index i = 0; i < count; ++i)
64 {
65 auto total = LogSumExp<Scalar>{};
66 for (Eigen::Index l = 0; l < count; ++l)
67 {
68 total.add(logTransition(l, i) + logFiltered(l));
69 }
70 logPredicted(i) = total.value();
71 }
72
73 auto logMixing = ArrayType{};
74 logMixing.resize(count);
75 auto logModes = ArrayType{};
76 logModes.resize(count);
77 for (Eigen::Index j = 0; j < count; ++j)
78 {
79 // The backward mixing weights mu^{i|j} of (8.12), unnormalized: b_ij mu_{t+1|N}^i.
80 for (Eigen::Index i = 0; i < count; ++i)
81 {
82 logMixing(i) = logPredicted(i) == minusInfinity
83 ? minusInfinity
84 : logTransition(j, i) + logFiltered(j) - logPredicted(i) + logNext(i);
85 }
86 auto const & modeJoint = joint.joints[std::size_t(j)];
87 if (logTotalWeight<Scalar>(logMixing))
88 {
89 // Mixing (8.13, 8.14) and the mode-matched RTS step (8.15 to 8.17).
90 GaussianType const mixed = ComponentTraits<GaussianType>::collapse(
91 count,
92 [&](Eigen::Index i) -> GaussianType const & {
93 return next.conditional(i);
94 },
95 [&](Eigen::Index i) {
96 return logMixing(i);
97 }
98 );
99 smoothed.setConditional(j, smoothStep(modeJoint, gains[std::size_t(j)], mixed));
100 }
101 else
102 {
103 // Nothing smoothed at t + 1 comes from mode j, so it keeps its filtered Gaussian.
104 smoothed.setConditional(j, modeJoint.filtered);
105 }
106 }
107
108 // The mode probabilities by the expectation correction of Barber (2006), (12) and (13) under
109 // its mean approximation: p(r_t = j | r_{t+1} = i, z_{1:N}) is b_ij reweighted by the density
110 // of mode i's smoothed mean at t + 1 under mode j's filtered Gaussian moved through mode i's
111 // model, normalized over j, and mu_{t|N}^j is its sum over i weighted by mu_{t+1|N}^i. Where no
112 // mode's prediction gives that mean a density, b_ij is left as it is.
113 auto const logAdd = [](Scalar a, Scalar b) {
114 auto sum = LogSumExp<Scalar>{};
115 sum.add(a);
116 sum.add(b);
117 return sum.value();
118 };
119 logModes.setConstant(minusInfinity);
120 auto logPair = ArrayType{};
121 logPair.resize(count);
122 auto logBackward = ArrayType{};
123 logBackward.resize(count);
124 for (Eigen::Index i = 0; i < count; ++i)
125 {
126 if (logNext(i) == minusInfinity || logPredicted(i) == minusInfinity)
127 {
128 continue;
129 }
130 auto const & smoothedMean = next.conditional(i).mean();
131 for (Eigen::Index j = 0; j < count; ++j)
132 {
133 logBackward(j) = logTransition(j, i) + logFiltered(j) - logPredicted(i);
134 logPair(j) = minusInfinity;
135 if (logBackward(j) > minusInfinity)
136 {
137 if (auto const density = joint.predictedThrough(j, i).toDensity())
138 {
139 logPair(j) = logBackward(j) + density->logDensity(smoothedMean);
140 }
141 }
142 }
143 auto const logNormalizer = logTotalWeight<Scalar>(logPair);
144 for (Eigen::Index j = 0; j < count; ++j)
145 {
146 Scalar const logGiven = logNormalizer ? logPair(j) - *logNormalizer : logBackward(j);
147 logModes(j) = logAdd(logModes(j), logNext(i) + logGiven);
148 }
149 }
150 auto modes = ModesType{joint.modes.space(), logModes};
151 [[maybe_unused]] auto const normalized = modes.normalize();
152 EMRYS_FILTER_ASSERT(normalized && "no mode smoothed at the step's end came from a mode at its start");
153 smoothed.setModes(std::move(modes));
154}
155
156} // namespace internal
157
239template <Space Space_, DiscreteSpace ModeSpace_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
241{
242public:
243
244 using Scalar = Scalar_;
245 using SpaceType = Space_;
246 using ModeSpaceType = ModeSpace_;
249 using allocator_type = Allocator_;
250 using Index = Eigen::Index;
251
253 class Step
254 {
255 public:
256
257 using allocator_type = Allocator_;
258
261
263 using GainAllocator = internal::ReBound<Allocator_, GainType>;
264
268 explicit Step(JointType const & recorded, Allocator_ const & allocator = {})
269 : joint{copyJoint(recorded, allocator)}
270 , smoothed{recorded.modes, recorded.joints.front().filtered, allocator}
271 , gains(GainAllocator(allocator))
272 {
274 computeGains();
275 }
276
278 Step(Step const & other, Allocator_ const & allocator)
279 : joint{copyJoint(other.joint, allocator)}
280 , smoothed{other.smoothed, allocator}
281 , gains(other.gains, GainAllocator(allocator))
282 {}
283
287 Step(Step && other, Allocator_ const & allocator)
288 : joint{
289 std::move(other.joint.modes),
290 typename JointType::JointsType(std::move(other.joint.joints), allocator),
291 std::move(other.joint.transitions),
292 typename JointType::PredictionsType(std::move(other.joint.throughOtherModels), allocator)
293 }
294 , smoothed{std::move(other.smoothed), allocator}
295 , gains(std::move(other.gains), GainAllocator(allocator))
296 {}
297
298 Step(Step const &) = default;
299 Step(Step &&) = default;
300
302 Step &
303 operator=(Step const &) = default;
304
306 Step &
307 operator=(Step &&) = default;
308
309 ~Step() = default;
310
314 void
316 {
317 smoothed.setModes(joint.modes);
318 for (Index j = 0; j < smoothed.size(); ++j)
319 {
320 smoothed.setConditional(j, joint.joints[std::size_t(j)].filtered);
321 }
322 }
323
325 void
327 {
328 gains.resize(joint.joints.size());
329 for (std::size_t j = 0; j < gains.size(); ++j)
330 {
331 gains[j] = internal::rtsGain(joint.joints[j]);
332 }
333 }
334
336
339
341 internal::Buffer<GainType, Allocator_> gains;
342
343 private:
344
346 [[nodiscard]] static JointType
347 copyJoint(JointType const & recorded, Allocator_ const & allocator)
348 {
349 return {
350 recorded.modes,
351 typename JointType::JointsType(recorded.joints, allocator),
352 recorded.transitions,
353 typename JointType::PredictionsType(recorded.throughOtherModels, allocator)
354 };
355 }
356 };
357
358private:
359
360 using StepAllocator = internal::ReBound<Allocator_, Step>;
361 using ListType = std::list<Step, StepAllocator>;
362
363public:
364
365 using const_iterator = typename ListType::const_iterator;
366
369
371 explicit HybridGaussianHistory(Allocator_ const & allocator)
372 : m_steps(StepAllocator(allocator))
373 {}
374
376 explicit HybridGaussianHistory(Lag lag, Allocator_ const & allocator = {})
377 : m_steps(StepAllocator(allocator))
378 , m_lag{lag.steps}
379 {
380 EMRYS_FILTER_ASSERT(lag.steps > 0 && "a fixed-lag history keeps at least one step");
381 }
382
387
389 HybridGaussianHistory(HybridGaussianHistory const & other, Allocator_ const & allocator)
390 : m_steps(other.m_steps, StepAllocator(allocator))
391 , m_lag{other.m_lag}
392 {}
393
396
400 HybridGaussianHistory(HybridGaussianHistory && other, Allocator_ const & allocator)
401 : m_steps(std::move(other.m_steps), StepAllocator(allocator))
402 , m_lag{other.m_lag}
403 {}
404
407 operator=(HybridGaussianHistory const & other) = default;
408
411 operator=(HybridGaussianHistory && other) = default;
412
413 ~HybridGaussianHistory() = default;
414
416 [[nodiscard]] allocator_type
418 {
419 return allocator_type(m_steps.get_allocator());
420 }
421
423 [[nodiscard]] std::optional<Index>
424 lag() const
425 {
426 return m_lag > 0 ? std::optional<Index>{m_lag} : std::nullopt;
427 }
428
430 [[nodiscard]] Index
431 size() const
432 {
433 return Index(m_steps.size());
434 }
435
437 [[nodiscard]] bool
438 empty() const
439 {
440 return m_steps.empty();
441 }
442
444 [[nodiscard]] Step const &
445 front() const
446 {
447 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
448 return m_steps.front();
449 }
450
452 [[nodiscard]] Step const &
453 back() const
454 {
455 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
456 return m_steps.back();
457 }
458
460 [[nodiscard]] const_iterator
461 begin() const
462 {
463 return m_steps.begin();
464 }
465
467 [[nodiscard]] const_iterator
468 end() const
469 {
470 return m_steps.end();
471 }
472
476 void
477 push(JointType const & joint)
478 {
479 EMRYS_FILTER_ASSERT(!joint.joints.empty() && "the joint has no modes");
480 if (m_lag > 0 && size() == m_lag)
481 {
482 m_steps.splice(m_steps.end(), m_steps, m_steps.begin());
483 Step & step = m_steps.back();
484 step.joint.modes = joint.modes;
485 step.joint.joints = joint.joints;
486 step.joint.transitions = joint.transitions;
488 step.resetSmoothed();
489 step.computeGains();
490 }
491 else
492 {
493 m_steps.emplace_back(joint);
494 }
495 }
496
501 template <typename LatestAllocator>
502 void
504 {
505 auto step = m_steps.rbegin();
506 if (step == m_steps.rend())
507 {
508 return;
509 }
510 internal::smoothHybridStep(step->joint, step->gains, latest, step->smoothed);
511 for (auto next = step++; step != m_steps.rend(); next = step++)
512 {
513 internal::smoothHybridStep(step->joint, step->gains, next->smoothed, step->smoothed);
514 }
515 }
516
518 void
520 {
521 m_steps.clear();
522 }
523
524private:
525
526 ListType m_steps;
527 Index m_lag = 0;
528};
529
530} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
Discrete spaces (decisions.md [discrete-spaces]): the DiscreteSpace concept; Finite,...
Smoothing the Kalman family (decisions.md [smoothing]): smoothStep(), the Rauch-Tung-Striebel backwar...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
The hybrid state of a discrete mode and a component per mode (decisions.md [imm], [component-first]):...
ImmRecursion and ImmFilter (decisions.md [switching-models], [imm], [filters-and-recursions]): a Hybr...
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,...
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...
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Gaussian.h:267
One recorded prediction and its smoothed hybrid state, built from the history's allocator.
Definition HybridGaussianHistory.h:254
internal::ReBound< Allocator_, GainType > GainAllocator
The allocator of the gains.
Definition HybridGaussianHistory.h:263
Step & operator=(Step &&)=default
Takes the other step's storage, as the members' do.
Step(Step &&)=default
moves the joint and the smoothed state
JointType joint
the prediction as predictJoint() recorded it
Definition HybridGaussianHistory.h:335
Step(Step const &)=default
copies the joint and the smoothed state
Step(JointType const &recorded, Allocator_ const &allocator={})
A copy of recorded in storage from allocator, with the filtered state as its smoothed one until smoot...
Definition HybridGaussianHistory.h:268
void resetSmoothed()
Replaces the smoothed state with the filtered one: the joint's mode probabilities and each mode's fil...
Definition HybridGaussianHistory.h:315
Step & operator=(Step const &)=default
Copies into this step's storage.
void computeGains()
Computes each mode's gain from its joint (internal::rtsGain()), once per recording.
Definition HybridGaussianHistory.h:326
Step(Step const &other, Allocator_ const &allocator)
Copies other into storage from allocator.
Definition HybridGaussianHistory.h:278
Allocator_ allocator_type
the allocator its joints and Gaussians come from
Definition HybridGaussianHistory.h:257
Step(Step &&other, Allocator_ const &allocator)
Moves other into storage from allocator, taking its storage when the allocators are equal.
Definition HybridGaussianHistory.h:287
internal::Buffer< GainType, Allocator_ > gains
Each mode's Rauch-Tung-Striebel gain, made once when the joint is recorded.
Definition HybridGaussianHistory.h:341
typename GaussianParams< Space_, Scalar_ >::MatrixType GainType
One mode's Rauch-Tung-Striebel gain.
Definition HybridGaussianHistory.h:260
SmoothedType smoothed
as of the last smooth(); the filtered state until then
Definition HybridGaussianHistory.h:338
The record of an IMM filter's forward pass that its smoother runs backward over: one HybridPredictedJ...
Definition HybridGaussianHistory.h:241
std::optional< Index > lag() const
Definition HybridGaussianHistory.h:424
HybridGaussianHistory & operator=(HybridGaussianHistory &&other)=default
Takes other's steps and lag, as std::list's move assignment does.
HybridGaussianHistory()=default
An empty history that keeps every step.
HybridGaussianHistory & operator=(HybridGaussianHistory const &other)=default
Copies other's steps and lag.
Allocator_ allocator_type
the allocator the steps come from
Definition HybridGaussianHistory.h:249
allocator_type get_allocator() const
Definition HybridGaussianHistory.h:417
HybridGaussianHistory(HybridGaussianHistory const &other)
Copies the steps into nodes from other's allocator (see Allocation).
Definition HybridGaussianHistory.h:384
HybridPredictedJoint< Space_, ModeSpace_, Scalar_, Allocator_ > JointType
what each step records
Definition HybridGaussianHistory.h:247
typename ListType::const_iterator const_iterator
iterates the steps, oldest first
Definition HybridGaussianHistory.h:365
Step const & front() const
Definition HybridGaussianHistory.h:445
ModeSpace_ ModeSpaceType
the discrete mode space
Definition HybridGaussianHistory.h:246
Index size() const
Definition HybridGaussianHistory.h:431
HybridGaussianParams< Space_, ModeSpace_, Scalar_, Allocator_ > SmoothedType
the smoothed state's type
Definition HybridGaussianHistory.h:248
void push(JointType const &joint)
Records a copy of joint as the newest step.
Definition HybridGaussianHistory.h:477
const_iterator begin() const
Definition HybridGaussianHistory.h:461
Space_ SpaceType
the continuous state space
Definition HybridGaussianHistory.h:245
Scalar_ Scalar
the scalar type
Definition HybridGaussianHistory.h:244
HybridGaussianHistory(Allocator_ const &allocator)
An empty history that keeps every step, with storage from allocator.
Definition HybridGaussianHistory.h:371
Step const & back() const
Definition HybridGaussianHistory.h:453
void clear()
Drops every step, keeping the lag.
Definition HybridGaussianHistory.h:519
HybridGaussianHistory(HybridGaussianHistory &&other, Allocator_ const &allocator)
Moves other's steps into storage from allocator, taking its storage when the allocators are equal.
Definition HybridGaussianHistory.h:400
HybridGaussianHistory(HybridGaussianHistory &&other)=default
Takes other's steps.
bool empty() const
Definition HybridGaussianHistory.h:438
Eigen::Index Index
Eigen's index type.
Definition HybridGaussianHistory.h:250
HybridGaussianHistory(Lag lag, Allocator_ const &allocator={})
An empty history that keeps the newest lag steps, with storage from allocator.
Definition HybridGaussianHistory.h:376
HybridGaussianHistory(HybridGaussianHistory const &other, Allocator_ const &allocator)
Copies other's steps into storage from allocator.
Definition HybridGaussianHistory.h:389
const_iterator end() const
Definition HybridGaussianHistory.h:468
void smooth(HybridGaussianParams< Space_, ModeSpace_, Scalar_, LatestAllocator > const &latest)
Fills every step's smoothed hybrid state by the backward IMM step above, from the newest back,...
Definition HybridGaussianHistory.h:503
Scalar pi()
Definition Math.h:145
HybridParams< Gaussian< Space_, Scalar_ >, ModeSpace_, Allocator_ > HybridGaussianParams
The HybridParams of Gaussians in covariance form: the parameters of a HybridGaussian,...
Definition Hybrid.h:754
CategoricalParams< Space, Scalar > smoothStep(CategoricalPredictedJoint< Space, Scalar > const &joint, CategoricalParams< Space, Scalar > const &smoothedNext)
Definition CategoricalHistory.h:114
One IMM prediction as its smoother needs it: the mode probabilities the step started from,...
Definition ImmFilter.h:138
ModesType modes
, the mode probabilities before the step
Definition ImmFilter.h:148
internal::Buffer< JointType, Allocator_ > JointsType
Definition ImmFilter.h:144
PredictionsType throughOtherModels
Mode j's filtered Gaussian moved through mode i's model for every i other than j, j major and i minor...
Definition ImmFilter.h:155
internal::Buffer< GaussianParams< Space_, Scalar_ >, Allocator_ > PredictionsType
Definition ImmFilter.h:146
TransitionsType transitions
, with
Definition ImmFilter.h:150
JointsType joints
one per mode, in flat-index order
Definition ImmFilter.h:149
The number of steps a fixed-lag history keeps.
Definition Lag.h:17