EmrysFilter 0.1.0
State estimation utilities in C++23 using Eigen
Loading...
Searching...
No Matches
ContinuousTimeMarkovChain.h
Go to the documentation of this file.
1#pragma once
2
11
12#include <tuple>
13#include <type_traits>
14#include <utility>
15
16#include <unsupported/Eigen/MatrixFunctions>
17
25
26namespace Emrys
27{
28
59template <DiscreteSpace Space_, RealScalar Scalar_ = double>
61{
62public:
63
64 using Scalar = Scalar_;
65 using SpaceType = Space_;
66 static constexpr int CardinalityAtCompileTime = Space_::Cardinality;
67 static constexpr int MaxCardinalityAtCompileTime = Space_::MaxCardinality;
68
71
73 explicit ContinuousTimeMarkovChain(Space_ space = {})
74 : m_space{std::move(space)}
75 , m_generator{MatrixType::Zero(m_space.cardinality(), m_space.cardinality())}
76 {}
77
81 template <MatrixExpression Generator>
83 : m_space{std::move(space)}
84 , m_generator{internal::castScalar<Scalar>(std::forward<Generator>(generator))}
85 {
86 EMRYS_FILTER_ASSERT(
87 m_generator.rows() == m_space.cardinality() && m_generator.cols() == m_space.cardinality()
88 && "the generator has a row and a column per value"
89 );
90 [[maybe_unused]] Scalar scale = 0;
91 for (Eigen::Index i = 0; i < m_generator.rows(); ++i)
92 {
93 for (Eigen::Index j = 0; j < m_generator.cols(); ++j)
94 {
95 EMRYS_FILTER_ASSERT((i == j || m_generator(i, j) >= Scalar(0)) && "rates must be nonnegative");
96 }
97 scale = Eigen::numext::maxi(scale, Scalar(-m_generator(i, i)));
98 }
99 EMRYS_FILTER_ASSERT(
100 (m_generator.rowwise().sum().array().abs()
101 <= Eigen::NumTraits<Scalar>::dummy_precision() * Eigen::numext::maxi(scale, Scalar(1)))
102 .all()
103 && "each row of the generator must sum to zero"
104 );
105 }
106
110 template <MatrixExpression Generator>
113 : ContinuousTimeMarkovChain{Space_{}, std::forward<Generator>(generator)}
114 {}
115
119 template <DiscreteSpace... Axes>
120 requires(
121 FixedSizeSpace<Space_> && internal::IsDiscreteProduct<Space_>
122 && sizeof...(Axes) == std::tuple_size_v<internal::DiscreteProductAxes<Space_>>
123 )
125 : ContinuousTimeMarkovChain{Space_{}, chains...}
126 {}
127
131 template <DiscreteSpace... Axes>
132 requires(
133 internal::IsDiscreteProduct<Space_>
134 && sizeof...(Axes) == std::tuple_size_v<internal::DiscreteProductAxes<Space_>>
135 )
137 : m_space{std::move(space)}
138 {
139 static_assert(
140 std::is_same_v<std::tuple<Axes...>, internal::DiscreteProductAxes<Space_>>,
141 "give one chain per axis of the product, in order"
142 );
143 Eigen::Index const n = m_space.cardinality();
144 m_generator = MatrixType::Zero(n, n);
145 Eigen::Index stride = n;
146 (
147 [&] {
148 Eigen::Index const count = chains.space().cardinality();
149 stride /= count;
150 // The axis jumps from i to j while every other coordinate stays: i and j differ
151 // only in this axis's coordinate, which a multiple of the stride carries.
152 for (Eigen::Index i = 0; i < n; ++i)
153 {
154 Eigen::Index const from = (i / stride) % count;
155 Eigen::Index const base = i - from * stride;
156 for (Eigen::Index to = 0; to < count; ++to)
157 {
158 m_generator(i, base + to * stride) += chains.generator()(from, to);
159 }
160 }
161 }(),
162 ...
163 );
164 EMRYS_FILTER_ASSERT(stride == 1 && "each chain must have its axis's cardinality");
165 }
166
170 [[nodiscard]] static ContinuousTimeMarkovChain
172 {
173 EMRYS_FILTER_ASSERT(rate >= Scalar(0) && "the rate must be nonnegative");
174 Eigen::Index const n = space.cardinality();
175 auto result = ContinuousTimeMarkovChain{std::move(space)};
176 if (n > 1)
177 {
178 result.m_generator.setConstant(rate / Scalar(n - 1));
179 result.m_generator.diagonal().setConstant(-rate);
180 }
181 return result;
182 }
183
199 template <typename DwellTimes, typename ExitWeights>
200 [[nodiscard]] static ContinuousTimeMarkovChain
202 Eigen::MatrixBase<DwellTimes> const & dwellTimes,
203 Eigen::MatrixBase<ExitWeights> const & exitWeights,
204 Space_ space = {}
205 )
206 {
207 Eigen::Index const n = space.cardinality();
208 MatrixType generator = internal::dwellGenerator<Scalar, MatrixType>(dwellTimes, exitWeights);
209 EMRYS_FILTER_ASSERT(generator.rows() == n && "give a dwell time per value");
210 return ContinuousTimeMarkovChain{std::move(space), std::move(generator)};
211 }
212
217 template <typename DwellTimes>
218 [[nodiscard]] static ContinuousTimeMarkovChain
219 fromDwellTimes(Eigen::MatrixBase<DwellTimes> const & dwellTimes, Space_ space = {})
220 {
221 Eigen::Index const n = space.cardinality();
222 return fromDwellTimes(dwellTimes, MatrixType::Ones(n, n) - MatrixType::Identity(n, n), std::move(space));
223 }
224
226 [[nodiscard]] Space_ const &
227 space() const
228 {
229 return m_space;
230 }
231
233 [[nodiscard]] MatrixType const &
234 generator() const
235 {
236 return m_generator;
237 }
238
242 [[nodiscard]] Scalar
243 rate(Eigen::Index from, Eigen::Index to) const
244 {
245 return m_generator(from, to);
246 }
247
251 [[nodiscard]] MarkovChain<Space_, Scalar_>
252 transition(TimeStep<Scalar_> auto const & time) const
253 {
254 Scalar const dt = internal::toScalarTime<Scalar_>(time);
255 MatrixType const scaled = m_generator * dt;
256 MatrixType transitions = scaled.exp();
257 // The exponential of a generator is stochastic up to rounding: clear the negative
258 // rounding off the zeros and rescale each row to sum to one.
259 transitions = transitions.cwiseMax(Scalar(0));
260 transitions.array().colwise() /= transitions.rowwise().sum().array();
261 return MarkovChain<Space_, Scalar_>{m_space, std::move(transitions)};
262 }
263
264private:
265
266 Space_ m_space;
267 MatrixType m_generator;
268};
269
271template <DiscreteSpace Space, MatrixExpression Generator>
272ContinuousTimeMarkovChain(Space, Generator &&)
273 -> ContinuousTimeMarkovChain<Space, typename std::remove_cvref_t<Generator>::Scalar>;
274
276
280template <typename Scalar, typename Space>
281[[nodiscard]] CategoricalParams<Space, Scalar>
285 TimeStep<Scalar> auto const & dt
286)
287{
288 return predict(prior, chain.transition(dt));
289}
290
300template <typename Scalar, typename Space>
301[[nodiscard]] std::expected<CategoricalParams<Space, Scalar>, ChainError>
303{
304 using ArrayType = typename CategoricalParams<Space, Scalar>::ArrayType;
305 std::expected<ArrayType, ChainError> const probabilities =
306 internal::stationaryProbabilities<Scalar, ArrayType>(chain.generator());
307 if (!probabilities)
308 {
309 return std::unexpected{probabilities.error()};
310 }
311 return CategoricalParams<Space, Scalar>{chain.space(), probabilities->log()};
312}
313
314} // namespace Emrys
The distribution over a discrete space (decisions.md [discrete-spaces], [params-and-density]): Catego...
Discrete spaces (decisions.md [discrete-spaces]): the DiscreteSpace concept; Finite,...
How a function that stores a matrix argument takes it (decisions.md [forwarded-matrices]): the concep...
The macros every header includes first.
Mathematical constants and scalar functions that work in every scalar type the library accepts,...
The Space concept and what every continuous space shares (decisions.md [sizes-and-dimensions]): the t...
Time steps (decisions.md [time-steps]): TimeTraits, which converts a time type to a scalar,...
The parameters of a probability distribution over a discrete space: one weight per value.
Definition Categorical.h:257
Eigen::Array< Scalar_, CardinalityAtCompileTime, 1, Eigen::ColMajor, MaxCardinalityAtCompileTime, 1 > ArrayType
One entry per value, in flat-index order.
Definition Categorical.h:268
A continuous-time Markov chain on a discrete space: the rate of moving from each value to each other ...
Definition ContinuousTimeMarkovChain.h:61
Scalar rate(Eigen::Index from, Eigen::Index to) const
Definition ContinuousTimeMarkovChain.h:243
ContinuousTimeMarkovChain(Space_ space, ContinuousTimeMarkovChain< Axes, Scalar_ > const &... chains)
The chain on the DiscreteProduct space whose axes move independently, each by its own chain in chains...
Definition ContinuousTimeMarkovChain.h:136
static ContinuousTimeMarkovChain fromDwellTimes(Eigen::MatrixBase< DwellTimes > const &dwellTimes, Space_ space={})
Definition ContinuousTimeMarkovChain.h:219
ContinuousTimeMarkovChain(Space_ space, Generator &&generator)
The chain on space with generator generator: nonnegative off the diagonal, with each row summing to z...
Definition ContinuousTimeMarkovChain.h:82
MarkovChain< Space_, Scalar_ > transition(TimeStep< Scalar_ > auto const &time) const
Definition ContinuousTimeMarkovChain.h:252
ContinuousTimeMarkovChain(Space_ space={})
The chain that never moves: every rate zero.
Definition ContinuousTimeMarkovChain.h:73
Space_ const & space() const
Definition ContinuousTimeMarkovChain.h:227
static ContinuousTimeMarkovChain persistent(Scalar rate, Space_ space={})
Definition ContinuousTimeMarkovChain.h:171
static constexpr int MaxCardinalityAtCompileTime
a bound on it, or Eigen::Dynamic
Definition ContinuousTimeMarkovChain.h:67
MatrixType const & generator() const
Definition ContinuousTimeMarkovChain.h:234
static constexpr int CardinalityAtCompileTime
the number of values, or Eigen::Dynamic
Definition ContinuousTimeMarkovChain.h:66
Space_ SpaceType
the discrete space
Definition ContinuousTimeMarkovChain.h:65
ContinuousTimeMarkovChain(ContinuousTimeMarkovChain< Axes, Scalar_ > const &... chains)
The chain on a DiscreteProduct that is a FixedSizeSpace, named as the template argument,...
Definition ContinuousTimeMarkovChain.h:124
static ContinuousTimeMarkovChain fromDwellTimes(Eigen::MatrixBase< DwellTimes > const &dwellTimes, Eigen::MatrixBase< ExitWeights > const &exitWeights, Space_ space={})
Definition ContinuousTimeMarkovChain.h:201
ContinuousTimeMarkovChain(Generator &&generator)
The chain on a FixedSizeSpace, named as the template argument, with generator generator: ContinuousTi...
Definition ContinuousTimeMarkovChain.h:112
Scalar_ Scalar
the scalar type of the rates
Definition ContinuousTimeMarkovChain.h:64
typename MarkovChain< Space_, Scalar_ >::MatrixType MatrixType
The generator's type, the same as a MarkovChain's transition matrix.
Definition ContinuousTimeMarkovChain.h:70
A Markov chain on a discrete space: the probability of moving from each value to each value in one st...
Definition Categorical.h:829
Eigen::Matrix< Scalar_, CardinalityAtCompileTime, CardinalityAtCompileTime, Eigen::RowMajor, MaxCardinalityAtCompileTime, MaxCardinalityAtCompileTime > MatrixType
The transition matrix's type: a row per value moved from, a column per value moved to.
Definition Categorical.h:838
Axes: a way of taking a root of a covariance , , whose columns carry a sigma-point rule's unit point...
Definition Roots.h:300
A type usable as a discrete space: a finite set of values.
Definition Discrete.h:72
A space whose instance holds nothing, so that S{} is the only one there is: a space of fixed size,...
Definition Space.h:217
A step of time that converts to Scalar: Scalar itself, or any type with a TimeTraits,...
Definition Time.h:128
CategoricalParams< Space, Scalar > predict(CategoricalParams< Space, Scalar > const &prior, MarkovChain< Space, Scalar > const &chain)
Definition Categorical.h:1049
std::expected< CategoricalParams< Space, Scalar >, ChainError > stationary(MarkovChain< Space, Scalar > const &chain)
Definition Categorical.h:1069
ChainError
Reasons a Markov chain has no single stationary distribution.
Definition Categorical.h:48