EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Weights.h
Go to the documentation of this file.
1#pragma once
2
14
15#include <algorithm>
16#include <concepts>
17#include <expected>
18#include <limits>
19#include <random>
20#include <ranges>
21#include <span>
22
27
28namespace Emrys
29{
30
44template <typename Scalar>
46{
47public:
48
50 void
51 add(Scalar x)
52 {
53 if (x == -Eigen::NumTraits<Scalar>::infinity())
54 {
55 return;
56 }
57 if (x <= m_max)
58 {
59 m_sum += Eigen::numext::exp(x - m_max);
60 }
61 else
62 {
63 m_sum = m_sum * Eigen::numext::exp(m_max - x) + Scalar(1);
64 m_max = x;
65 }
66 }
67
74 template <typename Derived>
75 void
76 add(Eigen::ArrayBase<Derived> const & xs)
77 {
78 if constexpr (std::floating_point<Scalar>)
79 {
80 if (xs.size() == 0)
81 {
82 return;
83 }
84 Scalar const max = xs.template maxCoeff<Eigen::PropagateNaN>();
85 if (max == -Eigen::NumTraits<Scalar>::infinity())
86 {
87 return;
88 }
89 auto range = LogSumExp{};
90 range.m_max = max;
91 // Each +inf input is the new maximum, so each counts one relative to it.
92 range.m_sum = max == Eigen::NumTraits<Scalar>::infinity() ? Scalar((xs == max).count())
93 : Scalar((xs - max).exp().sum());
94 add(range);
95 }
96 else
97 {
98 for (Eigen::Index i = 0; i < xs.size(); ++i)
99 {
100 add(Scalar(xs(i)));
101 }
102 }
103 }
104
108 void
109 add(LogSumExp const & other)
110 {
111 if (other.m_sum == Scalar(0))
112 {
113 return;
114 }
115 if (m_sum == Scalar(0))
116 {
117 *this = other;
118 return;
119 }
120 if (other.m_max <= m_max)
121 {
122 m_sum += other.m_sum * Eigen::numext::exp(other.m_max - m_max);
123 }
124 else
125 {
126 m_sum = m_sum * Eigen::numext::exp(m_max - other.m_max) + other.m_sum;
127 m_max = other.m_max;
128 }
129 }
130
132 [[nodiscard]] Scalar
133 value() const
134 {
135 return m_sum == Scalar(0) ? -Eigen::NumTraits<Scalar>::infinity() : m_max + Eigen::numext::log(m_sum);
136 }
137
138private:
139
140 Scalar m_max = -Eigen::NumTraits<Scalar>::infinity();
141 Scalar m_sum = 0;
142};
143
149template <typename Scalar, ExecutionPolicy P, typename LogAt>
150[[nodiscard]] Scalar
151logSumExp(P && policy, Eigen::Index count, LogAt const & logAt)
152{
153 return internal::reduceRanges(
154 std::forward<P>(policy),
155 count,
156 [&](Eigen::Index begin, Eigen::Index length) {
157 auto sum = LogSumExp<Scalar>{};
158 for (Eigen::Index i = begin; i < begin + length; ++i)
159 {
160 sum.add(Scalar(logAt(i)));
161 }
162 return sum;
163 },
164 [](LogSumExp<Scalar> a, LogSumExp<Scalar> const & b) {
165 a.add(b);
166 return a;
167 }
168 ).value();
169}
170
174template <typename Scalar, typename LogAt>
175[[nodiscard]] Scalar
176logSumExp(Eigen::Index count, LogAt const & logAt)
177{
178 return logSumExp<Scalar>(execution::sequential, count, logAt);
179}
180
187template <ExecutionPolicy P, typename Derived>
188[[nodiscard]] typename Derived::Scalar
189logSumExp(P && policy, Eigen::ArrayBase<Derived> const & logs)
190{
191 using Scalar = typename Derived::Scalar;
192 return internal::reduceRanges(
193 std::forward<P>(policy),
194 logs.size(),
195 [&](Eigen::Index begin, Eigen::Index length) {
196 auto sum = LogSumExp<Scalar>{};
197 sum.add(logs.derived().segment(begin, length));
198 return sum;
199 },
200 [](LogSumExp<Scalar> a, LogSumExp<Scalar> const & b) {
201 a.add(b);
202 return a;
203 }
204 ).value();
205}
206
210template <typename Derived>
211[[nodiscard]] typename Derived::Scalar
212logSumExp(Eigen::ArrayBase<Derived> const & logs)
213{
214 return logSumExp(execution::sequential, logs);
215}
216
217namespace internal
218{
219
220template <typename Scalar, ExecutionPolicy P>
221[[nodiscard]] std::expected<Scalar, WeightError>
222logTotalWeight(P && policy, std::span<Scalar const> logWeights);
223
228template <typename Scalar, std::ranges::input_range R>
229[[nodiscard]] std::expected<Scalar, WeightError>
230logTotalWeight(R && logWeights)
231{
232 if constexpr (std::ranges::contiguous_range<R>
233 && std::same_as<std::remove_cv_t<std::ranges::range_value_t<R>>, Scalar>)
234 {
235 return logTotalWeight<Scalar>(execution::sequential, std::span<Scalar const>{logWeights});
236 }
237 else
238 {
239 auto total = LogSumExp<Scalar>{};
240 bool empty = true;
241 for (Scalar const w : logWeights)
242 {
243 if (isNaN(w) || w == Eigen::NumTraits<Scalar>::infinity())
244 {
245 return std::unexpected{WeightError::NonFinite};
246 }
247 total.add(w);
248 empty = false;
249 }
250 if (empty)
251 {
252 return std::unexpected{WeightError::Empty};
253 }
254 Scalar const result = total.value();
255 if (result == -Eigen::NumTraits<Scalar>::infinity())
256 {
257 return std::unexpected{WeightError::ZeroSum};
258 }
259 return result;
260 }
261}
262
266template <typename Scalar, ExecutionPolicy P>
267[[nodiscard]] std::expected<Scalar, WeightError>
268logTotalWeight(P && policy, std::span<Scalar const> logWeights)
269{
270 // A range's sum, and whether a weight in it is NaN or +inf.
271 struct Total
272 {
273 LogSumExp<Scalar> sum;
274 bool nonFinite = false;
275 };
276
277 if (logWeights.empty())
278 {
279 return std::unexpected{WeightError::Empty};
280 }
281 Total const total = reduceRanges(
282 std::forward<P>(policy),
283 Eigen::Index(logWeights.size()),
284 [&](Eigen::Index begin, Eigen::Index length) {
285 auto range = Total{};
286 auto const weights = logWeights.subspan(std::size_t(begin), std::size_t(length));
287 if constexpr (std::floating_point<Scalar>)
288 {
289 auto const map = Eigen::Map<Eigen::Array<Scalar, Eigen::Dynamic, 1> const>{weights.data(), length};
290 // NaN and +inf are the weights that are not below +inf.
291 range.nonFinite = !(map < Eigen::NumTraits<Scalar>::infinity()).all();
292 if (!range.nonFinite)
293 {
294 range.sum.add(map);
295 }
296 }
297 else
298 {
299 for (Scalar const w : weights)
300 {
301 if (isNaN(w) || w == Eigen::NumTraits<Scalar>::infinity())
302 {
303 range.nonFinite = true;
304 break;
305 }
306 range.sum.add(w);
307 }
308 }
309 return range;
310 },
311 [](Total a, Total const & b) {
312 a.sum.add(b.sum);
313 a.nonFinite = a.nonFinite || b.nonFinite;
314 return a;
315 }
316 );
317 if (total.nonFinite)
318 {
319 return std::unexpected{WeightError::NonFinite};
320 }
321 Scalar const result = total.sum.value();
322 if (result == -Eigen::NumTraits<Scalar>::infinity())
323 {
324 return std::unexpected{WeightError::ZeroSum};
325 }
326 return result;
327}
328
334template <typename Scalar>
335class PruneCut
336{
337public:
338
340 PruneCut() = default;
341
345 PruneCut(Scalar cutoff, Eigen::Index keepTies)
346 : m_cutoff{cutoff}
347 , m_keepTies{keepTies}
348 {}
349
351 [[nodiscard]] bool
352 removes(Scalar logWeight)
353 {
354 if (logWeight < m_cutoff)
355 {
356 return true;
357 }
358 if (logWeight == m_cutoff)
359 {
360 return m_tiesSeen++ >= m_keepTies;
361 }
362 return false;
363 }
364
365private:
366
367 Scalar m_cutoff = -Eigen::NumTraits<Scalar>::infinity();
368 Eigen::Index m_keepTies = std::numeric_limits<Eigen::Index>::max();
369 Eigen::Index m_tiesSeen = 0;
370};
371
376template <typename Scalar>
377[[nodiscard]] PruneCut<Scalar>
378lightestCut(std::span<Scalar const> sorted, Eigen::Index count)
379{
380 if (count == 0)
381 {
382 return {};
383 }
384 Scalar const cutoff = sorted[std::size_t(count - 1)];
385 auto const [first, last] = std::equal_range(sorted.begin(), sorted.end(), cutoff);
386 auto const lighter = Eigen::Index(first - sorted.begin());
387 auto const ties = Eigen::Index(last - first);
388 return {cutoff, ties - (count - lighter)};
389}
390
394template <typename Scalar>
395struct PruneBelowRule
396{
397 Scalar logThreshold;
398
400 template <typename LogAt, typename Scratch>
401 [[nodiscard]] PruneCut<Scalar>
402 operator()(Eigen::Index count, LogAt const & logAt, Scalar logTotal, Scratch & /*scratch*/) const
403 {
404 Scalar const cut = logThreshold + logTotal;
405 Scalar heaviest = -Eigen::NumTraits<Scalar>::infinity();
406 for (Eigen::Index i = 0; i < count; ++i)
407 {
408 heaviest = std::max(heaviest, Scalar(logAt(i)));
409 }
410 if (heaviest < cut)
411 {
412 // Every element is below the threshold: the first of the heaviest stays.
413 return {heaviest, 1};
414 }
415 return {cut, count};
416 }
417};
418
423template <typename Scalar>
424struct PruneTailRule
425{
426 Scalar logFraction;
427
431 template <typename LogAt, typename Scratch>
432 [[nodiscard]] PruneCut<Scalar>
433 operator()(Eigen::Index count, LogAt const & logAt, Scalar logTotal, Scratch & scratch) const
434 {
435 EMRYS_FILTER_ASSERT(logFraction < Scalar(0) && "the fraction pruned is below one");
436 scratch.resize(std::size_t(count));
437 for (Eigen::Index i = 0; i < count; ++i)
438 {
439 scratch[std::size_t(i)] = Scalar(logAt(i));
440 }
441 std::sort(scratch.begin(), scratch.end());
442 Scalar const budget = logFraction + logTotal;
443 auto removed = LogSumExp<Scalar>{};
444 Eigen::Index taken = 0;
445 while (taken < count - 1)
446 {
447 auto next = removed;
448 next.add(scratch[std::size_t(taken)]);
449 if (!(next.value() < budget))
450 {
451 break;
452 }
453 removed = next;
454 ++taken;
455 }
456 return lightestCut(std::span<Scalar const>{scratch.data(), scratch.size()}, taken);
457 }
458};
459
463struct PruneToSizeRule
464{
465 Eigen::Index maxSize;
466
470 template <typename Scalar, typename LogAt, typename Scratch>
471 [[nodiscard]] PruneCut<Scalar>
472 operator()(Eigen::Index count, LogAt const & logAt, Scalar /*logTotal*/, Scratch & scratch) const
473 {
474 EMRYS_FILTER_ASSERT(maxSize >= 1 && "a prune leaves at least one element");
475 if (count <= maxSize)
476 {
477 return {};
478 }
479 scratch.resize(std::size_t(count));
480 for (Eigen::Index i = 0; i < count; ++i)
481 {
482 scratch[std::size_t(i)] = Scalar(logAt(i));
483 }
484 // The lightest elements to remove come first; the ties among them are found in a sort of
485 // those up to the one at the cut.
486 Eigen::Index const removed = count - maxSize;
487 auto const at = scratch.begin() + std::ptrdiff_t(removed - 1);
488 std::nth_element(scratch.begin(), at, scratch.end());
489 Scalar const cutoff = *at;
490 auto const lighter = Eigen::Index(std::count_if(scratch.begin(), scratch.end(), [&](Scalar w) {
491 return w < cutoff;
492 }));
493 auto const ties = Eigen::Index(std::count(scratch.begin(), scratch.end(), cutoff));
494 return {cutoff, ties - (removed - lighter)};
495 }
496};
497
503template <typename Scalar, typename Weight, std::uniform_random_bit_generator G>
504[[nodiscard]] Eigen::Index
505drawIndex(Eigen::Index count, Weight const & weight, G & rng)
506{
507 static_assert(std::floating_point<Scalar>, "sampling requires a standard floating-point scalar");
508 EMRYS_FILTER_ASSERT(count > 0 && "draw from at least one element");
509 Scalar const u = std::uniform_real_distribution<Scalar>{}(rng);
510 Scalar sum = 0;
511 Eigen::Index last = 0;
512 for (Eigen::Index i = 0; i < count; ++i)
513 {
514 Scalar const w = weight(i);
515 if (w > 0)
516 {
517 sum += w;
518 last = i;
519 if (u < sum)
520 {
521 return i;
522 }
523 }
524 }
525 return last;
526}
527
528} // namespace internal
529
530} // namespace Emrys
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
NumericError, the reasons a factorization or a normalization can fail, and its two names,...
Streaming log-sum-exp: accumulates without overflow.
Definition Weights.h:46
void add(Scalar x)
Adds x to the sum, rescaling the running sum if x is the new maximum.
Definition Weights.h:51
Scalar value() const
Definition Weights.h:133
void add(LogSumExp const &other)
Adds everything added to other, as if its inputs had been added one by one, up to rounding.
Definition Weights.h:109
void add(Eigen::ArrayBase< Derived > const &xs)
Adds every element of xs, as if they had been added one by one, up to rounding.
Definition Weights.h:76
Scalar logSumExp(P &&policy, Eigen::Index count, LogAt const &logAt)
Definition Weights.h:151
bool isNaN(T const &x)
Definition Math.h:329
constexpr AxisIndex< Path, sizeof...(I)> at(I... indices)
Definition Discrete.h:1071
constexpr Sequential sequential
The default execution policy: plain loops on the calling thread, in index order (Sequential).
Definition Execution.h:128