libstp
Robotics library for Botball
Loading...
Searching...
No Matches
resampling.hpp
Go to the documentation of this file.
1#pragma once
2
3// Particle resampling algorithms.
4//
5// Ported (and adapted to std::mt19937) from MRPT's CParticleFilterCapable
6// (BSD-3-Clause, https://github.com/MRPT/mrpt). Four schemes are provided —
7// Multinomial, Residual, Stratified, Systematic. References:
8// - Doucet & Johansen, "A tutorial on particle filtering and smoothing" (2009)
9// - Liu & Chen, "Sequential Monte Carlo methods for dynamic systems" (1998)
10// - Kitagawa, "Monte Carlo filter ..." (1996)
11// - Carpenter, Clifford, Fearnhead, "Improved particle filter ..." (1999)
12//
13// All variants take log-weights (need not be normalized) and emit indexes the
14// caller plugs into its own particle buffer via performSubstitution-style copy.
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <random>
20#include <vector>
21
22namespace libstp::localization {
23
24enum class ResamplingAlgorithm : uint8_t {
29};
30
42 const std::vector<double>& logWeights,
43 std::mt19937& rng,
44 std::vector<size_t>& outIndexes,
45 size_t outCount = 0) {
46 const size_t M = logWeights.size();
47 if (M == 0) {
48 outIndexes.clear();
49 return;
50 }
51 if (outCount == 0) {
52 outCount = M;
53 }
54
55 // Log-sum-exp normalization to a linear weight vector.
56 double maxLog = logWeights.front();
57 for (const double lw : logWeights) {
58 maxLog = std::max(maxLog, lw);
59 }
60 std::vector<double> linW(M);
61 double sum = 0.0;
62 for (size_t i = 0; i < M; ++i) {
63 linW[i] = std::exp(logWeights[i] - maxLog);
64 sum += linW[i];
65 }
66 if (!(sum > 0.0)) {
67 // Degenerate input: uniform fallback so callers never see UB.
68 outIndexes.assign(outCount, 0);
69 std::uniform_int_distribution<size_t> uniDist(0, M - 1);
70 for (auto& idx : outIndexes) {
71 idx = uniDist(rng);
72 }
73 return;
74 }
75 const double inv = 1.0 / sum;
76 for (auto& w : linW) {
77 w *= inv;
78 }
79
80 std::uniform_real_distribution<double> uni01(0.0, 1.0);
81
82 auto cumulativeWalk = [&](const std::vector<double>& T) {
83 // Shared CDF traversal used by Multinomial / Stratified / Systematic.
84 std::vector<double> Q(M);
85 double acc = 0.0;
86 for (size_t i = 0; i < M; ++i) {
87 acc += linW[i];
88 Q[i] = acc;
89 }
90 Q[M - 1] = 1.1; // Guard against FP-rounding past 1.0.
91
92 outIndexes.resize(outCount);
93 size_t i = 0;
94 size_t j = 0;
95 while (i < outCount) {
96 if (T[i] < Q[j]) {
97 outIndexes[i++] = j;
98 } else if (j + 1 < M) {
99 ++j;
100 } else {
101 outIndexes[i++] = j; // Saturate at last bin.
102 }
103 }
104 };
105
106 switch (method) {
108 std::vector<double> T(outCount);
109 for (auto& t : T) {
110 t = uni01(rng);
111 }
112 std::sort(T.begin(), T.end());
113 cumulativeWalk(T);
114 break;
115 }
116
118 std::vector<uint32_t> N(M, 0);
119 size_t R = 0;
120 for (size_t i = 0; i < M; ++i) {
121 N[i] = static_cast<uint32_t>(static_cast<double>(outCount) * linW[i]);
122 R += N[i];
123 }
124 outIndexes.resize(outCount);
125 size_t j = 0;
126 for (size_t i = 0; i < M && j < outCount; ++i) {
127 for (uint32_t k = 0; k < N[i] && j < outCount; ++k) {
128 outIndexes[j++] = i;
129 }
130 }
131 const size_t residualCount = outCount - j;
132 if (residualCount == 0) {
133 break;
134 }
135 // Residual part: modified weights = M * w_i - N_i, multinomial.
136 std::vector<double> linWmod(M);
137 const double invRes = 1.0 / static_cast<double>(residualCount);
138 for (size_t i = 0; i < M; ++i) {
139 linWmod[i] =
140 invRes * (static_cast<double>(outCount) * linW[i] - static_cast<double>(N[i]));
141 }
142 std::vector<double> Qres(M);
143 double acc = 0.0;
144 for (size_t i = 0; i < M; ++i) {
145 acc += linWmod[i];
146 Qres[i] = acc;
147 }
148 Qres[M - 1] = 1.1;
149 std::vector<double> T(residualCount);
150 for (auto& t : T) {
151 t = uni01(rng);
152 }
153 std::sort(T.begin(), T.end());
154 size_t walk = 0;
155 size_t k = 0;
156 while (walk < residualCount) {
157 if (T[walk] < Qres[k]) {
158 outIndexes[j + walk] = k;
159 ++walk;
160 } else if (k + 1 < M) {
161 ++k;
162 } else {
163 outIndexes[j + walk] = k;
164 ++walk;
165 }
166 }
167 break;
168 }
169
171 std::vector<double> T(outCount);
172 const double step = 1.0 / static_cast<double>(outCount);
173 for (size_t i = 0; i < outCount; ++i) {
174 T[i] = (static_cast<double>(i) + uni01(rng)) * step;
175 }
176 cumulativeWalk(T);
177 break;
178 }
179
181 std::vector<double> T(outCount);
182 const double step = 1.0 / static_cast<double>(outCount);
183 const double offset = uni01(rng) * step;
184 for (size_t i = 0; i < outCount; ++i) {
185 T[i] = offset + static_cast<double>(i) * step;
186 }
187 cumulativeWalk(T);
188 break;
189 }
190 }
191}
192
199inline double effectiveSampleRatio(const std::vector<double>& logWeights) {
200 const size_t M = logWeights.size();
201 if (M == 0) {
202 return 0.0;
203 }
204 double maxLog = logWeights.front();
205 for (const double lw : logWeights) {
206 maxLog = std::max(maxLog, lw);
207 }
208 double sumLin = 0.0;
209 for (const double lw : logWeights) {
210 sumLin += std::exp(lw - maxLog);
211 }
212 if (!(sumLin > 0.0)) {
213 return 0.0;
214 }
215 double sumSq = 0.0;
216 for (const double lw : logWeights) {
217 const double w = std::exp(lw - maxLog) / sumLin;
218 sumSq += w * w;
219 }
220 if (!(sumSq > 0.0)) {
221 return 0.0;
222 }
223 return 1.0 / (static_cast<double>(M) * sumSq);
224}
225
226} // namespace libstp::localization
void computeResampling(ResamplingAlgorithm method, const std::vector< double > &logWeights, std::mt19937 &rng, std::vector< size_t > &outIndexes, size_t outCount=0)
double effectiveSampleRatio(const std::vector< double > &logWeights)
@ Multinomial
Classical: M independent samples from the CDF.
@ Systematic
Single uniform offset + fixed-step traversal (lowest variance).
@ Stratified
Strata of size 1/M, one uniform draw per stratum.
@ Residual
Deterministic floor(M*w) part + multinomial remainder.