EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
CategoricalHistory.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <cstddef>
13#include <iterator>
14#include <list>
15#include <optional>
16#include <utility>
17
28
29namespace Emrys
30{
31
32namespace internal
33{
34
38template <typename Space, typename Scalar>
39[[nodiscard]] Scalar
40smoothingRatioScale(
41 CategoricalPredictedJoint<Space, Scalar> const & joint,
42 CategoricalParams<Space, Scalar> const & smoothedNext
43)
44{
45 EMRYS_FILTER_ASSERT(
46 smoothedNext.logTotalWeight() && "the smoothed distribution at the step's end must have valid weights"
47 );
48 Scalar const minusInfinity = -Eigen::NumTraits<Scalar>::infinity();
49 auto const logNext = logProbabilitiesOf(smoothedNext);
50 auto const logPredicted = logProbabilitiesOf(joint.predicted);
51 Scalar scale = minusInfinity;
52 for (Eigen::Index j = 0; j < logNext.size(); ++j)
53 {
54 if (logNext(j) != minusInfinity)
55 {
56 EMRYS_FILTER_ASSERT(
57 logPredicted(j) != minusInfinity && "a value the prediction rules out has smoothed probability"
58 );
59 scale = Eigen::numext::maxi(scale, Scalar(logNext(j) - logPredicted(j)));
60 }
61 }
62 return scale;
63}
64
69template <typename Space, typename Scalar>
71smoothingRatios(
72 CategoricalPredictedJoint<Space, Scalar> const & joint,
73 CategoricalParams<Space, Scalar> const & smoothedNext
74)
75{
76 EMRYS_FILTER_ASSERT(joint.filtered.space() == smoothedNext.space() && "the distributions are on different spaces");
77 Scalar const minusInfinity = -Eigen::NumTraits<Scalar>::infinity();
78 Scalar const scale = smoothingRatioScale(joint, smoothedNext);
79 auto const logNext = logProbabilitiesOf(smoothedNext);
80 auto const logPredicted = logProbabilitiesOf(joint.predicted);
81 auto ratios = typename CategoricalParams<Space, Scalar>::ArrayType{};
82 ratios.resize(logNext.size());
83 for (Eigen::Index j = 0; j < logNext.size(); ++j)
84 {
85 ratios(j) =
86 logNext(j) == minusInfinity ? Scalar(0) : Eigen::numext::exp(Scalar(logNext(j) - logPredicted(j) - scale));
87 }
88 return ratios;
89}
90
91} // namespace internal
92
112template <typename Space, typename Scalar>
116 CategoricalParams<Space, Scalar> const & smoothedNext
117)
118{
119 using ArrayType = typename CategoricalParams<Space, Scalar>::ArrayType;
120 ArrayType const ratios = internal::smoothingRatios(joint, smoothedNext);
121 ArrayType const logSmoothed = internal::logProbabilitiesOf(joint.filtered)
122 + internal::multiply(joint.transitions, ratios.matrix()).array().log();
123 auto smoothed = CategoricalParams<Space, Scalar>{joint.filtered.space(), logSmoothed};
124 // A value with probability at the end was predicted from one with probability at the start.
125 [[maybe_unused]] auto const normalized = smoothed.normalize();
126 EMRYS_FILTER_ASSERT(normalized && "no value smoothed at the step's end came from a value at its start");
127 return smoothed;
128}
129
143template <typename Space, typename Scalar>
147 CategoricalParams<Space, Scalar> const & smoothedNext
148)
149{
150 using ArrayType = typename CategoricalParams<Space, Scalar>::ArrayType;
151 ArrayType const ratios = internal::smoothingRatios(joint, smoothedNext);
152 ArrayType const filtered = internal::logProbabilitiesOf(joint.filtered).exp();
154 filtered.matrix().asDiagonal() * joint.transitions * ratios.matrix().asDiagonal();
155 posterior /= posterior.sum();
156 return posterior;
157}
158
191template <DiscreteSpace Space_, RealScalar Scalar_ = double, typename Allocator_ = DefaultAllocator>
193{
194public:
195
196 using Scalar = Scalar_;
197 using SpaceType = Space_;
200 using allocator_type = Allocator_;
201 using Index = Eigen::Index;
202
213
214private:
215
216 using StepAllocator = internal::ReBound<Allocator_, Step>;
217 using ListType = std::list<Step, StepAllocator>;
218
219public:
220
221 using const_iterator = typename ListType::const_iterator;
222
225
227 explicit CategoricalHistory(Allocator_ const & allocator)
228 : m_steps(StepAllocator(allocator))
229 {}
230
232 explicit CategoricalHistory(Lag lag, Allocator_ const & allocator = {})
233 : m_steps(StepAllocator(allocator))
234 , m_lag{lag.steps}
235 {
236 EMRYS_FILTER_ASSERT(lag.steps > 0 && "a fixed-lag history keeps at least one step");
237 }
238
241 : CategoricalHistory{other, other.get_allocator()}
242 {}
243
245 CategoricalHistory(CategoricalHistory const & other, Allocator_ const & allocator)
246 : m_steps(other.m_steps, StepAllocator(allocator))
247 , m_lag{other.m_lag}
248 {}
249
252
256 CategoricalHistory(CategoricalHistory && other, Allocator_ const & allocator)
257 : m_steps(std::move(other.m_steps), StepAllocator(allocator))
258 , m_lag{other.m_lag}
259 {}
260
263 operator=(CategoricalHistory const & other) = default;
264
267 operator=(CategoricalHistory && other) = default;
268
269 ~CategoricalHistory() = default;
270
272 [[nodiscard]] allocator_type
274 {
275 return allocator_type(m_steps.get_allocator());
276 }
277
279 [[nodiscard]] std::optional<Index>
280 lag() const
281 {
282 return m_lag > 0 ? std::optional<Index>{m_lag} : std::nullopt;
283 }
284
286 [[nodiscard]] Index
287 size() const
288 {
289 return Index(m_steps.size());
290 }
291
293 [[nodiscard]] bool
294 empty() const
295 {
296 return m_steps.empty();
297 }
298
300 [[nodiscard]] Step const &
301 front() const
302 {
303 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
304 return m_steps.front();
305 }
306
308 [[nodiscard]] Step const &
309 back() const
310 {
311 EMRYS_FILTER_ASSERT(!empty() && "the history is empty");
312 return m_steps.back();
313 }
314
316 [[nodiscard]] const_iterator
317 begin() const
318 {
319 return m_steps.begin();
320 }
321
323 [[nodiscard]] const_iterator
324 end() const
325 {
326 return m_steps.end();
327 }
328
332 void
334 {
335 if (m_lag > 0 && size() == m_lag)
336 {
337 m_steps.splice(m_steps.end(), m_steps, m_steps.begin());
338 Step & step = m_steps.back();
339 step.joint = std::move(joint);
340 step.smoothed = step.joint.filtered;
341 }
342 else
343 {
344 CategoricalType smoothed = joint.filtered;
345 m_steps.push_back(Step{std::move(joint), std::move(smoothed)});
346 }
347 }
348
353 void
354 smooth(CategoricalType const & latest)
355 {
356 CategoricalType const * next{&latest};
357 for (auto step = m_steps.rbegin(); step != m_steps.rend(); ++step)
358 {
359 step->smoothed = smoothStep(step->joint, *next);
360 next = &step->smoothed;
361 }
362 }
363
365 void
367 {
368 m_steps.clear();
369 }
370
371private:
372
373 ListType m_steps;
374 Index m_lag = 0;
375};
376
385template <DiscreteSpace Space, RealScalar Scalar, typename Allocator = DefaultAllocator>
387{
389 internal::Buffer<Eigen::Index, Allocator> values;
390
393};
394
417template <typename Space, typename Scalar, typename Allocator>
422)
423{
424 using ArrayType = typename CategoricalParams<Space, Scalar>::ArrayType;
425 Scalar const minusInfinity = -Eigen::NumTraits<Scalar>::infinity();
426 Eigen::Index const count = latest.cardinality();
427 std::size_t const steps = std::size_t(history.size());
428 auto backPointers = internal::bufferOf<Eigen::Index>(history.get_allocator());
429 backPointers.resize(steps * std::size_t(count));
430
431 ArrayType delta = internal::logProbabilitiesOf(history.empty() ? latest : history.front().joint.filtered);
432 auto next = ArrayType{};
433 next.resize(count);
434 std::size_t k = 0;
435 for (auto step = history.begin(); step != history.end(); ++step, ++k)
436 {
437 auto const following = std::next(step);
438 auto const filtered =
439 internal::logProbabilitiesOf(following == history.end() ? latest : following->joint.filtered);
440 auto const predicted = internal::logProbabilitiesOf(step->joint.predicted);
441 for (Eigen::Index j = 0; j < count; ++j)
442 {
443 Scalar best = minusInfinity;
444 Eigen::Index from = 0;
445 for (Eigen::Index i = 0; i < count; ++i)
446 {
447 Scalar const candidate = delta(i) + Eigen::numext::log(step->joint.transitions(i, j));
448 if (candidate > best)
449 {
450 best = candidate;
451 from = i;
452 }
453 }
454 backPointers[k * std::size_t(count) + std::size_t(j)] = from;
455 bool const reachable = best != minusInfinity && predicted(j) != minusInfinity;
456 next(j) = reachable ? Scalar(best + filtered(j) - predicted(j)) : minusInfinity;
457 }
458 delta.swap(next);
459 }
460
462 internal::bufferOf<Eigen::Index>(history.get_allocator()),
463 minusInfinity
464 };
465 result.values.resize(steps + 1);
466 Eigen::Index value = 0;
467 result.logProbability = delta.maxCoeff(&value);
468 result.values[steps] = value;
469 for (std::size_t s = steps; s > 0; --s)
470 {
471 value = backPointers[(s - 1) * std::size_t(count) + std::size_t(value)];
472 result.values[s - 1] = value;
473 }
474 return result;
475}
476
477} // namespace Emrys
The allocator conventions of decisions.md [allocation]: DefaultAllocator, Capacity,...
The distribution over a discrete space (decisions.md [discrete-spaces], [params-and-density]): Catego...
Discrete spaces (decisions.md [discrete-spaces]): the DiscreteSpace concept; Finite,...
HiddenMarkovFilter (decisions.md [filters-and-recursions], [emissions], [update-outcomes]): a Categor...
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,...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
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...
The record of a HiddenMarkovFilter's forward pass that a smoother runs backward over: one Categorical...
Definition CategoricalHistory.h:193
CategoricalHistory()=default
An empty history that keeps every step.
CategoricalHistory(CategoricalHistory const &other, Allocator_ const &allocator)
Copies other's steps into nodes from allocator.
Definition CategoricalHistory.h:245
CategoricalHistory(Allocator_ const &allocator)
An empty history that keeps every step, with nodes from allocator.
Definition CategoricalHistory.h:227
CategoricalParams< Space_, Scalar_ > CategoricalType
the smoothed distribution's type
Definition CategoricalHistory.h:198
void push(JointType joint)
Records joint as the newest step.
Definition CategoricalHistory.h:333
const_iterator begin() const
Definition CategoricalHistory.h:317
Allocator_ allocator_type
the allocator the steps come from
Definition CategoricalHistory.h:200
CategoricalHistory & operator=(CategoricalHistory const &other)=default
Copies other's steps and lag.
Index size() const
Definition CategoricalHistory.h:287
std::optional< Index > lag() const
Definition CategoricalHistory.h:280
CategoricalHistory(CategoricalHistory const &other)
Copies the steps into nodes from other's allocator (see Allocation).
Definition CategoricalHistory.h:240
bool empty() const
Definition CategoricalHistory.h:294
void clear()
Drops every step, keeping the lag.
Definition CategoricalHistory.h:366
CategoricalHistory(CategoricalHistory &&other)=default
Takes other's steps.
Step const & back() const
Definition CategoricalHistory.h:309
typename ListType::const_iterator const_iterator
iterates the steps, oldest first
Definition CategoricalHistory.h:221
CategoricalPredictedJoint< Space_, Scalar_ > JointType
what each step records
Definition CategoricalHistory.h:199
CategoricalHistory & operator=(CategoricalHistory &&other)=default
Takes other's steps and lag, as std::list's move assignment does.
Space_ SpaceType
the discrete space
Definition CategoricalHistory.h:197
void smooth(CategoricalType const &latest)
Fills every step's smoothed distribution by smoothStep(), from the newest back, given latest,...
Definition CategoricalHistory.h:354
CategoricalHistory(CategoricalHistory &&other, Allocator_ const &allocator)
Moves other's steps into nodes from allocator, taking its nodes when the allocators are equal.
Definition CategoricalHistory.h:256
allocator_type get_allocator() const
Definition CategoricalHistory.h:273
Step const & front() const
Definition CategoricalHistory.h:301
const_iterator end() const
Definition CategoricalHistory.h:324
Eigen::Index Index
Eigen's index type.
Definition CategoricalHistory.h:201
Scalar_ Scalar
the scalar type
Definition CategoricalHistory.h:196
CategoricalHistory(Lag lag, Allocator_ const &allocator={})
An empty history that keeps the newest lag steps, with nodes from allocator.
Definition CategoricalHistory.h:232
The parameters of a probability distribution over a discrete space: one weight per value.
Definition Categorical.h:257
Space_ const & space() const
Definition Categorical.h:330
Eigen::Index cardinality() const
Definition Categorical.h:337
std::expected< void, WeightError > normalize()
Shifts the log-weights so that the weights sum to one, or returns the WeightError that stops it,...
Definition Categorical.h:467
Eigen::Array< Scalar_, CardinalityAtCompileTime, 1, Eigen::ColMajor, MaxCardinalityAtCompileTime, 1 > ArrayType
One entry per value, in flat-index order.
Definition Categorical.h:268
ValueSequence< Space, Scalar, Allocator > mostProbableSequence(CategoricalHistory< Space, Scalar, Allocator > const &history, CategoricalParams< Space, Scalar > const &latest)
Definition CategoricalHistory.h:419
CategoricalPredictedJoint< Space, Scalar >::MatrixType transitionPosterior(CategoricalPredictedJoint< Space, Scalar > const &joint, CategoricalParams< Space, Scalar > const &smoothedNext)
Definition CategoricalHistory.h:145
CategoricalParams< Space, Scalar > smoothStep(CategoricalPredictedJoint< Space, Scalar > const &joint, CategoricalParams< Space, Scalar > const &smoothedNext)
Definition CategoricalHistory.h:114
One recorded prediction and its smoothed distribution.
Definition CategoricalHistory.h:205
CategoricalType smoothed
for the step's (joint.filtered), as of the last smooth(); a copy of joint.filtered until then
Definition CategoricalHistory.h:211
JointType joint
the prediction as predictJoint() returned it
Definition CategoricalHistory.h:206
One prediction of a HiddenMarkovFilter as a smoother needs it: the distribution the step started from...
Definition HiddenMarkovFilter.h:58
CategoricalParams< Space, Scalar > filtered
, the state before the step
Definition HiddenMarkovFilter.h:61
MatrixType transitions
, with predicted filtered
Definition HiddenMarkovFilter.h:63
typename MarkovChain< Space, Scalar >::MatrixType MatrixType
the transitions' type
Definition HiddenMarkovFilter.h:59
The number of steps a fixed-lag history keeps.
Definition Lag.h:17
The most probable sequence of values over a CategoricalHistory, and its posterior probability,...
Definition CategoricalHistory.h:387
internal::Buffer< Eigen::Index, Allocator > values
The flat index of each value, oldest first: one per step of the history, then the latest.
Definition CategoricalHistory.h:389
Scalar logProbability
, the sequence's posterior log-probability
Definition CategoricalHistory.h:392