EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
Uniform.h
Go to the documentation of this file.
1#pragma once
2
9
10#include <cmath>
11#include <expected>
12#include <limits>
13#include <optional>
14#include <random>
15#include <utility>
16
27
28namespace Emrys
29{
30
40
67template <Space Space_, RealScalar Scalar_ = double>
69{
70public:
71
72 using Scalar = Scalar_;
73 using Index = Eigen::Index;
74 using SpaceType = Space_;
78
81
84
87
90
91 static_assert(
92 internal::hasEqualSizes<Space_>,
93 "a uniform box needs a space whose values and differences have the same coordinates"
94 );
95
97 explicit Uniform(Space_ space = {})
98 requires(!isLinear<Space_>)
99 : m_space{std::move(space)}
100 {
101 Index const n = Emrys::valueDimension(m_space);
102 m_lower.resize(n);
103 m_width.resize(n);
104 m_period.resize(n);
105 internal::forEachLeaf(m_space, [&](Index offset, auto const & leaf) {
106 using Leaf = std::remove_cvref_t<decltype(leaf)>;
107 static_assert(hasDefaultInterval<Leaf>(), "a coordinate has no default interval; give the box explicitly");
108 constexpr auto found = internal::leafSupport<Leaf>();
109 constexpr internal::LeafSupport support = *found;
110 constexpr double width = support.upper - support.lower;
111 auto const rows = Eigen::seqN(offset, leaf.valueDimension());
112 m_lower(rows).setConstant(Scalar(support.lower));
113 m_width(rows).setConstant(Scalar(width));
114 // Only a Periodic coordinate has a default interval.
115 static_assert(support.periodic);
116 m_period(rows).setConstant(Scalar(width));
117 });
118 m_logVolume = m_width.array().log().sum();
119 }
120
126 template <MatrixExpression Lower, typename UpperDerived>
127 [[nodiscard]] static std::expected<Uniform, UniformError>
128 create(Space_ space, Lower && lower, Eigen::MatrixBase<UpperDerived> const & upper)
129 {
130 VectorType start = std::forward<Lower>(lower);
131 EMRYS_FILTER_ASSERT(upper.size() == start.size() && "give one upper bound per lower bound");
132 VectorType width = upper - start;
133 internal::fitValueDimension(space, start.size());
134 if (!allFinite(start) || !allFinite(width))
135 {
136 return std::unexpected{UniformError::NonFinite};
137 }
138 if (!(width.array() > Scalar(0)).all())
139 {
140 return std::unexpected{UniformError::Empty};
141 }
142 auto period = VectorType{VectorType::Zero(start.size())};
143 bool fits = true;
144 internal::forEachLeaf(space, [&](Index offset, auto const & leaf) {
145 constexpr auto support = internal::leafSupport<std::remove_cvref_t<decltype(leaf)>>();
146 if constexpr (support.has_value() && support->periodic)
147 {
148 constexpr double leafPeriod = support->upper - support->lower;
149 for (Index i = offset; i < offset + leaf.valueDimension(); ++i)
150 {
151 fits = fits && width(i) <= Scalar(leafPeriod);
152 period(i) = Scalar(leafPeriod);
153 }
154 }
155 });
156 if (!fits)
157 {
158 return std::unexpected{UniformError::WiderThanPeriod};
159 }
160 return Uniform{std::move(space), std::move(start), std::move(width), std::move(period)};
161 }
162
164 template <MatrixExpression Lower, typename UpperDerived>
165 requires internal::SizedByLength<Space_>
166 [[nodiscard]] static std::expected<Uniform, UniformError>
167 create(Lower && lower, Eigen::MatrixBase<UpperDerived> const & upper)
168 {
169 return create(Space_{}, std::forward<Lower>(lower), upper);
170 }
171
173 [[nodiscard]] Index
175 {
176 return m_lower.size();
177 }
178
180 [[nodiscard]] Index
182 {
183 return m_lower.size();
184 }
185
187 [[nodiscard]] Space_ const &
188 space() const
189 {
190 return m_space;
191 }
192
194 [[nodiscard]] VectorType const &
195 lower() const
196 {
197 return m_lower;
198 }
199
203 [[nodiscard]] VectorType
204 upper() const
205 {
206 return m_lower + m_width;
207 }
208
210 [[nodiscard]] VectorType const &
211 width() const
212 {
213 return m_width;
214 }
215
217 [[nodiscard]] Scalar
218 logVolume() const
219 {
220 return m_logVolume;
221 }
222
224 template <typename Derived>
225 [[nodiscard]] bool
226 contains(Eigen::MatrixBase<Derived> const & x) const
227 {
228 static_assert(Derived::IsVectorAtCompileTime, "contains() expects a vector");
229 EMRYS_FILTER_ASSERT(x.size() == valueDimension());
230 for (Index i = 0; i < valueDimension(); ++i)
231 {
232 Scalar offset = Scalar(x(i)) - m_lower(i);
233 if (m_period(i) > Scalar(0))
234 {
235 offset -= m_period(i) * floorOf<Scalar>(offset / m_period(i));
236 }
237 if (!(offset >= Scalar(0) && offset <= m_width(i)))
238 {
239 return false;
240 }
241 }
242 return true;
243 }
244
248 template <typename Derived>
249 [[nodiscard]] Scalar
250 logDensity(Eigen::MatrixBase<Derived> const & x) const
251 {
252 return contains(x) ? -m_logVolume : -std::numeric_limits<Scalar>::infinity();
253 }
254
256 template <typename Derived>
257 [[nodiscard]] Scalar
258 density(Eigen::MatrixBase<Derived> const & x) const
259 {
260 return Eigen::numext::exp(logDensity(x));
261 }
262
270 [[nodiscard]] MomentsType
271 moments() const
272 {
274 result.mean() = m_lower + Scalar(0.5) * m_width;
275 normalizeValues(m_space, result.mean());
276 result.covarianceLower() = (m_width.array().square() / Scalar(12)).matrix().asDiagonal();
277 return result;
278 }
279
281 template <typename Path>
282 [[nodiscard]] auto
283 marginal() const
284 {
285 return internal::marginalOverPath<Path>(m_space, [&](auto const & target, auto space) {
286 return restricted(target.values, std::move(space));
287 });
288 }
289
291 template <typename Path>
292 [[nodiscard]] auto
293 marginal(Index element) const
294 {
295 return internal::marginalOverElement<Path>(m_space, element, [&](auto const & target, auto space) {
296 return restricted(target.values, std::move(space));
297 });
298 }
299
306 template <typename GivenPath, typename Derived>
307 [[nodiscard]] auto
308 conditional(Eigen::MatrixBase<Derived> const & values) const
309 {
310 return internal::tagConditional<GivenPath>(
311 m_space,
312 [&](auto const & t, auto const & g, auto space, auto const &) {
313 EMRYS_FILTER_ASSERT(values.size() == g.values.size());
314 return restricted(t.values, std::move(space));
315 }
316 );
317 }
318
319private:
320
322 template <Space, RealScalar>
323 friend class Uniform;
324
328 template <typename Leaf>
329 static consteval bool
330 hasDefaultInterval()
331 {
332 constexpr auto support = internal::leafSupport<Leaf>();
333 return support && support->lower > -std::numeric_limits<double>::infinity()
334 && support->upper < std::numeric_limits<double>::infinity();
335 }
336
341 : m_space{std::move(space)}
342 , m_lower{std::move(lower)}
343 , m_width{std::move(width)}
344 , m_period{std::move(period)}
345 , m_logVolume{m_width.array().log().sum()}
346 {}
347
349 template <typename Target, typename SubSpace>
350 [[nodiscard]] Uniform<SubSpace, Scalar>
351 restricted(Target const & target, SubSpace space) const
352 {
353 using Result = Uniform<SubSpace, Scalar>;
354 using ResultVector = typename Result::VectorType;
355 return Result{
356 std::move(space),
357 ResultVector{m_lower(target)},
358 ResultVector{m_width(target)},
359 ResultVector{m_period(target)}
360 };
361 }
362
363 EMRYS_FILTER_NO_UNIQUE_ADDRESS Space_ m_space;
364 VectorType m_lower;
365 VectorType m_width;
366 VectorType m_period;
367 Scalar m_logVolume{};
368};
369
376template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
377[[nodiscard]] SampleSet<Space, Scalar>
378drawSamples(Uniform<Space, Scalar> const & uniform, Eigen::Index n, G & rng)
379{
380 EMRYS_FILTER_ASSERT(n >= 0);
381 using SampleSetType = SampleSet<Space, Scalar>;
382 SampleSetType samples =
383 uniform.width().asDiagonal() * internal::standardUniform<SampleSetType>(uniform.valueDimension(), n, rng);
384 samples.colwise() += uniform.lower();
385 normalizeValues(uniform.space(), samples);
386 return samples;
387}
388
394template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
395[[nodiscard]] typename Moments<Space, Scalar>::VectorType
396drawSample(Uniform<Space, Scalar> const & uniform, G & rng)
397{
398 static_assert(std::floating_point<Scalar>, "sampling requires a standard floating-point scalar");
399 using Vector = typename Moments<Space, Scalar>::VectorType;
400 auto draw = std::uniform_real_distribution<Scalar>{};
401 auto sample = Vector{};
402 sample.resize(uniform.valueDimension());
403 for (Eigen::Index i = 0; i < sample.size(); ++i)
404 {
405 sample(i) = uniform.lower()(i) + uniform.width()(i) * draw(rng);
406 }
407 normalizeValues(uniform.space(), sample);
408 return sample;
409}
410
417template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
418[[nodiscard]] SamplesWithLogDensity<Space, Scalar>
419drawSamplesWithLogDensity(Uniform<Space, Scalar> const & uniform, Eigen::Index n, G & rng)
420{
421 return {drawSamples(uniform, n, rng), Eigen::Array<Scalar, Eigen::Dynamic, 1>::Constant(n, -uniform.logVolume())};
422}
423
429template <typename Scalar, typename Space, std::uniform_random_bit_generator G>
430[[nodiscard]] SampleWithLogDensity<Space, Scalar>
432{
433 return {drawSample(uniform, rng), -uniform.logVolume()};
434}
435
436} // namespace Emrys
437
438#ifndef EMRYS_FILTER_NO_IO
439
440namespace Emrys
441{
442
443namespace internal
444{
445
447inline void
448describe(std::ostream & stream, UniformError error, Indent indent)
449{
450 stream << indent;
451 switch (error)
452 {
454 stream << "an upper bound is not above its lower bound";
455 break;
457 stream << "a bound is not finite";
458 break;
460 stream << "an interval is wider than its coordinate's period";
461 break;
462 }
463}
464
465} // namespace internal
466
467} // namespace Emrys
468
469#endif // EMRYS_FILTER_NO_IO
Spaces built from other spaces (decisions.md [sizes-and-dimensions], [several-resizable-blocks]): Com...
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
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...
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
The uniform distribution over a box in a space.
Definition Uniform.h:69
static std::expected< Uniform, UniformError > create(Lower &&lower, Eigen::MatrixBase< UpperDerived > const &upper)
create() in the default space.
Definition Uniform.h:167
static constexpr int MaxDifferenceDimensionAtCompileTime
a bound on the length of a difference, or Eigen::Dynamic
Definition Uniform.h:89
VectorType const & width() const
Definition Uniform.h:211
static std::expected< Uniform, UniformError > create(Space_ space, Lower &&lower, Eigen::MatrixBase< UpperDerived > const &upper)
Definition Uniform.h:128
Scalar density(Eigen::MatrixBase< Derived > const &x) const
Definition Uniform.h:258
Index differenceDimension() const
Definition Uniform.h:181
Scalar logDensity(Eigen::MatrixBase< Derived > const &x) const
Definition Uniform.h:250
friend class Uniform
Marginals build another instantiation from checked parts.
Definition Uniform.h:323
static constexpr int ValueDimensionAtCompileTime
the length of a value, or Eigen::Dynamic
Definition Uniform.h:80
Index valueDimension() const
Definition Uniform.h:174
auto marginal(Index element) const
marginal() of one repetition of a repeated block; see GaussianParams::marginal(element).
Definition Uniform.h:293
Scalar logVolume() const
Definition Uniform.h:218
Space_ const & space() const
Definition Uniform.h:188
typename MomentsType::MatrixType MatrixType
a matrix over differences
Definition Uniform.h:77
MomentsType moments() const
Definition Uniform.h:271
static constexpr int DifferenceDimensionAtCompileTime
the length of a difference and the rows of a covariance, or Eigen::Dynamic
Definition Uniform.h:86
auto conditional(Eigen::MatrixBase< Derived > const &values) const
Definition Uniform.h:308
Uniform(Space_ space={})
Uniform over the whole of space.
Definition Uniform.h:97
static constexpr int MaxValueDimensionAtCompileTime
a bound on the length of a value, or Eigen::Dynamic
Definition Uniform.h:83
Moments< Space_, Scalar_ > MomentsType
a mean and covariance together
Definition Uniform.h:75
bool contains(Eigen::MatrixBase< Derived > const &x) const
Definition Uniform.h:226
VectorType upper() const
Definition Uniform.h:204
auto marginal() const
marginal() of the subspace named by the tag path Path (see idx()), in that subspace.
Definition Uniform.h:283
typename MomentsType::VectorType VectorType
a value
Definition Uniform.h:76
Eigen::Index Index
Eigen's index type.
Definition Uniform.h:73
Scalar_ Scalar
the scalar type
Definition Uniform.h:72
Space_ SpaceType
the space the values live in
Definition Uniform.h:74
VectorType const & lower() const
Definition Uniform.h:195
bool allFinite(Eigen::DenseBase< Derived > const &x)
Definition Math.h:354
#define EMRYS_FILTER_NO_UNIQUE_ADDRESS
Assertion used for programmer errors (misuse of the API).
Definition Macros.h:38
Scalar floorOf(std::type_identity_t< Scalar > const &x)
Definition Math.h:277
@ NonFinite
a matrix, a factor, or a log-weight holds NaN or infinity (+inf, for a log-weight)
Definition NumericError.h:33
@ Empty
there are no weights
Definition NumericError.h:34
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
UniformError
Reasons bounds fail to define a Uniform.
Definition Uniform.h:35
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 bound is NaN or infinite
Definition Uniform.h:37
@ Empty
an upper bound is not above its lower bound
Definition Uniform.h:36
@ WiderThanPeriod
an interval on a Periodic coordinate spans more than one period
Definition Uniform.h:38
Sample< std::remove_cvref_t< G > > sample(G &&function)
Definition Policies.h:665
Eigen::Index valueDimension(S const &space)
Definition Space.h:272
constexpr bool isLinear
Whether S is linear: its values and its differences are both plain vectors.
Definition Space.h:321
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