Skip to content

Commit 5d5b7d7

Browse files
authored
Merge pull request sympy#20196 from Maelstrom6/mc_stationary_distribution
Improved fixed_row_vector for Discrete Markov Chains
2 parents f03e8d2 + 07faec0 commit 5d5b7d7

File tree

2 files changed

+94
-12
lines changed

2 files changed

+94
-12
lines changed

sympy/stats/stochastic_process_types.py

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
linsolve, eye, Or, Not, Intersection, factorial, Contains,
1010
Union, Expr, Function, exp, cacheit, sqrt, pi, gamma,
1111
Ge, Piecewise, Symbol, NonSquareMatrixError, EmptySet,
12-
ceiling, MatrixBase)
12+
ceiling, MatrixBase, ConditionSet, ones, zeros, Identity)
1313
from sympy.core.relational import Relational
1414
from sympy.logic.boolalg import Boolean
1515
from sympy.utilities.exceptions import SymPyDeprecationWarning
@@ -1012,21 +1012,98 @@ def is_absorbing_chain(self):
10121012
return any(self.is_absorbing_state(state) == True
10131013
for state in range(trans_probs.shape[0]))
10141014

1015-
def fixed_row_vector(self):
1015+
def stationary_distribution(self, condition_set=False) -> tUnion[ImmutableMatrix, ConditionSet, Lambda]:
1016+
"""
1017+
The stationary distribution is any row vector, p, that solves p = pP,
1018+
is row stochastic and each element in p must be nonnegative.
1019+
That means in matrix form: :math:`(P-I)^T p^T = 0` and
1020+
:math:`(1, ..., 1) p = 1`
1021+
where ``P`` is the one-step transition matrix.
1022+
1023+
All time-homogeneous Markov Chains with a finite state space
1024+
have at least one stationary distribution. In addition, if
1025+
a finite time-homogeneous Markov Chain is irreducible, the
1026+
stationary distribution is unique.
1027+
1028+
Parameters
1029+
==========
1030+
1031+
condition_set : bool
1032+
If the chain has a symbolic size or transition matrix,
1033+
it will return a ``Lambda`` if ``False`` and return a
1034+
``ConditionSet`` if ``True``.
1035+
1036+
Examples
1037+
========
1038+
1039+
>>> from sympy.stats import DiscreteMarkovChain
1040+
>>> from sympy import Matrix, S
1041+
1042+
An irreducible Markov Chain
1043+
1044+
>>> T = Matrix([[S(1)/2, S(1)/2, 0],
1045+
... [S(4)/5, S(1)/5, 0],
1046+
... [1, 0, 0]])
1047+
>>> X = DiscreteMarkovChain('X', trans_probs=T)
1048+
>>> X.stationary_distribution()
1049+
Matrix([[8/13, 5/13, 0]])
1050+
1051+
A reducible Markov Chain
1052+
1053+
>>> T = Matrix([[S(1)/2, S(1)/2, 0],
1054+
... [S(4)/5, S(1)/5, 0],
1055+
... [0, 0, 1]])
1056+
>>> X = DiscreteMarkovChain('X', trans_probs=T)
1057+
>>> X.stationary_distribution()
1058+
Matrix([[8/13 - 8*tau0/13, 5/13 - 5*tau0/13, tau0]])
1059+
1060+
>>> Y = DiscreteMarkovChain('Y')
1061+
>>> Y.stationary_distribution()
1062+
Lambda((wm, _T), Eq(wm*_T, wm))
1063+
1064+
>>> Y.stationary_distribution(condition_set=True)
1065+
ConditionSet(wm, Eq(wm*_T, wm))
1066+
1067+
References
1068+
==========
1069+
1070+
.. [1] https://www.probabilitycourse.com/chapter11/11_2_6_stationary_and_limiting_distributions.php
1071+
.. [2] https://galton.uchicago.edu/~yibi/teaching/stat317/2014/Lectures/Lecture4_6up.pdf
1072+
1073+
See Also
1074+
========
1075+
1076+
sympy.stats.stochastic_process_types.DiscreteMarkovChain.limiting_distribution
1077+
"""
10161078
trans_probs = self.transition_probabilities
1017-
if trans_probs == None:
1018-
return None
1079+
n = self.number_of_states
1080+
1081+
if n == 0:
1082+
return ImmutableMatrix(Matrix([[]]))
1083+
1084+
# symbolic matrix version
10191085
if isinstance(trans_probs, MatrixSymbol):
1020-
wm = MatrixSymbol('wm', 1, trans_probs.shape[0])
1021-
return Lambda((wm, trans_probs), Eq(wm*trans_probs, wm))
1022-
w = IndexedBase('w')
1023-
wi = [w[i] for i in range(trans_probs.shape[0])]
1024-
wm = Matrix([wi])
1025-
eqs = (wm*trans_probs - wm).tolist()[0]
1026-
eqs.append(sum(wi) - 1)
1027-
soln = list(linsolve(eqs, wi))[0]
1086+
wm = MatrixSymbol('wm', 1, n)
1087+
if condition_set:
1088+
return ConditionSet(wm, Eq(wm * trans_probs, wm))
1089+
else:
1090+
return Lambda((wm, trans_probs), Eq(wm * trans_probs, wm))
1091+
1092+
# numeric matrix version
1093+
a = Matrix(trans_probs - Identity(n)).T
1094+
a[0, 0:n] = ones(1, n)
1095+
b = zeros(n, 1)
1096+
b[0, 0] = 1
1097+
1098+
soln = list(linsolve((a, b)))[0]
10281099
return ImmutableMatrix([[sol for sol in soln]])
10291100

1101+
def fixed_row_vector(self):
1102+
"""
1103+
A wrapper for ``stationary_distribution()``.
1104+
"""
1105+
return self.stationary_distribution()
1106+
10301107
@property
10311108
def limiting_distribution(self):
10321109
"""

sympy/stats/tests/test_stochastic_process.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,18 @@ def test_DiscreteMarkovChain():
131131
TS1 = MatrixSymbol('T', 3, 3)
132132
Y5 = DiscreteMarkovChain('Y', trans_probs=TS1)
133133
assert Y5.limiting_distribution(w, TO4).doit() == True
134+
assert Y5.stationary_distribution(condition_set=True).subs(TS1, TO4).contains(w).doit() == S.true
134135
TO6 = Matrix([[S.One, 0, 0, 0, 0],[S.Half, 0, S.Half, 0, 0],[0, S.Half, 0, S.Half, 0], [0, 0, S.Half, 0, S.Half], [0, 0, 0, 0, 1]])
135136
Y6 = DiscreteMarkovChain('Y', trans_probs=TO6)
136137
assert Y6._transient2absorbing() == ImmutableMatrix([[S.Half, 0], [0, 0], [0, S.Half]])
137138
assert Y6._transient2transient() == ImmutableMatrix([[0, S.Half, 0], [S.Half, 0, S.Half], [0, S.Half, 0]])
138139
assert Y6.fundamental_matrix() == ImmutableMatrix([[Rational(3, 2), S.One, S.Half], [S.One, S(2), S.One], [S.Half, S.One, Rational(3, 2)]])
139140
assert Y6.absorbing_probabilities() == ImmutableMatrix([[Rational(3, 4), Rational(1, 4)], [S.Half, S.Half], [Rational(1, 4), Rational(3, 4)]])
140141

142+
# test for zero-sized matrix functionality
143+
X = DiscreteMarkovChain('X', trans_probs=Matrix([[]]))
144+
assert X.number_of_states == 0
145+
assert X.stationary_distribution() == Matrix([[]])
141146
# test communication_class
142147
# see https://drive.google.com/drive/folders/1HbxLlwwn2b3U8Lj7eb_ASIUb5vYaNIjg?usp=sharing
143148
# tutorial 2.pdf

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy