Skip to content

Commit 5d94b5c

Browse files
dennjclaude
andcommitted
feat(Probability/Markov): stationary distributions for stochastic matrices
This PR proves that every row-stochastic matrix on a finite nonempty state space has a stationary distribution in the standard simplex. Main additions to `Mathlib/Probability/Markov/Stationary.lean`: - `IsStationary`: A distribution μ is stationary for matrix P if μ ᵥ* P = μ - `cesaroAverage`: Cesàro average of iterates of a vector under a matrix - `Matrix.rowStochastic.exists_stationary_distribution`: existence theorem The proof uses Cesàro averaging: start with uniform distribution, form averages, extract convergent subsequence by compactness, show limit is stationary via L¹ non-expansiveness. Also adds `vecMul_mem_stdSimplex` to `Stochastic.lean`: multiplying a probability vector by a row-stochastic matrix preserves simplex membership. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0fec9f3 commit 5d94b5c

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

Mathlib/LinearAlgebra/Matrix/Stochastic.lean

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module
88
public import Mathlib.Data.Matrix.Basic
99
public import Mathlib.Data.Matrix.Mul
1010
public import Mathlib.Analysis.Convex.Basic
11+
public import Mathlib.Analysis.Convex.StdSimplex
1112
public import Mathlib.LinearAlgebra.Matrix.Permutation
1213

1314
/-!
@@ -226,4 +227,13 @@ lemma transpose_mem_colStochastic_iff_mem_rowStochastic :
226227
and_congr_left_iff]
227228
exact fun _ ↦ forall_swap
228229

230+
/-- Multiplying a probability vector on the left by a row-stochastic matrix
231+
preserves membership in the standard simplex. -/
232+
lemma vecMul_mem_stdSimplex (hM : M ∈ rowStochastic R n)
233+
{v : n → R} (hv : v ∈ stdSimplex R n) : v ᵥ* M ∈ stdSimplex R n := by
234+
refine ⟨fun j => Finset.sum_nonneg fun i _ => mul_nonneg (hv.1 i) (hM.1 i j), ?_⟩
235+
simp only [vecMul, dotProduct]
236+
rw [Finset.sum_comm, ← hv.2]
237+
exact Finset.sum_congr rfl fun i _ => by simp [← mul_sum, sum_row_of_mem_rowStochastic hM]
238+
229239
end Matrix
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/-
2+
Copyright (c) 2026 Dennj Osele. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Dennj Osele
5+
-/
6+
module
7+
8+
public import Mathlib.LinearAlgebra.Matrix.Stochastic
9+
public import Mathlib.Analysis.Convex.StdSimplex
10+
public import Mathlib.Analysis.Normed.Lp.PiLp
11+
public import Mathlib.Order.Filter.AtTopBot.Archimedean
12+
13+
/-!
14+
# Stationary Distributions for Stochastic Matrices
15+
16+
This file proves that every row-stochastic matrix on a finite nonempty state space has a
17+
stationary distribution in the standard simplex.
18+
19+
## Main definitions
20+
21+
* `IsStationary`: A distribution `μ` is stationary for a matrix `P` if `μ ᵥ* P = μ`.
22+
* `cesaroAverage`: The Cesàro average of the iterates of a vector under a matrix.
23+
* `uniformDistribution`: The uniform distribution on a finite nonempty type.
24+
25+
## Main results
26+
27+
* `Matrix.rowStochastic.exists_stationary_distribution`: Every row-stochastic matrix on a finite
28+
nonempty state space has a stationary distribution in the standard simplex.
29+
30+
## Proof outline
31+
32+
The existence proof uses the Cesàro averaging technique:
33+
1. Start with the uniform distribution `x₀`
34+
2. Form Cesàro averages `xₖ = (1/(k+1)) ∑ᵢ x₀ ᵥ* Pⁱ`
35+
3. By compactness of the simplex, extract a convergent subsequence `xₖₙ → μ`
36+
4. Show `μ` is stationary using the L¹ non-expansiveness of stochastic matrices
37+
38+
## Tags
39+
40+
stochastic matrix, Markov chain, stationary distribution, Cesàro average
41+
-/
42+
43+
@[expose] public section
44+
45+
open Finset Function Matrix ENNReal Filter
46+
open scoped BigOperators
47+
48+
variable {R n : Type*} [Fintype n] [DecidableEq n]
49+
50+
/-! ### Stationary distributions -/
51+
52+
section Stationary
53+
54+
variable {R : Type*} [Semiring R]
55+
56+
/-- A distribution `μ` is stationary for a matrix `P` if `μ ᵥ* P = μ`. -/
57+
class IsStationary (μ : n → R) (P : Matrix n n R) : Prop where
58+
stationary : μ ᵥ* P = μ
59+
60+
/-- Powers of a matrix preserve stationary distributions. -/
61+
lemma IsStationary.pow (μ : n → R) (P : Matrix n n R) [IsStationary μ P] (k : ℕ) :
62+
IsStationary μ (P ^ k) :=
63+
by induction k with
64+
| zero => simp [Matrix.vecMul_one]
65+
| succ k ih => rw [pow_succ, ← Matrix.vecMul_vecMul, ih, IsStationary.stationary]⟩
66+
67+
end Stationary
68+
69+
/-! ### Cesàro averages -/
70+
71+
section CesaroAverage
72+
73+
variable {R : Type*} [DivisionRing R] [PartialOrder R] [IsStrictOrderedRing R]
74+
75+
/-- The Cesàro average of the iterates of a vector under a matrix. -/
76+
def cesaroAverage (x₀ : n → R) (P : Matrix n n R) (k : ℕ) : n → R :=
77+
(k + 1 : R)⁻¹ • ∑ i ∈ Finset.range (k + 1), x₀ ᵥ* (P ^ i)
78+
79+
variable [Nonempty n]
80+
81+
/-- The uniform distribution on a finite nonempty type. -/
82+
@[reducible, nolint unusedArguments]
83+
def uniformDistribution (R : Type*) (n : Type*) [Fintype n] [DivisionRing R] :
84+
n → R := fun _ => 1 / Fintype.card n
85+
86+
end CesaroAverage
87+
88+
/-! ### L¹ non-expansiveness for row-stochastic matrices -/
89+
90+
section L1Norm
91+
92+
variable {M : Matrix n n ℝ}
93+
94+
omit [DecidableEq n] in
95+
/-- The L¹ norm of a function equals the sum of absolute values. -/
96+
private lemma l1_nnnorm_eq_sum (f : PiLp 1 (fun _ : n => ℝ)) : (‖f‖₊ : ℝ) = ∑ i, |f.ofLp i| := by
97+
rw [PiLp.nnnorm_eq_sum one_ne_top]
98+
simp only [ENNReal.toReal_one, NNReal.rpow_one, div_one, NNReal.coe_sum, coe_nnnorm,
99+
Real.norm_eq_abs]
100+
101+
omit [DecidableEq n] in
102+
/-- The L¹ norm of a probability vector is 1. -/
103+
private lemma l1_nnnorm_eq_one {x : n → ℝ} (hx : x ∈ stdSimplex ℝ n) : ‖WithLp.toLp 1 x‖₊ = 1 := by
104+
rw [← NNReal.coe_inj, NNReal.coe_one, l1_nnnorm_eq_sum,
105+
(sum_congr rfl fun i _ => abs_of_nonneg (hx.1 i) : ∑ i, |x i| = ∑ i, x i), hx.2]
106+
107+
namespace Matrix.rowStochastic
108+
109+
/-- Powers of row-stochastic matrices are row-stochastic. -/
110+
theorem pow_mem (hM : M ∈ rowStochastic ℝ n) (k : ℕ) : M ^ k ∈ rowStochastic ℝ n :=
111+
Submonoid.pow_mem (rowStochastic ℝ n) hM k
112+
113+
/-- Row-stochastic matrices are non-expansive operators on probability vectors in the L¹ norm.
114+
This is the key contraction property for Markov chains. -/
115+
theorem nnnorm_vecMul_sub_le (hM : M ∈ rowStochastic ℝ n) (x y : n → ℝ) :
116+
‖WithLp.toLp 1 (x ᵥ* M - y ᵥ* M)‖₊ ≤ ‖WithLp.toLp 1 (x - y)‖₊ := by
117+
have hxy : x ᵥ* M - y ᵥ* M = fun j => ∑ i, (x i - y i) * M i j := by
118+
ext j; simp only [Pi.sub_apply, vecMul, dotProduct, sub_mul, sum_sub_distrib]
119+
have key : ∑ j, |∑ i, (x i - y i) * M i j| ≤ ∑ i, |x i - y i| := calc
120+
∑ j, |∑ i, (x i - y i) * M i j|
121+
≤ ∑ j, ∑ i, |x i - y i| * M i j := sum_le_sum fun j _ => (abs_sum_le_sum_abs _ _).trans
122+
(sum_le_sum fun i _ => by rw [abs_mul, abs_of_nonneg (hM.1 i j)])
123+
_ = ∑ i, |x i - y i| := by
124+
rw [sum_comm]; simp_rw [← mul_sum, sum_row_of_mem_rowStochastic hM, mul_one]
125+
have hnorm : (‖WithLp.toLp 1 (x ᵥ* M - y ᵥ* M)‖₊ : ℝ) = ∑ j, |∑ i, (x i - y i) * M i j| := by
126+
rw [l1_nnnorm_eq_sum]; exact sum_congr rfl fun j _ => congrArg abs (congrFun hxy j)
127+
exact NNReal.coe_le_coe.mp (by rw [hnorm, l1_nnnorm_eq_sum]; exact key)
128+
129+
/-- Row-stochastic matrices are non-expansive in L¹ norm (version with norm instead of nnnorm). -/
130+
theorem norm_vecMul_sub_le (hM : M ∈ rowStochastic ℝ n) (x y : n → ℝ) :
131+
‖WithLp.toLp 1 (x ᵥ* M - y ᵥ* M)‖ ≤ ‖WithLp.toLp 1 (x - y)‖ :=
132+
mod_cast nnnorm_vecMul_sub_le hM x y
133+
134+
end Matrix.rowStochastic
135+
136+
end L1Norm
137+
138+
/-! ### Existence of stationary distributions -/
139+
140+
section Existence
141+
142+
variable {M : Matrix n n ℝ}
143+
144+
/-- The Cesàro average of a probability vector under a row-stochastic matrix
145+
belongs to the standard simplex. -/
146+
lemma cesaroAverage_mem_stdSimplex (hM : M ∈ Matrix.rowStochastic ℝ n)
147+
{x₀ : n → ℝ} (hx₀ : x₀ ∈ stdSimplex ℝ n) (k : ℕ) :
148+
cesaroAverage x₀ M k ∈ stdSimplex ℝ n := by
149+
have hmem i : x₀ ᵥ* M ^ i ∈ stdSimplex ℝ n :=
150+
vecMul_mem_stdSimplex (Matrix.rowStochastic.pow_mem hM i) hx₀
151+
refine ⟨fun j => mul_nonneg (inv_nonneg.mpr (by linarith : (0 : ℝ) ≤ k + 1))
152+
(by simp only [Finset.sum_apply]; exact sum_nonneg fun i _ => (hmem i).1 j), ?_⟩
153+
simp only [cesaroAverage, Pi.smul_apply, smul_eq_mul, Finset.sum_apply, ← mul_sum,
154+
sum_comm (γ := n)]
155+
rw [show ∑ i ∈ range (k + 1), ∑ j, (x₀ ᵥ* M ^ i) j = k + 1 from
156+
(sum_congr rfl fun i _ => (hmem i).2).trans (by simp), mul_comm]
157+
exact mul_inv_cancel₀ (by linarith : (k : ℝ) + 10)
158+
159+
/-- The Cesàro average is almost invariant: applying the matrix changes it by at most `2/(k+1)`. -/
160+
lemma norm_cesaroAverage_vecMul_sub_le (hM : M ∈ Matrix.rowStochastic ℝ n)
161+
{x₀ : n → ℝ} (hx₀ : x₀ ∈ stdSimplex ℝ n) (k : ℕ) :
162+
‖WithLp.toLp 1 (cesaroAverage x₀ M k ᵥ* M - cesaroAverage x₀ M k)‖ ≤ 2 / (k + 1) := by
163+
have hk : (0 : ℝ) < k + 1 := by linarith
164+
have hsimp : cesaroAverage x₀ M k ᵥ* M - cesaroAverage x₀ M k =
165+
(k + 1 : ℝ)⁻¹ • (x₀ ᵥ* M ^ (k + 1) - x₀) := by
166+
unfold cesaroAverage
167+
rw [Matrix.smul_vecMul, ← smul_sub, Matrix.sum_vecMul]; congr 1
168+
have h1 : ∀ i, (x₀ ᵥ* M ^ i) ᵥ* M = x₀ ᵥ* M ^ (i + 1) := fun i => by
169+
rw [Matrix.vecMul_vecMul, ← pow_succ]
170+
simp_rw [h1]
171+
rw [Finset.sum_range_succ' (fun i => x₀ ᵥ* M ^ i), Finset.sum_range_succ, pow_zero,
172+
Matrix.vecMul_one]; abel
173+
rw [hsimp, WithLp.toLp_smul, norm_smul, Real.norm_eq_abs, abs_of_pos (inv_pos.mpr hk)]
174+
have h1 := vecMul_mem_stdSimplex (Matrix.rowStochastic.pow_mem hM (k + 1)) hx₀
175+
have : ‖WithLp.toLp 1 (x₀ ᵥ* M ^ (k + 1) - x₀)‖ ≤ 2 := calc
176+
‖WithLp.toLp 1 (x₀ ᵥ* M ^ (k + 1) - x₀)‖₊
177+
_ = ‖WithLp.toLp 1 (x₀ ᵥ* M ^ (k + 1)) - WithLp.toLp 1 x₀‖₊ := by rw [← WithLp.toLp_sub]
178+
_ ≤ ‖WithLp.toLp 1 (x₀ ᵥ* M ^ (k + 1))‖₊ + ‖WithLp.toLp 1 x₀‖₊ := nnnorm_sub_le _ _
179+
_ = 2 := by rw [l1_nnnorm_eq_one h1, l1_nnnorm_eq_one hx₀]; norm_num
180+
calc (k + 1 : ℝ)⁻¹ * ‖WithLp.toLp 1 (x₀ ᵥ* M ^ (k + 1) - x₀)‖
181+
_ ≤ (k + 1 : ℝ)⁻¹ * 2 := by gcongr
182+
_ = 2 / (k + 1) := by ring
183+
184+
variable [Nonempty n]
185+
186+
omit [DecidableEq n] in
187+
/-- The uniform distribution belongs to the standard simplex. -/
188+
lemma uniformDistribution_mem_stdSimplex : uniformDistribution ℝ (n := n) ∈ stdSimplex ℝ n :=
189+
fun _ => by simp only [uniformDistribution, one_div, inv_nonneg]; positivity,
190+
by simp [uniformDistribution, Finset.card_univ, nsmul_eq_mul]⟩
191+
192+
namespace Matrix.rowStochastic
193+
194+
/-- Every row-stochastic matrix on a finite nonempty state space has a stationary distribution. -/
195+
theorem exists_stationary_distribution (hM : M ∈ rowStochastic ℝ n) :
196+
∃ μ : n → ℝ, μ ∈ stdSimplex ℝ n ∧ IsStationary μ M := by
197+
let x₀ := uniformDistribution ℝ (n := n)
198+
let xₖ : ℕ → n → ℝ := fun k => cesaroAverage x₀ M k
199+
have hxₖ k : xₖ k ∈ stdSimplex ℝ n :=
200+
cesaroAverage_mem_stdSimplex hM uniformDistribution_mem_stdSimplex k
201+
obtain ⟨μ, hμ_mem, nₖ, hnₖ_mono, hnₖ_lim⟩ := (isCompact_stdSimplex n).tendsto_subseq hxₖ
202+
refine ⟨μ, hμ_mem, ⟨?_⟩⟩
203+
have h_lim_diff : Tendsto (fun k => xₖ (nₖ k) ᵥ* M - xₖ (nₖ k)) atTop (nhds (μ ᵥ* M - μ)) :=
204+
((Continuous.matrix_vecMul continuous_id continuous_const).continuousAt.tendsto.comp
205+
hnₖ_lim).sub hnₖ_lim
206+
have h_error_tendsto : Tendsto (fun k => ‖WithLp.toLp 1 (xₖ (nₖ k) ᵥ* M - xₖ (nₖ k))‖)
207+
atTop (nhds 0) :=
208+
tendsto_of_tendsto_of_tendsto_of_le_of_le tendsto_const_nhds
209+
((tendsto_const_nhds.div_atTop tendsto_id).comp
210+
((tendsto_natCast_atTop_atTop.comp hnₖ_mono.tendsto_atTop).atTop_add tendsto_const_nhds))
211+
(fun _ => norm_nonneg _)
212+
(fun k => norm_cesaroAverage_vecMul_sub_le hM uniformDistribution_mem_stdSimplex (nₖ k))
213+
have h_norm_zero := tendsto_nhds_unique
214+
(((PiLp.continuous_toLp 1 (fun _ => ℝ)).continuousAt.tendsto.comp h_lim_diff).norm)
215+
h_error_tendsto
216+
exact sub_eq_zero.mp ((WithLp.toLp_injective 1).eq_iff.mp (norm_eq_zero.mp h_norm_zero))
217+
218+
end Matrix.rowStochastic
219+
220+
end Existence

0 commit comments

Comments
 (0)