EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
StudentT.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <cmath>
11#include <concepts>
12#include <expected>
13#include <optional>
14#include <random>
15#include <utility>
16
30
31namespace Emrys
32{
33
44
72template <Space Space_, RealScalar Scalar_ = double>
73class StudentT
74{
75public:
76
79
81 using Scalar = Scalar_;
82 using Index = Eigen::Index;
83 using SpaceType = Space_;
84 using VectorType = typename MomentsType::VectorType;
85 using MatrixType = typename MomentsType::MatrixType;
86
88 static constexpr int ValueDimensionAtCompileTime = MomentsType::ValueDimensionAtCompileTime;
89
91 static constexpr int MaxValueDimensionAtCompileTime = MomentsType::MaxValueDimensionAtCompileTime;
92
94 static constexpr int DifferenceDimensionAtCompileTime = MomentsType::DifferenceDimensionAtCompileTime;
95
97 static constexpr int MaxDifferenceDimensionAtCompileTime = MomentsType::MaxDifferenceDimensionAtCompileTime;
98
103 template <MatrixExpression Location, typename ScaleDerived>
104 [[nodiscard]] static std::expected<StudentT, StudentTError>
105 create(Space_ space, Location && location, Eigen::MatrixBase<ScaleDerived> const & scale, Scalar dof)
106 {
107 if (auto const error = checkDegreesOfFreedom(dof))
108 {
109 return std::unexpected{*error};
110 }
111 auto gaussian = ScaleGaussianType{
112 std::move(space),
113 std::forward<Location>(location),
114 MatrixType{scale.template selfadjointView<Eigen::Lower>()}
115 };
116 if (auto result = gaussian.factorize(); !result)
117 {
118 return std::unexpected{
121 };
122 }
123 return StudentT{std::move(gaussian), dof};
124 }
125
127 template <MatrixExpression Location, typename ScaleDerived>
128 [[nodiscard]] static std::expected<StudentT, StudentTError>
129 create(Location && location, Eigen::MatrixBase<ScaleDerived> const & scale, Scalar dof)
130 {
131 return create(Space_{}, std::forward<Location>(location), scale, dof);
132 }
133
135 [[nodiscard]] static std::expected<StudentT, StudentTError>
137 {
138 return create(gaussian.space(), gaussian.mean(), gaussian.covarianceLower(), dof);
139 }
140
142 [[nodiscard]] Index
144 {
145 return m_gaussian.valueDimension();
146 }
147
149 [[nodiscard]] Index
151 {
152 return m_gaussian.differenceDimension();
153 }
154
156 [[nodiscard]] Space_ const &
157 space() const
158 {
159 return m_gaussian.space();
160 }
161
163 [[nodiscard]] VectorType const &
164 location() const
165 {
166 return m_gaussian.mean();
167 }
168
170 [[nodiscard]] MatrixType const &
171 scale() const
172 {
173 return m_gaussian.covarianceLower();
174 }
175
177 [[nodiscard]] Scalar
178 dof() const
179 {
180 return m_dof;
181 }
182
186 [[nodiscard]] typename ScaleGaussianType::DensityType
188 {
189 auto const density = m_gaussian.toDensity();
190 return *density;
191 }
192
197 template <typename Derived>
198 [[nodiscard]] Scalar
199 logDensity(Eigen::MatrixBase<Derived> const & x) const
200 {
201 return logDensityAtSquaredDistance(scaleDensity().squaredMahalanobisDistance(x));
202 }
203
208 template <typename Derived>
209 [[nodiscard]] Scalar
210 density(Eigen::MatrixBase<Derived> const & x) const
211 {
212 return Eigen::numext::exp(logDensity(x));
213 }
214
218 [[nodiscard]] std::expected<MomentsType, MomentsError>
219 moments() const
220 {
221 if (!(m_dof > Scalar(2)))
222 {
223 return std::unexpected{MomentsError::Infinite};
224 }
225 return MomentsType{location(), MatrixType{(m_dof / (m_dof - Scalar(2))) * scale()}};
226 }
227
231 [[nodiscard]] Scalar
232 logDensityAtSquaredDistance(Scalar squaredDistance) const
233 {
234 return m_logNormalizer
235 - Scalar(0.5) * (m_dof + internal::scalarFromCount<Scalar>(differenceDimension()))
236 * logOnePlus<Scalar>(squaredDistance / m_dof);
237 }
238
240 template <typename Path>
241 [[nodiscard]] auto
242 marginal() const
243 {
244 return internal::marginalOverPath<Path>(space(), [&](auto const & target, auto subspace) {
245 return restricted(target, std::move(subspace));
246 });
247 }
248
250 template <typename Path>
251 [[nodiscard]] auto
252 marginal(Index element) const
253 {
254 return internal::marginalOverElement<Path>(space(), element, [&](auto const & target, auto subspace) {
255 return restricted(target, std::move(subspace));
256 });
257 }
258
271 template <typename GivenPath, typename Derived>
272 [[nodiscard]] auto
273 conditional(Eigen::MatrixBase<Derived> const & values) const
274 {
275 return internal::tagConditional<GivenPath>(
276 space(),
277 [&](auto const & t, auto const & g, auto subspace, auto const & givenSpace) {
278 return conditioned(t, g, values, std::move(subspace), givenSpace);
279 }
280 );
281 }
282
283private:
284
286 [[nodiscard]] static std::optional<StudentTError>
287 checkDegreesOfFreedom(Scalar dof)
288 {
289 if (!isFinite(dof))
290 {
292 }
293 if (!(dof > Scalar(0)))
294 {
296 }
297 return std::nullopt;
298 }
299
301 template <typename Target, typename SubSpace>
302 [[nodiscard]] StudentT<SubSpace, Scalar>
303 restricted(Target const & target, SubSpace subspace) const
304 {
305 using Result = StudentT<SubSpace, Scalar>;
306 auto result = Result::create(
307 std::move(subspace),
308 location()(target.values),
309 typename Result::MatrixType{internal::lowerSymmetricBlock(scale(), target.differences, target.differences)},
310 m_dof
311 );
312 EMRYS_FILTER_ASSERT(result.has_value() && "a principal block of a positive definite scale should factorize");
313 return std::move(*result);
314 }
315
319 template <typename Target, typename Given, typename Derived, typename SubSpace, typename GivenSpace>
320 [[nodiscard]] std::expected<StudentT<SubSpace, Scalar>, StudentTError>
321 conditioned(
322 Target const & target,
323 Given const & given,
324 Eigen::MatrixBase<Derived> const & values,
325 SubSpace subspace,
326 GivenSpace const & givenSpace
327 ) const
328 {
329 using Result = StudentT<SubSpace, Scalar>;
330 auto gaussian =
331 internal::conditionMomentsWithEvidence(location(), scale(), target, given, values, givenSpace, subspace);
332 if (!gaussian)
333 {
334 return std::expected<Result, StudentTError>{std::unexpect, StudentTError::NotPositiveDefinite};
335 }
336 Scalar const givenSize = Scalar(given.differences.size());
337 Scalar const stretch = (m_dof + gaussian->squaredDistance) / (m_dof + givenSize);
338 return Result::create(
339 std::move(subspace),
340 std::move(gaussian->moments.mean),
341 typename Result::MatrixType{stretch * gaussian->moments.cov},
342 m_dof + givenSize
343 );
344 }
345
350 StudentT(ScaleGaussianType gaussian, Scalar dof)
351 : m_gaussian{std::move(gaussian)}
352 , m_dof{dof}
353 {
354 Scalar const d = internal::scalarFromCount<Scalar>(differenceDimension());
355 Scalar const logDeterminant = scaleDensity().logDeterminant();
356 m_logNormalizer = logGamma<Scalar>(Scalar(0.5) * (m_dof + d)) - logGamma<Scalar>(Scalar(0.5) * m_dof)
357 - Scalar(0.5) * d * Eigen::numext::log(Scalar(m_dof * pi<Scalar>()))
358 - Scalar(0.5) * logDeterminant;
359 }
360
361 ScaleGaussianType m_gaussian;
362 Scalar m_dof;
363 Scalar m_logNormalizer{};
364};
365
366namespace internal
367{
368
370template <typename Space, typename Scalar = double>
371struct StudentTDraws
372{
374 Eigen::Array<Scalar, Eigen::Dynamic, 1>
375 squaredDistance;
376};
377
382template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
383[[nodiscard]] StudentTDraws<Space, Scalar>
384drawStudentT(StudentT<Space, Scalar> const & t, Eigen::Index n, G & rng)
385{
386 EMRYS_FILTER_ASSERT(n >= 0);
387 using Normals = SampleSet<DifferenceSpace<Space>, Scalar>;
388 Normals const z = standardNormal<Normals>(t.differenceDimension(), n, rng);
389 auto chiSquared = std::chi_squared_distribution<Scalar>{t.dof()};
390 auto stretch = Eigen::Array<Scalar, Eigen::Dynamic, 1>{n}; // sqrt(ν / u) for each draw
391 for (Eigen::Index j = 0; j < n; ++j)
392 {
393 stretch(j) = Eigen::numext::sqrt(t.dof() / chiSquared(rng));
394 }
395 Normals offsets = multiply(asTriangular<Eigen::Lower>(t.scaleDensity().llt().matrixLLT()), z);
396 for (Eigen::Index j = 0; j < n; ++j)
397 {
398 offsets.col(j) *= stretch(j);
399 }
400 return {
401 Emrys::plus(t.space(), t.location(), offsets),
402 z.colwise().squaredNorm().transpose().array() * stretch.square()
403 };
404}
405
407template <typename Space, typename Scalar = double>
408struct StudentTDraw
409{
410 typename Moments<Space, Scalar>::VectorType sample;
411 Scalar squaredDistance = 0;
412};
413
417template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
418[[nodiscard]] StudentTDraw<Space, Scalar>
419drawOneStudentT(StudentT<Space, Scalar> const & t, G & rng)
420{
421 using Difference = DifferenceVector<Space, Scalar>;
422 Difference const z = standardNormalVector<Difference>(t.differenceDimension(), rng);
423 Scalar const stretch = Eigen::numext::sqrt(t.dof() / std::chi_squared_distribution<Scalar>{t.dof()}(rng));
424 Difference offset = multiply(asTriangular<Eigen::Lower>(t.scaleDensity().llt().matrixLLT()), z);
425 offset *= stretch;
426 return {
427 typename Moments<Space, Scalar>::VectorType{Emrys::plus(t.space(), t.location(), offset)},
428 z.squaredNorm() * stretch * stretch
429 };
430}
431
432} // namespace internal
433
440template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
441[[nodiscard]] SampleSet<Space, Scalar>
442drawSamples(StudentT<Space, Scalar> const & t, Eigen::Index n, G & rng)
443{
444 auto draws = internal::drawStudentT(t, n, rng);
445 return std::move(draws.samples);
446}
447
453template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
454[[nodiscard]] typename Moments<Space, Scalar>::VectorType
456{
457 return internal::drawOneStudentT(t, rng).sample;
458}
459
469template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
470[[nodiscard]] SamplesWithLogDensity<Space, Scalar>
471drawSamplesWithLogDensity(StudentT<Space, Scalar> const & t, Eigen::Index n, G & rng)
472{
473 auto draws = internal::drawStudentT(t, n, rng);
474 auto result = SamplesWithLogDensity<Space, Scalar>{std::move(draws.samples), {}};
475 result.logDensity.resize(n);
476 if constexpr (isLinear<Space>)
477 {
478 for (Eigen::Index j = 0; j < n; ++j)
479 {
480 result.logDensity(j) = t.logDensityAtSquaredDistance(draws.squaredDistance(j));
481 }
482 }
483 else
484 {
485 for (Eigen::Index j = 0; j < n; ++j)
486 {
487 result.logDensity(j) = t.logDensity(result.samples.col(j));
488 }
489 }
490 return result;
491}
492
498template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
499[[nodiscard]] SampleWithLogDensity<Space, Scalar>
501{
502 auto draw = internal::drawOneStudentT(t, rng);
503 auto result = SampleWithLogDensity<Space, Scalar>{std::move(draw.sample), 0};
504 if constexpr (isLinear<Space>)
505 {
506 result.logDensity = t.logDensityAtSquaredDistance(draw.squaredDistance);
507 }
508 else
509 {
510 result.logDensity = t.logDensity(result.sample);
511 }
512 return result;
513}
514
515} // namespace Emrys
516
517#ifndef EMRYS_FILTER_NO_IO
518
519namespace Emrys
520{
521
522namespace internal
523{
524
526inline void
527describe(std::ostream & stream, StudentTError error, Indent indent)
528{
529 stream << indent;
530 switch (error)
531 {
533 stream << "the scale matrix is not positive definite";
534 break;
536 stream << "the scale matrix is not finite";
537 break;
539 stream << "the degrees of freedom are not positive";
540 break;
542 stream << "the degrees of freedom are not finite";
543 break;
544 }
545}
546
547} // namespace internal
548
549} // namespace Emrys
550
551#endif // EMRYS_FILTER_NO_IO
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The Gaussian: its parameters GaussianParams, the Gaussian that evaluates the density,...
What marginals and conditionals are built from: resolving Eigen index sets against a space; the share...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
Moments: the mean and covariance that a Gaussian carries and that a mixture or a sample set is moment...
Products of fixed-size matrices by Eigen's fixed-size product at every size: multiply() is by Eigen'...
Printing: operator<< and std::formatter for every distribution, every filter (which prints its name a...
SampleSet: a matrix whose columns are samples in a space, which the samplers return and the unscented...
Drawing samples: SamplesWithLogDensity and SampleWithLogDensity, the Gaussian draws drawSamples(),...
Solving by a factorization of a small matrix: transposedSolve() gives , the shape of a Kalman gain an...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Subspaces, and growing and shrinking blocks (decisions.md [several-resizable-blocks]).
A Gaussian distribution: a mean and covariance in a space.
Definition Gaussian.h:259
Moments< Space_, Scalar_ > MomentsType
Definition Gaussian.h:262
Gaussian< Space_, Scalar_ > DensityType
Definition Gaussian.h:269
MatrixType const & covarianceLower() const
Definition Gaussian.h:543
VectorType const & mean() const
Definition Gaussian.h:533
Space_ const & space() const
Definition Gaussian.h:519
Scalar logDeterminant() const
Definition Gaussian.h:1130
ValueVector< Space_, Scalar > VectorType
a value, such as the mean
Definition Moments.h:74
The multivariate Student-t distribution: a Gaussian with heavier tails.
Definition StudentT.h:74
Space_ const & space() const
Definition StudentT.h:157
GaussianParams< Space_, Scalar_ > ScaleGaussianType
, which holds the parameters
Definition StudentT.h:78
static std::expected< StudentT, StudentTError > create(Location &&location, Eigen::MatrixBase< ScaleDerived > const &scale, Scalar dof)
create() in the default space.
Definition StudentT.h:129
static constexpr int MaxDifferenceDimensionAtCompileTime
a bound on the length of a difference, or Eigen::Dynamic
Definition StudentT.h:97
MatrixType const & scale() const
Definition StudentT.h:171
static std::expected< StudentT, StudentTError > create(Space_ space, Location &&location, Eigen::MatrixBase< ScaleDerived > const &scale, Scalar dof)
Definition StudentT.h:105
typename ScaleGaussianType::MomentsType MomentsType
a mean and covariance together
Definition StudentT.h:80
auto marginal() const
marginal() of the subspace named by the tag path Path (see idx()), in that subspace.
Definition StudentT.h:242
typename MomentsType::VectorType VectorType
a value
Definition StudentT.h:84
Scalar logDensityAtSquaredDistance(Scalar squaredDistance) const
Definition StudentT.h:232
Space_ SpaceType
the space the values live in
Definition StudentT.h:83
ScaleGaussianType::DensityType scaleDensity() const
Definition StudentT.h:187
Scalar_ Scalar
the scalar type
Definition StudentT.h:81
VectorType const & location() const
Definition StudentT.h:164
static constexpr int MaxValueDimensionAtCompileTime
a bound on the length of a value, or Eigen::Dynamic
Definition StudentT.h:91
Eigen::Index Index
Eigen's index type.
Definition StudentT.h:82
Index differenceDimension() const
Definition StudentT.h:150
static constexpr int DifferenceDimensionAtCompileTime
the length of a difference and the rows of the scale matrix, or Eigen::Dynamic
Definition StudentT.h:94
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition StudentT.h:85
std::expected< MomentsType, MomentsError > moments() const
Definition StudentT.h:219
Scalar dof() const
Definition StudentT.h:178
auto conditional(Eigen::MatrixBase< Derived > const &values) const
Definition StudentT.h:273
Scalar density(Eigen::MatrixBase< Derived > const &x) const
Definition StudentT.h:210
static std::expected< StudentT, StudentTError > create(ScaleGaussianType const &gaussian, Scalar dof)
create() from the location and scale of gaussian, which keeps its space.
Definition StudentT.h:136
Scalar logDensity(Eigen::MatrixBase< Derived > const &x) const
Definition StudentT.h:199
static constexpr int ValueDimensionAtCompileTime
the length of a value, or Eigen::Dynamic
Definition StudentT.h:88
auto marginal(Index element) const
marginal() of one repetition of a repeated block; see GaussianParams::marginal(element).
Definition StudentT.h:252
Index valueDimension() const
Definition StudentT.h:143
Scalar pi()
Definition Math.h:145
Scalar logOnePlus(std::type_identity_t< Scalar > const &x)
Definition Math.h:247
Scalar logGamma(std::type_identity_t< Scalar > const &x)
Definition Math.h:216
bool isFinite(T const &x)
Definition Math.h:305
@ NotPositiveDefinite
the covariance is not positive definite, so it has no Cholesky factor or inverse
Definition NumericError.h:31
@ NonFinite
a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)
Definition NumericError.h:33
SampleWithLogDensity< Space, Scalar > drawSampleWithLogDensity(Gamma< Space, Scalar > const &gamma, G &rng)
Definition Gamma.h:546
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 drawSample(Categorical< Space, Scalar > const &distribution, G &rng)
Definition Categorical.h:1111
StudentTError
Reasons a location, scale matrix, and degrees of freedom fail to define a StudentT.
Definition StudentT.h:38
std::vector< Eigen::Index > drawSamples(Categorical< Space, Scalar > const &distribution, Eigen::Index n, G &rng)
Definition Categorical.h:1087
SamplesWithLogDensity< Space, Scalar > drawSamplesWithLogDensity(Gamma< Space, Scalar > const &gamma, Eigen::Index n, G &rng)
Definition Gamma.h:529
@ Infinite
the distribution has no finite mean or covariance, such as a Student-t with
Definition Moments.h:33
@ NotPositiveDegreesOfFreedom
the degrees of freedom are zero or negative
Definition StudentT.h:41
@ NotPositiveDefinite
the scale matrix is not positive definite
Definition StudentT.h:39
@ NonFiniteScale
the scale matrix is not finite
Definition StudentT.h:40
@ NonFiniteDegreesOfFreedom
the degrees of freedom are NaN or infinite
Definition StudentT.h:42
GivenBlock< Space, P, Value > given(Space const &space, Value const &value)
Definition Measurement.h:917
auto plus(S const &space, Eigen::MatrixBase< XDerived > const &x, Eigen::MatrixBase< DDerived > const &d)
Definition Space.h:675
ValueVector< DifferenceSpace< S >, Scalar > DifferenceVector
A difference in S as an Eigen column vector of Scalar, the type of minus() and of the offset plus() t...
Definition Space.h:650
constexpr bool isLinear
Whether S is linear: its values and its differences are both plain vectors.
Definition Space.h:321
One sample in Space, in a vector sized by the space, together with the log of the density it was draw...
Definition Sampling.h:50
Samples in Space together with the log of the density they were drawn from, evaluated at each.
Definition Sampling.h:36
Eigen::Array< Scalar, Eigen::Dynamic, 1 > logDensity
logDensity(i) is the log-density at samples.col(i)
Definition Sampling.h:38