Formalizing Markov Decision Processes in Lean
Documentation: the API documentation
and the companion paper are built by CI and
published to GitHub Pages on every push to main.
Verified Lean algorithms for solving tabular MDPs and proving their properties. The focus of this project is on two main goals:
-
Basic algorithms that can solve robust and risk-averse MDPs of moderate size.
-
Proofs of correctness of algorithms and fundamental MDP properties which can be used independently to prove structural results, such as the optimality of certain policy class.
Library Contents
The main results formalized so far. Everything is developed for finite sample spaces
([FinEnum Ω]) over an arbitrary linear ordered field R
([Field R] [LinearOrder R] [IsStrictOrderedRing R] [CharZero R] [Archimedean R]), so the
statements are free of measurability side conditions. Instantiate at ℚ — as
MDPLibTest.lean does — and every definition is computable and executable;
instantiate at ℝ for the standard theory, where the definitions remain well-formed but
noncomputable (Real.instField and the classical DecidableLE ℝ are noncomputable, so
VaR[X // P, α] cannot be #eval'd over ℝ).
Relationship to Mathlib's measure theory-based probability
Mathlib's measure-theoretic probability (Measure, PMF, ∫, ∫⁻) is not used, and
cannot be: it is uniformly noncomputable and valued in ℝ≥0∞ or an ℝ-normed space, so it
neither evaluates at ℚ nor says anything about a general scalar R. Mathlib also has no
quantiles, VaR or CVaR at all. What is reused is the scalar-generic algebraic API —
Finset.centerMass, which is weighted expectation over exactly this library's class stack, and
the order/convexity results built on it. See the audit note at the top of
MDPLib/Probability/Defs.lean for the details and citations.
Legend: ✅ proof complete · 🚧 statement final, proof still depends on a sorry
(check with #print axioms).
Probability foundations
MDPLib/Probability/Defs.lean,
MDPLib/Probability/Prelude.lean
| Result | Lean name | |
|---|---|---|
| ✅ | Finite distribution on Ω (nonneg weights summing to one), Dirac distribution | Findist, Findist.Δ, Findist.dirac |
| ✅ | A distribution forces its sample space to be nonempty | Findist.nonempty |
| ✅ | Random variables as bare functions Ω → ρ; events as Ω → Bool with a Boolean semiring structure | FinRV, FinRV.instMulBool, FinRV.instAddBool |
| ✅ | Comparison events X ≤ᵣ t, X <ᵣ t, X ≥ᵣ t, X >ᵣ t, X =ᵣ y and the indicator 𝕀 | FinRV.leq, FinRV.lt, FinRV.geq, FinRV.gt, FinRV.eq, FinRV.indicator |
| ✅ | Probability ℙ[B // P], conditional probability ℙ[B | C // P] | Findist.probability, Findist.probabilityCond |
| ✅ | Expectation 𝔼[X // P], conditional expectation 𝔼[X | B // P] and the conditional-expectation random variable 𝔼[X |ᵣ L // P] | Findist.expect, Findist.expectCond, Findist.expectCondRV |
| ✅ | Probability as the expectation of an indicator | Findist.probability_eq_expect_indicator |
| ✅ | Decomposition of a random variable along a discrete label X = ∑ᵢ X · 𝕀[L = i] | FinRV.eq_sum_mul_indicatorEq, Findist.expect_eq_sum_mul_indicatorEq |
| ✅ | Convexity bounds for two-point mixtures; Jensen's inequality for |·| on the uniform distribution | IsProb.self_le_combo_of_le, IsProb.combo_le_self_of_ge, Matrix.abs_dotProduct_le_dotProduct_abs_uniform |
Expectation as a convex combination
MDPLib/Probability/Convexity.lean
| Result | Lean name | |
|---|---|---|
| ✅ | 𝔼[X // P] is Mathlib's Finset.centerMass at weights summing to one | Findist.expect_eq_centerMass |
| ✅ | min X ≤ 𝔼[X] ≤ max X | Findist.expect_ge_min, Findist.expect_le_max |
| ✅ | Jensen's inequality for an arbitrary convex / concave function | Findist.expect_convexOn_le, Findist.expect_concaveOn_ge |
Bridge to Mathlib's standard simplex
MDPLib/Probability/StdSimplex.lean
| Result | Lean name | |
|---|---|---|
| ✅ | A Findist as a point of Convexity.StdSimplex (noncomputable; proof-layer only) | Findist.toStdSimplex |
| ✅ | Mathlib's indexed convex combination is this library's expectation | Findist.toStdSimplex_iConvexComb |
| ✅ | Findist.dirac is StdSimplex.single; the bridge is injective | Findist.toStdSimplex_dirac, Findist.toStdSimplex_injective |
Probability: basic properties
Probability: matrices and Markov reward processes
MDPLib/Probability/Matrix.lean
| Result | Lean name | |
|---|---|---|
| ✅ | Row-stochastic transition matrix | Matrix.ProbabilityMatrix |
| ✅ | A distribution pushed through a transition matrix is again a distribution | Matrix.nonneg_vecMul, Matrix.vecMul_dotProduct_one |
| ✅ | Discounted Markov reward process and its Bellman backup 𝔹[v // Proc] = r + γ · P v | Matrix.DiscountedMRP, Matrix.bellmanBackup |
Quantiles
MDPLib/Probability/Quantile.lean
| Result | Lean name | |
|---|---|---|
| ✅ | Quantile set {q | ℙ[X ≤ q] ≥ α ∧ ℙ[X ≥ q] ≥ 1-α} and its one-sided (lower) relaxation | Statistic.quantile, Statistic.quantileLower, Statistic.IsQuantile, Statistic.IsQuantileLower |
| ✅ | Equivalent characterizations of membership, including the strict form ℙ[X < q] ≤ α | Statistic.mem_quantile_iff, Statistic.mem_quantileLower_iff_probability_lt, Statistic.mem_quantile_of_probability_lt |
| ✅ | Every quantile is a lower quantile | Statistic.quantile_subset_quantileLower |
| ✅ | Reflection: q is an α-quantile of X iff -q is a (1-α)-quantile of -X | Statistic.mem_quantile_neg_iff |
| ✅ | Quantiles under monotone maps; equivalence for strictly monotone maps | Statistic.mem_quantile_comp_of_monotone, Statistic.mem_quantile_comp_iff_of_strictMono, Statistic.mem_quantileLower_comp_iff_of_strictMono |
| ✅ | Lower quantiles of X ≤ Y are cofinal — the basis for monotonicity of VaR | Statistic.isCofinalFor_quantileLower_of_le |
| ✅ | Cash invariance of the lower-quantile set: QuantileLower(X + c) = QuantileLower(X) + c | Statistic.quantileLower_add_const_eq_image |
| 🚧 | The image f '' quantile(X) is cofinal/coinitial in quantile(f∘X) for monotone f | Statistic.isCofinalFor_quantile_comp_image_of_monotone, Statistic.isCoinitialFor_quantile_comp_image_of_monotone |
Value at Risk
| Result | Lean name | |
|---|---|---|
| ✅ | Risk level 0 ≤ α < 1 | Risk.IsRiskLevel, Risk.RiskLevel |
| ✅ | Non-constructive VaR: the greatest lower quantile (equivalently, the greatest quantile) | Risk.IsVaR, Risk.IsVaRQuantile |
| ✅ | Characterization IsVaR v ↔ ℙ[X < v] ≤ α < ℙ[X ≤ v] | Risk.isVaR_iff |
| ✅ | Computable VaR VaR[X // P, α] as a max over the finite candidate set, which is nonempty | Risk.finVaR, Risk.finVaRSet, Risk.finVaRSet_nonempty |
| ✅ | VaR is monotone: X ≤ Y implies VaR[X] ≤ VaR[Y] | Risk.IsVaR.le_of_le |
| 🚧 | Correctness of the computable VaR: IsVaR P X α (VaR[X // P, α]) | Risk.isVaR_finVaR |
| 🚧 | The two definitions (greatest quantile / greatest lower quantile) agree | Risk.isVaRQuantile_iff_isVaR |
| 🚧 | The quantile set is nonempty | Risk.quantile_nonempty |
| ✅ | Translation (cash) invariance for the Risk.IsVaR predicate | Risk.IsVaR.add_const |
| 🚧 | … and for the computable VaR: VaR[X + c] = VaR[X] + c | Risk.finVaR_add_const |
| ✅ | Strictly monotone transformations for the Risk.IsVaR predicate | Risk.IsVaR.comp_of_strictMono |
| 🚧 | … and for the computable VaR: VaR[f∘X] = f(VaR[X]) | Risk.finVaR_comp_of_strictMono |
| 🚧 | Positive homogeneity: VaR[c·X] = c·VaR[X] for c > 0 | Risk.finVaR_const_mul |
The 🚧 entries above are all complete modulo the single missing lemma
Findist.exists_probability_leq_eq_probability_lt_of_lt_max.
Main.lean contains an executable that reads distributions from a JSON
file, computes VaR with computeVaR, and checks it against reference values
(see test_var.json). computeVaR is standalone IO glue over
Array ℚ rather than the verified Risk.finVaR; it is
MDPLibTest.lean that instantiates the library itself at ℚ.
MDPs and histories
| Result | Lean name | |
|---|---|---|
| ✅ | Tabular MDP with finite states, actions, transitions and rewards | MDP |
| ✅ | Histories s₀ a₀ s₁ …, their length, last state, prefixes, and the length-indexed subtype | Hist, Hist.length, Hist.last, Hist.prefix, MDP.HistOfLength |
| ✅ | The count S · (S·A)^t and the explicit index maps Fin (numHist t) ↔ HistOfLength t | MDP.numHist, MDP.idxToHist, MDP.histToIdx |
| 🚧 | … that those maps are mutually inverse, i.e. that the count is the cardinality | leftInverse_idxToHist_histToIdx, rightInverse_idxToHist_histToIdx, exists_init_of_length_eq_zero, exists_foll_of_length_eq_succ |
| ✅ | Embeddings Hist × A × S ↪ Hist and S ↪ Hist | tupleToHistEmbedding, stateToHistEmbedding |
| ✅ | The horizon-t history sets are exactly the histories of length t; Fintype (M.HistOfLength t) | mem_historiesHorizon, length_eq_of_mem_historiesHorizon, MDP.historiesHorizonT |
The ℋ h t family (histories extending h by t steps) is defined but its length
lemma length_of_mem_histories is still 🚧.
MDPLib/Risk/CVaR.lean is an empty stub; it carries notes on which
Mathlib pieces are and are not available for Conditional Value at Risk.
Building and testing
lake build MDPLib # the library
lake -R build MDPLib:docs # the API documentation
lake build mdplib # the binary file
lake build MDPLibTest # tests the elaborationa dn
On linux, you can use build and test the binary file as follows:
./test.sh # computability + regression tests, interpreted and compiled
CI (.github/workflows/ci.yml) runs the library build, that script, the docs and the paper on
every pull request, and deploys to GitHub Pages on main.
Lean Resources
Most useful
- Overview of tactics: https://github.com/madvorak/lean4-tactics
- Comprehensive list of tactics: https://seasawher.github.io/mathlib4-help/tactics/
- Loogle: https://loogle.lean-lang.org/
- Moogle: https://www.moogle.ai/
Others
- Blueprint: https://github.com/PatrickMassot/leanblueprint
- Lean packages and extensions: https://reservoir.lean-lang.org/
- Notations: https://github.com/leanprover-community/lean4-mode/blob/master/data/abbreviations.json
- Resource for Probability: https://korivernon.com/documents/MathematicalStatisticsandDataAnalysis3ed.pdf