|
9 | 9 | linsolve, eye, Or, Not, Intersection, factorial, Contains,
|
10 | 10 | Union, Expr, Function, exp, cacheit, sqrt, pi, gamma,
|
11 | 11 | Ge, Piecewise, Symbol, NonSquareMatrixError, EmptySet,
|
12 |
| - ceiling, MatrixBase) |
| 12 | + ceiling, MatrixBase, ConditionSet, ones, zeros, Identity) |
13 | 13 | from sympy.core.relational import Relational
|
14 | 14 | from sympy.logic.boolalg import Boolean
|
15 | 15 | from sympy.utilities.exceptions import SymPyDeprecationWarning
|
@@ -1012,21 +1012,98 @@ def is_absorbing_chain(self):
|
1012 | 1012 | return any(self.is_absorbing_state(state) == True
|
1013 | 1013 | for state in range(trans_probs.shape[0]))
|
1014 | 1014 |
|
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 | + """ |
1016 | 1078 | 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 |
1019 | 1085 | 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] |
1028 | 1099 | return ImmutableMatrix([[sol for sol in soln]])
|
1029 | 1100 |
|
| 1101 | + def fixed_row_vector(self): |
| 1102 | + """ |
| 1103 | + A wrapper for ``stationary_distribution()``. |
| 1104 | + """ |
| 1105 | + return self.stationary_distribution() |
| 1106 | + |
1030 | 1107 | @property
|
1031 | 1108 | def limiting_distribution(self):
|
1032 | 1109 | """
|
|
0 commit comments