Ergodicity and reversibility¶
A finite-state Markov chain has a unique stationary distribution iff it is
irreducible (equivalently, the directed graph of non-zero transitions
is strongly connected). It is ergodic when, additionally, it is
aperiodic: the greatest common divisor of cycle lengths in the chain's
underlying graph is 1. gpvolve-v2 exposes the standard predicates in
gpvolve.markov (re-exported at the top level):
from gpvolve import (
is_strongly_connected,
is_irreducible, # alias of is_strongly_connected
is_ergodic,
is_reversible,
absorbing_states,
transient_states,
)
is_strongly_connected(msm.transition_matrix)
is_ergodic(msm.transition_matrix)
is_reversible(msm.transition_matrix)
is_strongly_connected ignores self-loops, so a matrix that is row-stochastic
only because every row is absorbed by the diagonal does not pass.
is_ergodic is is_irreducible(P) and any(diag(P) > 0). The diagonal check
is a sufficient aperiodicity criterion: a single positive diagonal entry
produces a length-1 cycle, which forces the period of every (communicating)
state to 1. Every matrix produced by build_transition_matrix carries an
absorption diagonal that satisfies this, so the check is exact for typical
gpvolve workflows. For chains constructed elsewhere with no self-loops, the
sufficient criterion is conservative and may report False on a chain that
is in fact aperiodic by a deeper cycle-gcd argument.
is_reversible performs the detailed-balance test
\(\pi_i P_{ij} = \pi_j P_{ji}\) as a two-step check: support symmetry
(every nonzero \(P_{ij}\) has a nonzero \(P_{ji}\), which already fails for
SSWM-style asymmetric kernels), followed by the equality test on the
remaining pairs.
Reversibility¶
Reversibility¶
A chain is reversible if detailed balance holds:
for every pair \((i, j)\). Under reversibility:
- the backward committor equals \(1 - q^{+}\)
- the symmetric similarity \(D^{1/2} P D^{-1/2}\) (with \(D = \operatorname{diag}(\pi)\)) is symmetric, so its eigenvectors are orthogonal
- PCCA+ has an analytic refinement step
gpvolve-v2 does not assume reversibility anywhere: the backward committor
is computed by explicitly building the time-reversed chain, and PCCA+ uses
the right eigenvectors of P directly rather than the symmetric form.
This makes the analyses work for SSWM/Moran chains on landscapes with
epistasis (which violate detailed balance) at the price of a slightly more
expensive linear algebra path.
When connectivity fails¶
The genotype-phenotype graphs gpvolve-v2 works with are usually strongly
connected by construction (every edge in gpgraph-v2 is added in both
directions). If you build a graph that drops edges for biological reasons
(e.g., one-step accessible only in the wildtype-to-mutant direction), check
the connectivity before running MSM analyses.
Absorbing chains¶
When the chain has one or more absorbing states (\(P_{ii} = 1\), no off-diagonal mass) the standard TPT machinery refuses to operate: the backward committor requires a strictly positive stationary distribution, and on an absorbing chain \(\pi\) puts mass 1 on the absorbing class and 0 on every transient state.
SSWM (fixation="sswm") produces an absorbing chain on any landscape with
at least one local fitness maximum, since deleterious moves have fixation
probability 0 and a state with no beneficial neighbors becomes a true
sink. On a single-peak (Mount-Fuji-like) landscape SSWM produces one
absorbing state; on a rugged landscape with multiple local maxima, each
becomes its own sink and the chain is reducible.
gpvolve-v2 ships an absorbing-chain toolkit in gpvolve.markov.absorbing
(re-exported at the top level). The standard textbook decomposition
(Kemeny & Snell 1976, Finite Markov Chains, Ch 3) writes the matrix as
with \(Q\) the substochastic block on transient states and \(R\) the transient-to-absorbing block. Useful quantities follow:
| Function | Meaning |
|---|---|
fundamental_matrix(P) |
\(N = (I-Q)^{-1}\). Entry \(N_{ij}\) is the expected number of visits to transient state \(j\) starting from transient \(i\). |
absorption_probabilities(P) |
\(B = N R\). Row \(i\), column \(k\) is \(\Pr[\text{absorbed in } k \mid X_0 = i]\). Row sums equal 1. |
quasi_stationary_distribution(P) |
The Darroch-Seneta (1965) left Perron vector of \(Q\). Conditional on non-absorption, the chain converges to this distribution; the lifetime in QSD is \(1/(1 - \lambda_1)\). |
conditional_mfpt(P, A, B) |
\(E[\tau_B \mid \text{absorbed in } B, X_0 = i]\) via Doob's h-transform (Norris 1997, Theorem 4.2.3). |
absorption_rate(P, A, B) |
\(1/E[\tau_B \mid X_0 \sim \text{initial}]\). The chemical-kinetics rate constant for an irreversible A \(\to\) B process (Hanggi-Talkner-Borkovec 1990). |
The mfpt solver raises GpvolveError when the chain has absorbing
states outside the target set, because in that regime the standard
\((I - Q) m = 1\) system is singular and the unconditional expectation
\(E[\tau_B]\) diverges. Use conditional_mfpt instead.
Worked example¶
import numpy as np
from gpvolve import (
absorbing_states,
absorption_rate,
fundamental_matrix,
quasi_stationary_distribution,
)
# Build any SSWM chain on a single-peak landscape; here on L=5 binary map.
# msm = ... # see examples/streamlit/utils
P = msm.transition_matrix
abs_idx = absorbing_states(P) # e.g., [31] -> the fitness peak
N, transient = fundamental_matrix(P) # 31x31 expected-visits matrix
qsd, _, lam = quasi_stationary_distribution(P)
# 1 - lam is the per-step absorption probability from the QSD;
# 1 / (1 - lam) is the metastable lifetime.
# Compare reactive rate (TPT) with absorption rate (MFPT-based):
A = 0 # genotype "AAAAA"
B = 31 # peak "TTTTT"
k_reactive = gpvolve.rate(P, A=A, B=B) # 0.0 on absorbing chains
k_absorption = absorption_rate(P, A=A, B=B) # > 0; the meaningful rate
Reactive rate vs. absorption rate¶
For an ergodic chain (Moran or McCandlish at any finite \(N\)),
rate(P, A, B) returns the long-time reactive flux
(Berezhkovskii-Hummer-Szabo 2009), and absorption_rate(P, A, B) returns
\(1 / E[\tau_B]\). The two agree in the slow-transition limit
(Eyring/Kramers).
For an absorbing chain (e.g., SSWM), the reactive rate collapses to 0
because \(\pi_A = 0\), and only absorption_rate carries useful kinetic
information. The streamlit TPT explorer shows both side-by-side and
explains the contrast when SSWM produces a non-ergodic chain.