EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Statistics.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <utility>
13
18
19namespace Emrys::internal
20{
21
28template <typename MatrixType, typename DeviationsDerived, typename WeightsDerived>
29[[nodiscard]] MatrixType
30weightedCovariance(Eigen::MatrixBase<DeviationsDerived> const & deviations, Eigen::ArrayBase<WeightsDerived> const & w)
31{
32 using Scalar = typename MatrixType::Scalar;
33 using Deviations = MatrixOf<
34 Scalar,
35 MatrixType::RowsAtCompileTime,
36 DeviationsDerived::ColsAtCompileTime,
37 MatrixType::MaxRowsAtCompileTime,
38 DeviationsDerived::MaxColsAtCompileTime>;
39 Deviations const scaled = deviations * w.sqrt().matrix().asDiagonal();
40 auto cov = MatrixType{deviations.rows(), deviations.rows()};
41 if constexpr (MatrixType::MaxRowsAtCompileTime == 1)
42 {
43 // rankUpdate() would treat the single-row matrix as a column vector.
44 cov.setConstant(scaled.squaredNorm());
45 }
46 else
47 {
48 cov.setZero();
49 cov.template selfadjointView<Eigen::Lower>().rankUpdate(scaled);
50 for (Eigen::Index j = 1; j < cov.cols(); ++j)
51 {
52 for (Eigen::Index i = 0; i < j; ++i)
53 {
54 cov(i, j) = cov(j, i);
55 }
56 }
57 }
58 return cov;
59}
60
64template <typename Derived>
65void
66mirrorLower(Eigen::MatrixBase<Derived> & cov)
67{
68 cov.template triangularView<Eigen::StrictlyUpper>() = cov.transpose();
69}
70
85template <typename MomentsType, typename Space, typename MeanAt, typename LogWeightAt>
86[[nodiscard]] MomentsType
87pointMomentsIn(
88 Space const & space,
89 Eigen::Index count,
90 MeanAt meanAt,
91 LogWeightAt logWeightAt,
92 typename MomentsType::Scalar logTotal
93)
94{
95 using Scalar = typename MomentsType::Scalar;
96 using DifferenceVectorType = typename MomentsType::DifferenceVectorType;
97 auto const weight = [&](Eigen::Index i) {
98 return Eigen::numext::exp(Scalar(logWeightAt(i)) - logTotal);
99 };
100 auto const skip = [&](Eigen::Index i) {
101 return Scalar(logWeightAt(i)) == -Eigen::NumTraits<Scalar>::infinity();
102 };
103 auto result = MomentsType{valueDimension(space), differenceDimension(space)};
104 auto & cov = result.covarianceLower();
105 cov.setZero();
106 // Eigen's rank update loops at run time whatever the size, and so does a product assigned to a
107 // triangle, which Eigen sends to its rank update; at a fixed size the lazy outer product, of
108 // the same terms w v_j v_i, is assigned to the triangle coefficient by coefficient with the
109 // sizes fixed.
110 auto const addCov = [&](DifferenceVectorType const & deviation, Scalar w) {
111 if constexpr (DifferenceVectorType::SizeAtCompileTime != Eigen::Dynamic)
112 {
113 cov.template triangularView<Eigen::Lower>() += deviation.lazyProduct((w * deviation).transpose());
114 }
115 else
116 {
117 cov.template selfadjointView<Eigen::Lower>().rankUpdate(deviation, w);
118 }
119 };
120 if constexpr (hasPlainDifferences<Space>)
121 {
122 result.mean().setZero();
123 for (Eigen::Index i = 0; i < count; ++i)
124 {
125 if (!skip(i))
126 {
127 result.mean() += weight(i) * meanAt(i);
128 }
129 }
130 for (Eigen::Index i = 0; i < count; ++i)
131 {
132 if (!skip(i))
133 {
134 addCov(DifferenceVectorType{meanAt(i) - result.mean()}, weight(i));
135 }
136 }
137 normalizeValues(space, result.mean());
138 }
139 else
140 {
141 Eigen::Index reference = 0;
142 for (Eigen::Index i = 1; i < count; ++i)
143 {
144 if (logWeightAt(i) > logWeightAt(reference))
145 {
146 reference = i;
147 }
148 }
149 result.mean() = meanAt(reference);
150 auto shift = DifferenceVectorType{};
151 for (int pass = 0; pass < 2; ++pass)
152 {
153 shift.setZero(differenceDimension(space));
154 for (Eigen::Index i = 0; i < count; ++i)
155 {
156 if (!skip(i))
157 {
158 shift += weight(i) * Emrys::minus(space, meanAt(i), result.mean());
159 }
160 }
161 if (pass == 1)
162 {
163 for (Eigen::Index i = 0; i < count; ++i)
164 {
165 if (!skip(i))
166 {
167 addCov(DifferenceVectorType{Emrys::minus(space, meanAt(i), result.mean()) - shift}, weight(i));
168 }
169 }
170 }
171 result.mean() = Emrys::plus(space, result.mean(), shift);
172 }
173 }
174 return result;
175}
176
184template <typename MomentsType, typename Space, typename StatesDerived, typename WeightsDerived>
185[[nodiscard]] MomentsType
186weightedMomentsIn(
187 Space const & space,
188 Eigen::MatrixBase<StatesDerived> const & states,
189 Eigen::ArrayBase<WeightsDerived> const & logWeights,
190 typename MomentsType::Scalar logTotal
191)
192{
193 MomentsType result = pointMomentsIn<MomentsType>(
194 space,
195 states.cols(),
196 [&](Eigen::Index i) {
197 return states.col(i);
198 },
199 [&](Eigen::Index i) {
200 return logWeights.derived()(i);
201 },
202 logTotal
203 );
204 mirrorLower(result.covarianceLower());
205 return result;
206}
207
215template <typename MomentsType, ExecutionPolicy P, typename Space, typename StatesDerived, typename WeightsDerived>
216[[nodiscard]] MomentsType
217weightedMomentsIn(
218 P && policy,
219 Space const & space,
220 Eigen::MatrixBase<StatesDerived> const & states,
221 Eigen::ArrayBase<WeightsDerived> const & logWeights,
222 typename MomentsType::Scalar logTotal
223)
224{
225 if constexpr (isSequential<P>)
226 {
227 return weightedMomentsIn<MomentsType>(space, states, logWeights, logTotal);
228 }
229 else
230 {
231 using Scalar = typename MomentsType::Scalar;
232 using VectorType = typename MomentsType::VectorType;
233 using DifferenceVectorType = typename MomentsType::DifferenceVectorType;
234 using MatrixType = typename MomentsType::MatrixType;
236 using Weights = Eigen::Array<Scalar, Eigen::Dynamic, 1>;
237 Eigen::Index const count = states.cols();
238 auto const weightsOf = [&](Eigen::Index begin, Eigen::Index length) {
239 return Weights{(logWeights.derived().segment(begin, length) - logTotal).exp()};
240 };
241 auto const add = [](auto a, auto const & b) {
242 a += b;
243 return a;
244 };
245 // The weighted covariance of the differences of the points from center, less shift.
246 auto const covarianceAbout = [&](auto const & center, DifferenceVectorType const & shift) {
247 return reduceRanges(
248 policy,
249 count,
250 [&](Eigen::Index begin, Eigen::Index length) {
251 Deviations deviations = Emrys::minus(space, states.middleCols(begin, length), center);
252 deviations.colwise() -= shift;
253 return weightedCovariance<MatrixType>(deviations, weightsOf(begin, length));
254 },
255 add
256 );
257 };
258 auto result = MomentsType{valueDimension(space), differenceDimension(space)};
259 if constexpr (hasPlainDifferences<Space>)
260 {
261 result.mean() = reduceRanges(
262 policy,
263 count,
264 [&](Eigen::Index begin, Eigen::Index length) {
265 return VectorType{states.middleCols(begin, length) * weightsOf(begin, length).matrix()};
266 },
267 add
268 );
269 result.covarianceLower() =
270 covarianceAbout(result.mean(), DifferenceVectorType::Zero(differenceDimension(space)));
271 normalizeValues(space, result.mean());
272 }
273 else
274 {
275 // The first of the heaviest points, as Eigen's maxCoeff() finds it.
276 struct Heaviest
277 {
278 Scalar logWeight;
279 Eigen::Index index;
280 };
281
282 Heaviest const heaviest = reduceRanges(
283 policy,
284 count,
285 [&](Eigen::Index begin, Eigen::Index length) {
286 Eigen::Index index = 0;
287 Scalar const logWeight = logWeights.derived().segment(begin, length).maxCoeff(&index);
288 return Heaviest{logWeight, begin + index};
289 },
290 [](Heaviest const & a, Heaviest const & b) {
291 return b.logWeight > a.logWeight ? b : a;
292 }
293 );
294 auto const shiftFrom = [&](VectorType const & center) {
295 return reduceRanges(
296 policy,
297 count,
298 [&](Eigen::Index begin, Eigen::Index length) {
299 return DifferenceVectorType{
300 Emrys::minus(space, states.middleCols(begin, length), center)
301 * weightsOf(begin, length).matrix()
302 };
303 },
304 add
305 );
306 };
307 VectorType const reference = states.col(heaviest.index);
308 VectorType const center = Emrys::plus(space, reference, shiftFrom(reference));
309 DifferenceVectorType const shift = shiftFrom(center);
310 result.mean() = Emrys::plus(space, center, shift);
311 result.covarianceLower() = covarianceAbout(center, shift);
312 }
313 return result;
314 }
315}
316
327template <typename MomentsType, typename Space, typename MeanAt, typename CovAt, typename LogWeightAt>
328[[nodiscard]] MomentsType
329mixtureMomentsIn(
330 Space const & space,
331 Eigen::Index count,
332 MeanAt meanAt,
333 CovAt covAt,
334 LogWeightAt logWeightAt,
335 typename MomentsType::Scalar logTotal
336)
337{
338 using Scalar = typename MomentsType::Scalar;
339 MomentsType result = pointMomentsIn<MomentsType>(space, count, meanAt, logWeightAt, logTotal);
340 for (Eigen::Index i = 0; i < count; ++i)
341 {
342 if (Scalar(logWeightAt(i)) != -Eigen::NumTraits<Scalar>::infinity())
343 {
344 result.covarianceLower().template triangularView<Eigen::Lower>() +=
345 Eigen::numext::exp(Scalar(logWeightAt(i)) - logTotal) * covAt(i);
346 }
347 }
348 mirrorLower(result.covarianceLower());
349 return result;
350}
351
357template <typename WeightsDerived>
358[[nodiscard]] typename WeightsDerived::Scalar
359ess(Eigen::ArrayBase<WeightsDerived> const & logWeights, typename WeightsDerived::Scalar logTotal)
360{
361 using Scalar = typename WeightsDerived::Scalar;
362 return Scalar(1) / (logWeights - logTotal).exp().square().sum();
363}
364
368template <ExecutionPolicy P, typename WeightsDerived>
369[[nodiscard]] typename WeightsDerived::Scalar
370ess(P && policy, Eigen::ArrayBase<WeightsDerived> const & logWeights, typename WeightsDerived::Scalar logTotal)
371{
372 using Scalar = typename WeightsDerived::Scalar;
373 if constexpr (isSequential<P>)
374 {
375 return ess(logWeights, logTotal);
376 }
377 else
378 {
379 Scalar const sumOfSquares = reduceRanges(
380 std::forward<P>(policy),
381 logWeights.size(),
382 [&](Eigen::Index begin, Eigen::Index length) {
383 return Scalar((logWeights.derived().segment(begin, length) - logTotal).exp().square().sum());
384 },
385 [](Scalar a, Scalar b) {
386 return a + b;
387 }
388 );
389 return Scalar(1) / sumOfSquares;
390 }
391}
392
393} // namespace Emrys::internal
Execution policies (decisions.md [execution-policies]): execution::sequential, the ExecutionPolicy co...
The macros every header includes first.
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Eigen::Matrix< Scalar, valueDimensionAtCompileTime< Space >, Eigen::Dynamic,(maxValueDimensionAtCompileTime< Space >==1 ? Eigen::RowMajor :Eigen::ColMajor), maxValueDimensionAtCompileTime< Space >, Eigen::Dynamic > SampleSet
A matrix whose columns are samples in Space (see Spaces), with the space's size and storage bound.
Definition SampleSet.h:33
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
auto minus(S const &space, Eigen::MatrixBase< ADerived > const &a, Eigen::MatrixBase< BDerived > const &b)
Definition Space.h:715
Eigen::Index differenceDimension(S const &space)
Definition Space.h:284
void normalizeValues(S const &space, Eigen::MatrixBase< Derived > const &block)
Brings the values in block (rows are space's coordinates, columns samples) into canonical form,...
Definition Space.h:331
constexpr bool hasPlainDifferences
Whether the differences of S are plain: its difference space has no normalize(), and it has neither p...
Definition Space.h:311