EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Gamma.h
Go to the documentation of this file.
1#pragma once
2
10
11#include <cmath>
12#include <concepts>
13#include <expected>
14#include <limits>
15#include <optional>
16#include <random>
17#include <array>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21#include <vector>
22
32
33namespace Emrys
34{
35
44
45namespace internal
46{
47
48template <typename S>
49consteval bool
50holdsHalfLine();
51
53template <typename Blocks>
54struct AllHoldHalfLine;
55
57template <typename... Blocks>
58struct AllHoldHalfLine<std::tuple<Blocks...>> : std::bool_constant<(holdsHalfLine<Blocks>() && ...)>
59{};
60
65template <typename S>
66consteval bool
67holdsHalfLine()
68{
69 if constexpr (IsComposite<S>)
70 {
71 return AllHoldHalfLine<CompositeBlocks<S>>::value;
72 }
73 else if constexpr (IsRepeated<S>)
74 {
75 return holdsHalfLine<RepeatedBlock<S>>();
76 }
77 else
78 {
79 constexpr auto support = leafSupport<S>();
80 return support && !support->periodic && support->lower <= 0
81 && support->upper == std::numeric_limits<double>::infinity();
82 }
83}
84
85} // namespace internal
86
118template <Space Space_, RealScalar Scalar_ = double>
119class Gamma
120{
121public:
122
123 using Scalar = Scalar_;
124 using Index = Eigen::Index;
125 using SpaceType = Space_;
129
132
135
138
141
142 static_assert(
143 internal::hasEqualSizes<Space_>,
144 "a gamma distribution needs a space whose values and differences have the same coordinates"
145 );
146 static_assert(
147 internal::holdsHalfLine<Space_>(),
148 "a gamma distribution needs every coordinate to contain [0, inf) without folding it"
149 );
150
156 template <typename Shape, typename Scale>
157 [[nodiscard]] static std::expected<Gamma, GammaError>
158 create(Space_ space, Shape const & shape, Scale const & scale)
159 {
160 Index const n = parameterSize(shape, scale, Emrys::valueDimension(space));
161 internal::fitValueDimension(space, n);
162 VectorType shapes = broadcast(shape, n);
163 VectorType scales = broadcast(scale, n);
164 if (auto const error = check(shapes, scales))
165 {
166 return std::unexpected{*error};
167 }
168 return Gamma{std::move(space), std::move(shapes), std::move(scales)};
169 }
170
172 template <typename Shape, typename Scale>
173 requires internal::SizedByLength<Space_>
174 [[nodiscard]] static std::expected<Gamma, GammaError>
175 create(Shape const & shape, Scale const & scale)
176 {
177 return create(Space_{}, shape, scale);
178 }
179
183 template <typename Scale>
184 [[nodiscard]] static std::expected<Gamma, GammaError>
185 exponential(Space_ space, Scale const & scale)
186 {
187 return create(std::move(space), Scalar(1), scale);
188 }
189
191 template <typename Scale>
192 [[nodiscard]] static std::expected<Gamma, GammaError>
193 exponential(Scale const & scale)
194 {
195 return exponential(Space_{}, scale);
196 }
197
201 template <typename Dof>
202 [[nodiscard]] static std::expected<Gamma, GammaError>
203 chiSquared(Space_ space, Dof const & dof)
204 {
205 Index const n = parameterSize(dof, Scalar(2), Emrys::valueDimension(space));
206 return create(std::move(space), VectorType{Scalar(0.5) * broadcast(dof, n)}, Scalar(2));
207 }
208
210 template <typename Dof>
211 [[nodiscard]] static std::expected<Gamma, GammaError>
212 chiSquared(Dof const & dof)
213 {
214 return chiSquared(Space_{}, dof);
215 }
216
222 template <typename Mean, typename StdDev>
223 [[nodiscard]] static std::expected<Gamma, GammaError>
224 fromMeanAndStdDev(Space_ space, Mean const & mean, StdDev const & stdDev)
225 {
226 Index const n = parameterSize(mean, stdDev, Emrys::valueDimension(space));
227 VectorType const mu = broadcast(mean, n);
228 VectorType const sigma = broadcast(stdDev, n);
229 if (!allFinite(mu) || !allFinite(sigma))
230 {
231 return std::unexpected{GammaError::NonFinite};
232 }
233 if (!(mu.array() > Scalar(0)).all() || !(sigma.array() > Scalar(0)).all())
234 {
235 return std::unexpected{GammaError::NotPositive};
236 }
237 return create(
238 std::move(space),
239 VectorType{(mu.array() / sigma.array()).square().matrix()},
240 VectorType{(sigma.array().square() / mu.array()).matrix()}
241 );
242 }
243
245 template <typename Mean, typename StdDev>
246 [[nodiscard]] static std::expected<Gamma, GammaError>
247 fromMeanAndStdDev(Mean const & mean, StdDev const & stdDev)
248 {
249 return fromMeanAndStdDev(Space_{}, mean, stdDev);
250 }
251
253 [[nodiscard]] Index
255 {
256 return m_shape.size();
257 }
258
260 [[nodiscard]] Index
262 {
263 return m_shape.size();
264 }
265
267 [[nodiscard]] Space_ const &
268 space() const
269 {
270 return m_space;
271 }
272
274 [[nodiscard]] VectorType const &
275 shape() const
276 {
277 return m_shape;
278 }
279
281 [[nodiscard]] VectorType const &
282 scale() const
283 {
284 return m_scale;
285 }
286
288 template <typename Derived>
289 [[nodiscard]] Scalar
290 logDensity(Eigen::MatrixBase<Derived> const & x) const
291 {
292 static_assert(Derived::IsVectorAtCompileTime, "logDensity() expects a vector");
293 EMRYS_FILTER_ASSERT(x.size() == valueDimension());
294 Scalar result = m_logNormalizer;
295 for (Index i = 0; i < valueDimension(); ++i)
296 {
297 Scalar const xi = x(i);
298 if (xi < Scalar(0))
299 {
300 return -std::numeric_limits<Scalar>::infinity();
301 }
302 // Skip the power term for shape 1, where it is 0 ยท log 0 at the origin.
303 if (m_shape(i) != Scalar(1))
304 {
305 result += (m_shape(i) - Scalar(1)) * Eigen::numext::log(xi);
306 }
307 result -= xi / m_scale(i);
308 }
309 return result;
310 }
311
313 template <typename Derived>
314 [[nodiscard]] Scalar
315 density(Eigen::MatrixBase<Derived> const & x) const
316 {
317 return Eigen::numext::exp(logDensity(x));
318 }
319
321 [[nodiscard]] MomentsType
322 moments() const
323 {
325 result.mean() = m_shape.cwiseProduct(m_scale);
326 result.covarianceLower() = result.mean().cwiseProduct(m_scale).asDiagonal();
327 return result;
328 }
329
331 template <typename Path>
332 [[nodiscard]] auto
333 marginal() const
334 {
335 return internal::marginalOverPath<Path>(m_space, [&](auto const & target, auto space) {
336 return restricted(target.values, std::move(space));
337 });
338 }
339
341 template <typename Path>
342 [[nodiscard]] auto
343 marginal(Index element) const
344 {
345 return internal::marginalOverElement<Path>(m_space, element, [&](auto const & target, auto space) {
346 return restricted(target.values, std::move(space));
347 });
348 }
349
356 template <typename GivenPath, typename Derived>
357 [[nodiscard]] auto
358 conditional(Eigen::MatrixBase<Derived> const & values) const
359 {
360 return internal::tagConditional<GivenPath>(
361 m_space,
362 [&](auto const & t, auto const & g, auto space, auto const &) {
363 EMRYS_FILTER_ASSERT(values.size() == g.values.size());
364 return restricted(t.values, std::move(space));
365 }
366 );
367 }
368
369private:
370
372 template <Space, RealScalar>
373 friend class Gamma;
374
379 : m_space{std::move(space)}
380 , m_shape{std::move(shape)}
381 , m_scale{std::move(scale)}
382 {
383 for (Index i = 0; i < m_shape.size(); ++i)
384 {
385 m_logNormalizer -= logGamma<Scalar>(m_shape(i)) + m_shape(i) * Eigen::numext::log(m_scale(i));
386 }
387 }
388
390 [[nodiscard]] static std::optional<GammaError>
391 check(VectorType const & shape, VectorType const & scale)
392 {
393 if (!allFinite(shape) || !allFinite(scale))
394 {
396 }
397 if (!(shape.array() > Scalar(0)).all() || !(scale.array() > Scalar(0)).all())
398 {
400 }
401 return std::nullopt;
402 }
403
405 template <typename Target, typename SubSpace>
406 [[nodiscard]] Gamma<SubSpace, Scalar>
407 restricted(Target const & target, SubSpace space) const
408 {
409 using ResultVector = typename Gamma<SubSpace, Scalar>::VectorType;
410 return Gamma<SubSpace, Scalar>{std::move(space), ResultVector{m_shape(target)}, ResultVector{m_scale(target)}};
411 }
412
414 template <typename P>
415 static constexpr bool isScalar = std::is_arithmetic_v<P>;
416
418 template <typename A, typename B>
419 [[nodiscard]] static Index
420 parameterSize(A const & a, B const & b, Index spaceSize)
421 {
422 if constexpr (!isScalar<A>)
423 {
424 if constexpr (!isScalar<B>)
425 {
426 EMRYS_FILTER_ASSERT(a.size() == b.size() && "the parameters differ in size");
427 }
428 return a.size();
429 }
430 else if constexpr (!isScalar<B>)
431 {
432 return b.size();
433 }
434 else
435 {
436 return spaceSize;
437 }
438 }
439
441 template <typename P>
442 [[nodiscard]] static VectorType
443 broadcast(P const & p, Index n)
444 {
445 if constexpr (isScalar<P>)
446 {
447 return VectorType::Constant(n, Scalar(p));
448 }
449 else
450 {
451 EMRYS_FILTER_ASSERT(p.size() == n);
452 return VectorType{p};
453 }
454 }
455
456 EMRYS_FILTER_NO_UNIQUE_ADDRESS Space_ m_space;
457 VectorType m_shape;
458 VectorType m_scale;
459 Scalar m_logNormalizer = 0;
460};
461
468template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
469[[nodiscard]] SampleSet<Space, Scalar>
470drawSamples(Gamma<Space, Scalar> const & gamma, Eigen::Index n, G & rng)
471{
472 static_assert(std::floating_point<Scalar>, "sampling requires a standard floating-point scalar");
473 EMRYS_FILTER_ASSERT(n >= 0);
474 using Distribution = std::gamma_distribution<Scalar>;
475 constexpr int maxDimension = Gamma<Space, Scalar>::MaxValueDimensionAtCompileTime;
476 // One distribution per coordinate, inline for a bounded space (see \ref Allocation).
477 using Distributions = std::conditional_t<
478 maxDimension == Eigen::Dynamic,
479 std::vector<Distribution>,
480 std::array<Distribution, std::size_t(maxDimension == Eigen::Dynamic ? 0 : maxDimension)>>;
481 Eigen::Index const d = gamma.valueDimension();
482 auto distributions = Distributions();
483 if constexpr (maxDimension == Eigen::Dynamic)
484 {
485 distributions.resize(std::size_t(d));
486 }
487 for (Eigen::Index i = 0; i < d; ++i)
488 {
489 distributions[std::size_t(i)] = Distribution{gamma.shape()(i), gamma.scale()(i)};
490 }
491 auto samples = SampleSet<Space, Scalar>{d, n};
492 for (Eigen::Index j = 0; j < n; ++j)
493 {
494 for (Eigen::Index i = 0; i < d; ++i)
495 {
496 samples(i, j) = distributions[std::size_t(i)](rng);
497 }
498 }
499 return samples;
500}
501
508template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
509[[nodiscard]] typename Moments<Space, Scalar>::VectorType
510drawSample(Gamma<Space, Scalar> const & gamma, G & rng)
511{
512 static_assert(std::floating_point<Scalar>, "sampling requires a standard floating-point scalar");
514 sample.resize(gamma.valueDimension());
515 for (Eigen::Index i = 0; i < sample.size(); ++i)
516 {
517 sample(i) = std::gamma_distribution<Scalar>{gamma.shape()(i), gamma.scale()(i)}(rng);
518 }
519 return sample;
520}
521
527template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
528[[nodiscard]] SamplesWithLogDensity<Space, Scalar>
529drawSamplesWithLogDensity(Gamma<Space, Scalar> const & gamma, Eigen::Index n, G & rng)
530{
531 auto result = SamplesWithLogDensity<Space, Scalar>{drawSamples(gamma, n, rng), {}};
532 result.logDensity.resize(n);
533 for (Eigen::Index j = 0; j < n; ++j)
534 {
535 result.logDensity(j) = gamma.logDensity(result.samples.col(j));
536 }
537 return result;
538}
539
544template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
545[[nodiscard]] SampleWithLogDensity<Space, Scalar>
547{
548 auto result = SampleWithLogDensity<Space, Scalar>{drawSample(gamma, rng), 0};
549 result.logDensity = gamma.logDensity(result.sample);
550 return result;
551}
552
553} // namespace Emrys
554
555#ifndef EMRYS_FILTER_NO_IO
556
557namespace Emrys
558{
559
560namespace internal
561{
562
564inline void
565describe(std::ostream & stream, GammaError error, Indent indent)
566{
567 stream << indent;
568 switch (error)
569 {
571 stream << "a shape or scale is not positive";
572 break;
574 stream << "a shape or scale is not finite";
575 break;
576 }
577}
578
579} // namespace internal
580
581} // namespace Emrys
582
583#endif // EMRYS_FILTER_NO_IO
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
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...
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(),...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Independent Gamma distributions on the coordinates, for positive quantities such as ranges,...
Definition Gamma.h:120
static std::expected< Gamma, GammaError > chiSquared(Space_ space, Dof const &dof)
Definition Gamma.h:203
typename MomentsType::VectorType VectorType
a value
Definition Gamma.h:127
static std::expected< Gamma, GammaError > chiSquared(Dof const &dof)
chiSquared() in the default space.
Definition Gamma.h:212
Scalar_ Scalar
the scalar type
Definition Gamma.h:123
Index differenceDimension() const
Definition Gamma.h:261
static std::expected< Gamma, GammaError > exponential(Space_ space, Scale const &scale)
Definition Gamma.h:185
static constexpr int MaxDifferenceDimensionAtCompileTime
a bound on the length of a difference, or Eigen::Dynamic
Definition Gamma.h:140
auto marginal() const
marginal() of the subspace named by the tag path Path (see idx()), in that subspace.
Definition Gamma.h:333
VectorType const & shape() const
Definition Gamma.h:275
Space_ SpaceType
the space the values live in
Definition Gamma.h:125
static std::expected< Gamma, GammaError > exponential(Scale const &scale)
exponential() in the default space.
Definition Gamma.h:193
static std::expected< Gamma, GammaError > fromMeanAndStdDev(Mean const &mean, StdDev const &stdDev)
fromMeanAndStdDev() in the default space.
Definition Gamma.h:247
friend class Gamma
Marginals build another instantiation from checked parts.
Definition Gamma.h:373
Scalar logDensity(Eigen::MatrixBase< Derived > const &x) const
Definition Gamma.h:290
static std::expected< Gamma, GammaError > create(Space_ space, Shape const &shape, Scale const &scale)
Definition Gamma.h:158
Eigen::Index Index
Eigen's index type.
Definition Gamma.h:124
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Gamma.h:128
Index valueDimension() const
Definition Gamma.h:254
Scalar density(Eigen::MatrixBase< Derived > const &x) const
Definition Gamma.h:315
auto conditional(Eigen::MatrixBase< Derived > const &values) const
Definition Gamma.h:358
static constexpr int MaxValueDimensionAtCompileTime
a bound on the length of a value, or Eigen::Dynamic
Definition Gamma.h:134
Space_ const & space() const
Definition Gamma.h:268
VectorType const & scale() const
Definition Gamma.h:282
auto marginal(Index element) const
marginal() of one repetition of a repeated block; see GaussianParams::marginal(element).
Definition Gamma.h:343
static constexpr int DifferenceDimensionAtCompileTime
the length of a difference and the rows of a covariance, or Eigen::Dynamic
Definition Gamma.h:137
Moments< Space_, Scalar_ > MomentsType
a mean and covariance together
Definition Gamma.h:126
static std::expected< Gamma, GammaError > fromMeanAndStdDev(Space_ space, Mean const &mean, StdDev const &stdDev)
Definition Gamma.h:224
static std::expected< Gamma, GammaError > create(Shape const &shape, Scale const &scale)
create() in the default space.
Definition Gamma.h:175
MomentsType moments() const
Definition Gamma.h:322
static constexpr int ValueDimensionAtCompileTime
the length of a value, or Eigen::Dynamic
Definition Gamma.h:131
First and second moments (mean and covariance) of a distribution.
Definition Moments.h:55
DifferenceMatrix< Space_, Space_, Scalar > MatrixType
Definition Moments.h:76
ValueVector< Space_, Scalar > VectorType
Definition Moments.h:74
static constexpr int MaxDifferenceDimensionAtCompileTime
Definition Moments.h:72
VectorType const & mean() const
Definition Moments.h:141
static constexpr int DifferenceDimensionAtCompileTime
Definition Moments.h:69
static constexpr int MaxValueDimensionAtCompileTime
Definition Moments.h:66
static constexpr int ValueDimensionAtCompileTime
Definition Moments.h:63
bool allFinite(Eigen::DenseBase< Derived > const &x)
Definition Math.h:354
Scalar logGamma(std::type_identity_t< Scalar > const &x)
Definition Math.h:216
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
@ 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
GammaError
Reasons a shape and scale fail to define a Gamma.
Definition Gamma.h:40
Eigen::Index drawSample(Categorical< Space, Scalar > const &distribution, G &rng)
Definition Categorical.h:1111
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
@ NonFinite
a shape or scale is NaN or infinite
Definition Gamma.h:42
@ NotPositive
a shape or scale is zero or negative
Definition Gamma.h:41
Sample< std::remove_cvref_t< G > > sample(G &&function)
Definition Policies.h:665
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
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
Scalar logDensity
the log-density at sample
Definition Sampling.h:52
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