From 0b8f57ee78da7dca3b5708e32670473eb8f7508f Mon Sep 17 00:00:00 2001 From: Shoaib Khan Date: Wed, 9 Sep 2026 22:26:22 -0400 Subject: [PATCH] TST: Cover the multi-distribution branch in test_left_eigen_vec Test_markovchain_stationary_distributions_KMRMarkovMatrix2 only ever used the irreducible KMR matrix (N=27), which has exactly one stationary distribution. As a result, test_left_eigen_vec's else branch (the multiple-stationary-distributions case) and the `if len(stat_shape) == 1` arm in setup_method were dead code never executed by the suite. Factor the shared test methods into a base class and add a new Test_markovchain_stationary_distributions_ReducibleMarkovMatrix subclass built on a reducible 4-state matrix with two absorbing states, giving n_stat_dists == 2 and exercising the else branch. stationary_distributions is documented as always ndim=2, so the dead `if len(stat_shape) == 1` arm is removed rather than duplicated. The existing KMR matrix test behavior and assertions are unchanged. Fixes #961 Co-Authored-By: Claude Fable 5.1 --- quantecon/markov/tests/test_core.py | 81 ++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/quantecon/markov/tests/test_core.py b/quantecon/markov/tests/test_core.py index 06f55429e..6da206d21 100644 --- a/quantecon/markov/tests/test_core.py +++ b/quantecon/markov/tests/test_core.py @@ -178,31 +178,18 @@ def test_markovchain_pmatrices(): # Basic Class Structure with Setup # #################################### -class Test_markovchain_stationary_distributions_KMRMarkovMatrix2(): +class _Test_markovchain_stationary_distributions_Base(): """ - Test Suite for MarkovChain.stationary_distributions using KMR Markov - Matrix [suitable for nose] + Shared test suite for MarkovChain.stationary_distributions. Concrete + subclasses provide `setup_method`, which must set `self.mc`, + `self.stationary` (`self.mc.stationary_distributions`) and + `self.n_stat_dists` (the number of stationary distributions), so that + both the single- and multiple-stationary-distribution cases are + exercised by `test_left_eigen_vec`. """ - # Starting Values # - - N = 27 - epsilon = 1e-2 - p = 1/3 TOL = 1e-2 - def setup_method(self): - """ Setup a KMRMarkovMatrix and Compute Stationary Values """ - self.P = KMR_Markov_matrix_sequential(self.N, self.p, self.epsilon) - self.mc = MarkovChain(self.P) - self.stationary = self.mc.stationary_distributions - stat_shape = self.stationary.shape - - if len(stat_shape) == 1: - self.n_stat_dists = 1 - else: - self.n_stat_dists = stat_shape[0] - def test_markov_matrix(self): "Check that each row of matrix sums to 1" mc = self.mc @@ -233,6 +220,60 @@ def test_left_eigen_vec(self): assert_allclose(curr_v @ mc.P, curr_v, atol=self.TOL) +class Test_markovchain_stationary_distributions_KMRMarkovMatrix2( + _Test_markovchain_stationary_distributions_Base): + """ + Test Suite for MarkovChain.stationary_distributions using KMR Markov + Matrix [suitable for nose] + + The KMR matrix is irreducible, so this suite only ever exercises the + single-stationary-distribution case (`n_stat_dists == 1`); see + Test_markovchain_stationary_distributions_ReducibleMarkovMatrix below + for the multiple-stationary-distribution case. + """ + + # Starting Values # + + N = 27 + epsilon = 1e-2 + p = 1/3 + + def setup_method(self): + """ Setup a KMRMarkovMatrix and Compute Stationary Values """ + self.P = KMR_Markov_matrix_sequential(self.N, self.p, self.epsilon) + self.mc = MarkovChain(self.P) + self.stationary = self.mc.stationary_distributions + # stationary_distributions is always 2-dimensional, of shape + # (n_stat_dists, mc.n) + self.n_stat_dists = self.stationary.shape[0] + + +class Test_markovchain_stationary_distributions_ReducibleMarkovMatrix( + _Test_markovchain_stationary_distributions_Base): + """ + Test Suite for MarkovChain.stationary_distributions using a reducible + matrix with two recurrent classes (two absorbing states, reached from + two transient states), so that `n_stat_dists > 1` and the + multiple-stationary-distribution branch of test_left_eigen_vec is + exercised. + """ + + # States 0 and 1 are absorbing (each its own recurrent class); states 2 + # and 3 are transient. + P = np.array([[1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.3, 0.3, 0.4, 0.0], + [0.0, 0.2, 0.0, 0.8]]) + + def setup_method(self): + """ Setup the reducible MarkovChain and Compute Stationary Values """ + self.mc = MarkovChain(self.P) + self.stationary = self.mc.stationary_distributions + # stationary_distributions is always 2-dimensional, of shape + # (n_stat_dists, mc.n) + self.n_stat_dists = self.stationary.shape[0] + + def test_simulate_shape(): P = [[0.4, 0.6], [0.2, 0.8]] mcs = [MarkovChain(P), MarkovChain(sparse.csr_matrix(P))]