from datetime import timedelta
import numpy as np
from scipy.stats import multinomial
from .base import Property
from ...models.transition import TransitionModel
from ...types.array import Matrix, StateVector
[docs]
class MarkovianTransitionModel(TransitionModel):
r"""The transition model for categorical states
This is a time invariant, transition model of a Markov process.
A state space vector takes the form :math:`\alpha_t^i = P(\phi_t^i)`, representing a
categorical distribution over a discrete, finite set of possible categories
:math:`\Phi = \{\phi^m|m\in \mathbf{N}, m\le M\}` (for some finite :math:`M`).
Models the transition from one category to another.
Intended to be used in conjunction with the :class:`~.CategoricalState` type.
"""
transition_matrix: Matrix = Property(
doc=r"Stochastic matrix :math:`F_t^{ij} = F^{ij} = P(\phi_t^i|\phi_{t-1}^j)` determining "
r"the conditional probability that an object is category :math:`\phi^i` at 'time' "
r":math:`t` given that it was category :math:`\phi^j` at 'time' :math:`t-1`. "
r"Columns are normalised.")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Normalise matrix columns
self.transition_matrix = self.transition_matrix / np.sum(self.transition_matrix, axis=0)
[docs]
def function(self, state, time_interval: timedelta = None, noise: bool = False, **kwargs):
r"""Applies the linear transformation:
.. math::
F^{ij}\alpha_{t-1}^j = P(\phi_t^i|\phi_{t-1}^j)P(\phi_t^j)
The resultant vector is normalised.
Though this model is time-invariant, a check is made to see whether the time-interval given
is 0. In this instance, no transformation is applied.
Parameters
----------
state: :class:`~.CategoricalState`
The state to be transitioned.
time_interval: datetime.timedelta
Duration to transition state for.
noise: bool
Indicates whether transitioned vector is sampled from and the resultant category
returned instead. This is a discrete category instead of a distribution
over the state space. It is represented by an M-tuples, with all components
equal to 0, except at an index corresponding to the relevant category.
For example :math:`e^k` indicates that the category is :math:`\phi^k`.
If `False`, the resultant distribution is returned.
Returns
-------
state_vector: :class:`stonesoup.types.array.StateVector`
of shape (:py:attr:`~ndim_state, 1`). The resultant state vector of the transition.
"""
if time_interval is None or time_interval.total_seconds() == 0:
return state.state_vector
new_vector = self.transition_matrix @ state.state_vector
new_vector = new_vector / np.sum(new_vector) # normalise
if noise:
rv = multinomial(n=1, p=new_vector.flatten())
return StateVector(rv.rvs(size=1, random_state=None))
else:
return StateVector(new_vector)
@property
def ndim_state(self):
return self.transition_matrix.shape[1]
[docs]
def rvs(self):
raise NotImplementedError
[docs]
def pdf(self):
raise NotImplementedError