EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Resampling.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <algorithm>
11#include <cmath>
12#include <concepts>
13#include <cstddef>
14#include <random>
15#include <span>
16#include <utility>
17#include <vector>
18
21
22namespace Emrys
23{
24
56
60template <typename R, typename Scalar, typename G>
61concept Resampler = requires(R const & r, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) {
62 r(weights, ancestors, rng);
63};
64
70template <typename R, typename Scalar, typename G>
72 && requires(
73 R const & r,
74 std::span<Scalar const> weights,
75 std::span<Eigen::Index> ancestors,
76 G & rng,
77 std::span<Scalar> scratch
78 ) { r(weights, ancestors, rng, scratch); };
79
85template <typename R, typename P, typename Scalar, typename G>
88 && requires(R const & r, P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) {
89 r(std::forward<P>(policy), weights, ancestors, rng);
90 };
91
92namespace internal
93{
94
96template <std::floating_point Scalar, std::uniform_random_bit_generator G>
97[[nodiscard]] Scalar
98uniform01(G & rng)
99{
100 auto uniform = std::uniform_real_distribution<Scalar>{Scalar(0), Scalar(1)};
101 return std::min(uniform(rng), std::nextafter(Scalar(1), Scalar(0)));
102}
103
107template <typename Scalar, typename Positions>
108void
109invertCumulative(std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, Positions u)
110{
111 EMRYS_FILTER_ASSERT(!weights.empty());
112 Scalar total = 0;
113 std::size_t lastPositive = 0;
114 for (std::size_t i = 0; i < weights.size(); ++i)
115 {
116 EMRYS_FILTER_ASSERT(weights[i] >= Scalar(0) && "weights must be nonnegative");
117 total += weights[i];
118 if (weights[i] > Scalar(0))
119 {
120 lastPositive = i;
121 }
122 }
123 EMRYS_FILTER_ASSERT(total > Scalar(0) && "weights must have a positive sum");
124
125 std::size_t i = 0;
126 Scalar cumulative = weights[0];
127 for (std::size_t j = 0; j < ancestors.size(); ++j)
128 {
129 Scalar const position = u(j) * total;
130 while (i < lastPositive && position >= cumulative)
131 {
132 cumulative += weights[++i];
133 }
134 ancestors[j] = Eigen::Index(i);
135 }
136}
137
143template <ExecutionPolicy P, typename Scalar, typename Positions>
144void
145invertCumulative(P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, Positions const & u)
146{
147 if constexpr (isSequential<P>)
148 {
149 invertCumulative(weights, ancestors, u);
150 }
151 else
152 {
153 EMRYS_FILTER_ASSERT(!weights.empty());
154 Eigen::Index const count = Eigen::Index(weights.size());
155 auto cumulative = scratchVector<Scalar>(policy, weights.size());
156 cumulativeSum(policy, weights, std::span<Scalar>{cumulative});
157 // The last positive weight, or -1 for none, as the largest index of each range's.
158 Eigen::Index const lastPositive = reduceRanges(
159 policy,
160 count,
161 [&](Eigen::Index begin, Eigen::Index length) {
162 Eigen::Index last = -1;
163 for (Eigen::Index i = begin; i < begin + length; ++i)
164 {
165 EMRYS_FILTER_ASSERT(weights[std::size_t(i)] >= Scalar(0) && "weights must be nonnegative");
166 if (weights[std::size_t(i)] > Scalar(0))
167 {
168 last = i;
169 }
170 }
171 return last;
172 },
173 [](Eigen::Index a, Eigen::Index b) {
174 return b >= 0 ? b : a;
175 }
176 );
177 Scalar const total = cumulative.back();
178 EMRYS_FILTER_ASSERT(lastPositive >= 0 && total > Scalar(0) && "weights must have a positive sum");
179 forEachIndex(std::forward<P>(policy), Eigen::Index(ancestors.size()), [&](Eigen::Index j) {
180 Scalar const position = u(std::size_t(j)) * total;
181 auto const above = std::upper_bound(cumulative.begin(), cumulative.end(), position);
182 ancestors[std::size_t(j)] = std::min(Eigen::Index(above - cumulative.begin()), lastPositive);
183 });
184 }
185}
186
190template <std::floating_point Scalar, typename P, std::uniform_random_bit_generator G>
191[[nodiscard]] auto
192uniforms01(P const & policy, std::size_t count, G & rng)
193{
194 auto draws = scratchVector<Scalar>(policy, count);
195 for (Scalar & draw : draws)
196 {
197 draw = uniform01<Scalar>(rng);
198 }
199 return draws;
200}
201
202} // namespace internal
203
209namespace resamplers
210{
211
219{
224 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
225 void
227 std::span<Scalar const> weights,
228 std::span<Eigen::Index> ancestors,
229 G & rng,
230 std::span<Scalar> scratch
231 ) const
232 {
233 EMRYS_FILTER_ASSERT(scratch.size() >= ancestors.size() && "the scratch must hold a draw per ancestor");
234 auto const positions = scratch.first(ancestors.size());
235 for (auto & p : positions)
236 {
237 p = internal::uniform01<Scalar>(rng);
238 }
239 std::ranges::sort(positions);
240 internal::invertCumulative(weights, ancestors, [&](std::size_t j) {
241 return positions[j];
242 });
243 }
244
246 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
247 void
248 operator()(std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
249 {
250 auto positions = std::vector<Scalar>(ancestors.size());
251 (*this)(weights, ancestors, rng, std::span<Scalar>{positions});
252 }
253
258 template <ExecutionPolicy P, std::floating_point Scalar, std::uniform_random_bit_generator G>
259 void
260 operator()(P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
261 {
262 if constexpr (internal::isSequential<P>)
263 {
264 (*this)(weights, ancestors, rng);
265 }
266 else
267 {
268 auto const positions = internal::uniforms01<Scalar>(policy, ancestors.size(), rng);
269 internal::invertCumulative(std::forward<P>(policy), weights, ancestors, [&](std::size_t j) {
270 return positions[j];
271 });
272 }
273 }
274};
275
279[[nodiscard]] constexpr Multinomial
281{
282 return {};
283}
284
293{
297 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
298 void
299 operator()(std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
300 {
301 Scalar const m = Scalar(ancestors.size());
302 internal::invertCumulative(weights, ancestors, [&](std::size_t j) {
303 return (Scalar(j) + internal::uniform01<Scalar>(rng)) / m;
304 });
305 }
306
310 template <ExecutionPolicy P, std::floating_point Scalar, std::uniform_random_bit_generator G>
311 void
312 operator()(P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
313 {
314 if constexpr (internal::isSequential<P>)
315 {
316 (*this)(weights, ancestors, rng);
317 }
318 else
319 {
320 Scalar const m = Scalar(ancestors.size());
321 auto const offsets = internal::uniforms01<Scalar>(policy, ancestors.size(), rng);
322 internal::invertCumulative(std::forward<P>(policy), weights, ancestors, [&](std::size_t j) {
323 return (Scalar(j) + offsets[j]) / m;
324 });
325 }
326 }
327};
328
332[[nodiscard]] constexpr Stratified
334{
335 return {};
336}
337
351{
355 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
356 void
357 operator()(std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
358 {
359 Scalar const m = Scalar(ancestors.size());
360 Scalar const offset = internal::uniform01<Scalar>(rng);
361 internal::invertCumulative(weights, ancestors, [&](std::size_t j) {
362 return (Scalar(j) + offset) / m;
363 });
364 }
365
367 template <ExecutionPolicy P, std::floating_point Scalar, std::uniform_random_bit_generator G>
368 void
369 operator()(P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
370 {
371 Scalar const m = Scalar(ancestors.size());
372 Scalar const offset = internal::uniform01<Scalar>(rng);
373 internal::invertCumulative(std::forward<P>(policy), weights, ancestors, [&](std::size_t j) {
374 return (Scalar(j) + offset) / m;
375 });
376 }
377};
378
382[[nodiscard]] constexpr Systematic
384{
385 return {};
386}
387
397template <typename Inner = Multinomial>
399{
400 Inner fractional{};
401
407 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
408 void
410 std::span<Scalar const> weights,
411 std::span<Eigen::Index> ancestors,
412 G & rng,
413 std::span<Scalar> scratch
414 ) const
415 {
416 EMRYS_FILTER_ASSERT(
417 scratch.size() >= weights.size() + ancestors.size()
418 && "the scratch must hold a fraction per weight and a draw per ancestor"
419 );
420 Scalar total = 0;
421 for (Scalar const w : weights)
422 {
423 EMRYS_FILTER_ASSERT(w >= Scalar(0) && "weights must be nonnegative");
424 total += w;
425 }
426 EMRYS_FILTER_ASSERT(total > Scalar(0) && "weights must have a positive sum");
427
428 Scalar const m = Scalar(ancestors.size());
429 auto const fractions = scratch.first(weights.size());
430 std::size_t out = 0;
431 for (std::size_t i = 0; i < weights.size(); ++i)
432 {
433 Scalar const expected = m * weights[i] / total;
434 Scalar const copies = std::floor(expected);
435 fractions[i] = expected - copies;
436 // Converting a negative or NaN count to std::size_t is undefined, so it takes no copies.
437 std::size_t const count = copies > Scalar(0) ? std::size_t(copies) : 0;
438 for (std::size_t k = 0; k < count && out < ancestors.size(); ++k)
439 {
440 ancestors[out++] = Eigen::Index(i);
441 }
442 }
443 if (out < ancestors.size())
444 {
446 {
448 std::span<Scalar const>{fractions},
449 ancestors.subspan(out),
450 rng,
451 scratch.subspan(weights.size())
452 );
453 }
454 else
455 {
456 fractional(std::span<Scalar const>{fractions}, ancestors.subspan(out), rng);
457 }
458 }
459 }
460
462 template <std::floating_point Scalar, std::uniform_random_bit_generator G>
463 void
464 operator()(std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
465 {
466 auto scratch = std::vector<Scalar>(weights.size() + ancestors.size());
467 (*this)(weights, ancestors, rng, std::span<Scalar>{scratch});
468 }
469
475 template <ExecutionPolicy P, std::floating_point Scalar, std::uniform_random_bit_generator G>
476 void
477 operator()(P && policy, std::span<Scalar const> weights, std::span<Eigen::Index> ancestors, G & rng) const
478 {
479 if constexpr (internal::isSequential<P>)
480 {
481 (*this)(weights, ancestors, rng);
482 }
483 else
484 {
485 Eigen::Index const count = Eigen::Index(weights.size());
486 Scalar const total = internal::reduceRanges(
487 policy,
488 count,
489 [&](Eigen::Index begin, Eigen::Index length) {
490 Scalar sum = 0;
491 for (Scalar const w : weights.subspan(std::size_t(begin), std::size_t(length)))
492 {
493 sum += w;
494 }
495 return sum;
496 },
497 [](Scalar a, Scalar b) {
498 return a + b;
499 }
500 );
501 EMRYS_FILTER_ASSERT(total > Scalar(0) && "weights must have a positive sum");
502
503 Scalar const m = Scalar(ancestors.size());
504 auto fractions = internal::scratchVector<Scalar>(policy, weights.size());
505 auto ends = internal::scratchVector<Eigen::Index>(policy, weights.size());
506 internal::forEachIndex(policy, count, [&](Eigen::Index i) {
507 Scalar const expected = m * weights[std::size_t(i)] / total;
508 Scalar const copies = std::floor(expected);
509 fractions[std::size_t(i)] = expected - copies;
510 ends[std::size_t(i)] = Eigen::Index(copies);
511 });
512 internal::cumulativeSum(policy, std::span<Eigen::Index const>{ends}, std::span<Eigen::Index>{ends});
513 Eigen::Index const size = Eigen::Index(ancestors.size());
514 internal::forEachIndex(policy, count, [&](Eigen::Index i) {
515 Eigen::Index const begin = i == 0 ? 0 : ends[std::size_t(i - 1)];
516 for (Eigen::Index k = begin; k < std::min(ends[std::size_t(i)], size); ++k)
517 {
518 ancestors[std::size_t(k)] = i;
519 }
520 });
521 std::size_t const out = std::size_t(std::min(ends.back(), size));
522 if (out < ancestors.size())
523 {
524 auto const remainder = std::span<Scalar const>{fractions};
526 {
527 fractional(std::forward<P>(policy), remainder, ancestors.subspan(out), rng);
528 }
529 else
530 {
531 fractional(remainder, ancestors.subspan(out), rng);
532 }
533 }
534 }
535 }
536};
537
542template <typename Inner = Multinomial>
543[[nodiscard]] constexpr Residual<Inner>
544residual(Inner inner = {})
545{
546 return {std::move(inner)};
547}
548
549} // namespace resamplers
550
551} // namespace Emrys
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The macros every header includes first.
An execution policy: execution::sequential, a policy of the standard library such as std::execution::...
Definition Execution.h:459
A Resampler that also runs under the execution policy P, called as r(policy, weights,...
Definition Resampling.h:86
A Resampler that also takes scratch, called as r(weights, ancestors, rng, scratch) with scratch a std...
Definition Resampling.h:71
Requirements on a resampler for scalar type Scalar and random generator G.
Definition Resampling.h:61
constexpr Systematic systematic()
Definition Resampling.h:383
constexpr Stratified stratified()
Definition Resampling.h:333
constexpr Multinomial multinomial()
Definition Resampling.h:280
constexpr Residual< Inner > residual(Inner inner={})
Definition Resampling.h:544
The resamplers, each made by its factory (decisions.md [policy-factories]): multinomial(),...
Definition Resampling.h:210
Draws each ancestor independently.
Definition Resampling.h:219
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng, std::span< Scalar > scratch) const
Fills ancestors with indices into weights, each drawn independently in proportion to its weight,...
Definition Resampling.h:226
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the form with scratch, allocating the scratch for the draws.
Definition Resampling.h:248
void operator()(P &&policy, std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the sequential form, under policy: the uniforms are drawn as it draws them but not sorted,...
Definition Resampling.h:260
Chooses index i floor( ) times deterministically, then draws the remainder with Inner from the fracti...
Definition Resampling.h:399
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the form with scratch, allocating the scratch.
Definition Resampling.h:464
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng, std::span< Scalar > scratch) const
Fills ancestors with floor( ) copies of each index i, then draws the rest with fractional from the fr...
Definition Resampling.h:409
void operator()(P &&policy, std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the sequential form, under policy: the total is a reduction, each index's copies and fraction a fo...
Definition Resampling.h:477
Inner fractional
the resampler for the remainder, applied to the fractional parts
Definition Resampling.h:400
Draws one uniform in each of M equal strata of [0, 1).
Definition Resampling.h:293
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
Fills ancestors with indices into weights, one drawn in each stratum of [0, 1), using rng.
Definition Resampling.h:299
void operator()(P &&policy, std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the sequential form, under policy, with the uniforms drawn first in stratum order,...
Definition Resampling.h:312
Uses one uniform offset for M evenly spaced positions in [0, 1).
Definition Resampling.h:351
void operator()(P &&policy, std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
As the sequential form, under policy.
Definition Resampling.h:369
void operator()(std::span< Scalar const > weights, std::span< Eigen::Index > ancestors, G &rng) const
Fills ancestors with indices into weights at evenly spaced positions offset by one uniform draw from...
Definition Resampling.h:357