A (finite, time-homogeneous) Markov chain on a set of states is a function assigning to each state a probability distribution over the next state — a Kleisli endomorphism for the Distribution Monad. Category Theory for Scientists phrases it as a database instance: take the schema (one object , one arrow ) and interpret it in the Kleisli Category of rather than in — a Kleisli Instance. A Discrete Dynamical System is the special case where every distribution is a Kronecker delta.
Sources: CTfS Example 5.3.4.3, §5.3.4; Discrete Dynamical System (CTfS Example 3.5.2.9, the deterministic version).
CTfS Example 5.3.4.3
States and transition function
with the transition matrix (row is the distribution )
Because Kleisli composition in is matrix multiplication, the -step transition function (the image of the path in , which presents ) corresponds to the matrix power . Starting in state 4:
| steps | ||||
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 |
| 1 | .4 | .3 | 0 | .3 |
| 2 | .32 | .59 | 0 | .09 |
| 3 | .196 | .777 | 0 | .027 |
State 2 is absorbing (): in the long run all mass flows there, and state 3 is unreachable from 4. So the functor says everything about the chain’s evolution, and questions like “absorbing states”, “stationary distributions” () and “reachability” are questions about this functor.
Categorical reading
- A Markov chain is to a Discrete Dynamical System what a relation is to a function: replace by for the monad describing the kind of uncertainty — for probability, the Power Set Monad for nondeterminism (then is “reachable in steps”), the Maybe Monad for possibly-halting dynamics.
- Several kinds of state and several kinds of transitions (a hidden Markov model: hidden states and emissions) are Kleisli instances on a bigger schema.
- A probabilistic Finite State Machine is a Kleisli action of a free monoid on in — a matrix per letter.
Docs: plain Julia — Catlab has no dedicated API for this; related: Catlab v0.16 docs · GATlab standard library
# A Markov chain is a Kleisli arrow S → Dist(S) (CTfS Example 5.3.4.3)
M = [0.5 0.5 0.0 0.0;
0.0 1.0 0.0 0.0;
0.7 0.0 0.3 0.0;
0.4 0.3 0.0 0.3] # row x = the distribution f(x)
kleisli(M, N) = M * N # Kleisli composition in Dist = matrix product (weighted sum)
η = [1.0 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1] # unit: Kronecker deltas
kleisli(η, M) == M == kleisli(M, η) # unit laws: true
p = [0.0 0.0 0.0 1.0] * M^3 # where state 4 probably is after 3 steps
round.(p; digits = 3) # [0.196 0.777 0.0 0.027]
round.([0.0 0.0 0.0 1.0] * M^100; digits = 3) # [0.0 1.0 0.0 0.0]: absorbed in state 2import Mathlib
-- a Markov chain as a Kleisli arrow for the PMF monad
def step : Fin 4 → PMF (Fin 4) := fun _ => PMF.pure 1 -- (placeholder kernel)
-- the n-step kernel is the n-fold Kleisli composite
def nSteps (f : Fin 4 → PMF (Fin 4)) : ℕ → Fin 4 → PMF (Fin 4)
| 0 => PMF.pure
| n + 1 => fun s => (nSteps f n s).bind f
#check @ProbabilityTheory.Kernel -- measure-theoretic Markov kernelsimport Data.List (transpose)
type Matrix = [[Double]]
mmul :: Matrix -> Matrix -> Matrix -- Kleisli composition in Dist
mmul a b = [ [ sum (zipWith (*) r c) | c <- transpose b ] | r <- a ]
m :: Matrix -- CTfS Example 5.3.4.3
m = [[0.5,0.5,0,0],[0,1,0,0],[0.7,0,0.3,0],[0.4,0.3,0,0.3]]
after :: Int -> [Double] -> [Double] -- distribution after n steps
after n p = head (iterate (`mmul` m) [p] !! n)
-- after 3 [0,0,0,1] ≈ [0.196, 0.777, 0.0, 0.027]