diff --git a/LeanPool/AiyagariEquilibrium.lean b/LeanPool/AiyagariEquilibrium.lean new file mode 100644 index 000000000..e8aad7c5f --- /dev/null +++ b/LeanPool/AiyagariEquilibrium.lean @@ -0,0 +1,21 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module + +public import LeanPool.AiyagariEquilibrium.Models.AiyagariTauchenWitness +public import LeanPool.AiyagariEquilibrium.Equilibrium.Bisection + +/-! +# Normalized capped log-household clearing and dynamic programming + +Source: url:https://github.com/LeanEconomics/LeanEconomics +Authors: Robert Kirkby +Status: verified +Main declarations: `LeanEconomics.aiyagariTauchen_equilibriumRate_unique_Ioo` +Tags: mathematical-economics, dynamic-programming, stationary-equilibrium, markov-processes +MSC: 91B50, 90C39, 60J05 +-/ diff --git a/LeanPool/AiyagariEquilibrium/Analysis/DecreasingIncrements.lean b/LeanPool/AiyagariEquilibrium/Analysis/DecreasingIncrements.lean new file mode 100644 index 000000000..892a1ecd8 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Analysis/DecreasingIncrements.lean @@ -0,0 +1,161 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Analysis.Convex.SpecificFunctions.Basic + +/-! +# Decreasing increments of a concave function + +Numbered Light references use the October 31, 2018 author draft, printed page 13: +https://www.researchgate.net/publication/314238962 + +A concave function's increment over an interval of fixed length shrinks as the interval +slides right. In economics this is the increasing-differences (supermodularity) condition +that monotone-comparative-statics arguments run on, usually stated as a cross partial +`-u'' > 0`. Stated this way it needs no derivatives and no smoothness at all. + +The proof is two applications of concavity. Writing `L = (c₂ + Δ) - c₁`, the two interior +points `c₁ + Δ` and `c₂` are *complementary* convex combinations of the endpoints `c₁` and +`c₂ + Δ`: the weights on `c₂ + Δ` are `Δ / L` and `(c₂ - c₁) / L`, which sum to one. Adding +the two concavity inequalities makes the weights cancel. +-/ + +@[expose] public section + +open Set + +/-- **Decreasing increments.** For a concave `u`, sliding an interval to the right by `Δ` +cannot increase the increment of `u` across it. -/ +theorem ConcaveOn.sub_le_sub_of_shift {s : Set ℝ} {u : ℝ → ℝ} (hu : ConcaveOn ℝ s u) + {c₁ c₂ Δ : ℝ} (h₁ : c₁ ∈ s) (h₂ : c₂ + Δ ∈ s) (hc : c₁ ≤ c₂) (hΔ : 0 ≤ Δ) : + u (c₂ + Δ) - u (c₁ + Δ) ≤ u c₂ - u c₁ := by + rcases eq_or_lt_of_le (show c₁ ≤ c₂ + Δ by linarith) with hdeg | hpos + · -- `c₁ = c₂ + Δ` forces `Δ = 0` and `c₁ = c₂`, and the claim is an equality + have hΔ0 : Δ = 0 := by linarith + have hcc : c₁ = c₂ := by linarith + subst hΔ0 + simp [hcc] + · have hL : 0 < c₂ + Δ - c₁ := by linarith + set θ : ℝ := Δ / (c₂ + Δ - c₁) with hθdef + have hθ0 : 0 ≤ θ := div_nonneg hΔ hL.le + have hθ1 : 0 ≤ 1 - θ := by + rw [hθdef, sub_nonneg, div_le_one hL] + linarith + -- `c₁ + Δ` and `c₂` are complementary convex combinations of `c₁` and `c₂ + Δ` + have e1 : (1 - θ) * c₁ + θ * (c₂ + Δ) = c₁ + Δ := by + rw [hθdef]; field_simp; ring + have e2 : θ * c₁ + (1 - θ) * (c₂ + Δ) = c₂ := by + rw [hθdef]; field_simp; ring + have i1 := hu.2 h₁ h₂ hθ1 hθ0 (by ring) + have i2 := hu.2 h₁ h₂ hθ0 hθ1 (by ring) + rw [smul_eq_mul, smul_eq_mul, smul_eq_mul, smul_eq_mul, e1] at i1 + rw [smul_eq_mul, smul_eq_mul, smul_eq_mul, smul_eq_mul, e2] at i2 + nlinarith [i1, i2] + +/-! ### Lipschitz away from the left endpoint + +A concave function on `Ioi 0` need not be Lipschitz there -- utility functions used in +economics are exactly the ones that are not, diverging at zero -- but it IS Lipschitz on +`[δ, ∞)` for any `δ > 0`, with a constant read off a single secant. No differentiability is +needed: the bound comes from antitonicity of secant slopes. + +This is what lets a marginal argument be run without a derivative. Where the textbook +argument bounds the marginal utility of consumption by `u'(δ)`, this bounds it by the slope +of `u` across `[δ/2, δ]`, which is available from concavity alone. -/ + +/-- The secant slope of `u` across `[δ/2, δ]`, which bounds every slope further right. -/ +noncomputable def slopeBound (u : ℝ → ℝ) (δ : ℝ) : ℝ := (u δ - u (δ / 2)) / (δ / 2) + +theorem slopeBound_nonneg {u : ℝ → ℝ} (hmono : MonotoneOn u (Ioi 0)) {δ : ℝ} (hδ : 0 < δ) : + 0 ≤ slopeBound u δ := by + refine div_nonneg (sub_nonneg.mpr (hmono ?_ ?_ (by linarith))) (by linarith) + · exact mem_Ioi.mpr (by linarith) + · exact mem_Ioi.mpr hδ + +/-- The one-sided bound: moving right from `a ≥ δ` raises `u` by at most the slope bound +times the distance. -/ +theorem ConcaveOn.sub_le_slopeBound_mul {u : ℝ → ℝ} (hu : ConcaveOn ℝ (Ioi 0) u) {δ : ℝ} + (hδ : 0 < δ) {a b : ℝ} (ha : δ ≤ a) (hab : a ≤ b) : + u b - u a ≤ slopeBound u δ * (b - a) := by + rcases eq_or_lt_of_le hab with rfl | hlt + · simp + have hhalf : (0 : ℝ) < δ / 2 := by linarith + have hmemh : δ / 2 ∈ Ioi (0 : ℝ) := mem_Ioi.mpr hhalf + have hmemd : δ ∈ Ioi (0 : ℝ) := mem_Ioi.mpr hδ + have hmema : a ∈ Ioi (0 : ℝ) := mem_Ioi.mpr (by linarith) + have hmemb : b ∈ Ioi (0 : ℝ) := mem_Ioi.mpr (by linarith) + -- the slope across `[a, b]` is at most the slope bound + have hkey : (u b - u a) / (b - a) ≤ slopeBound u δ := by + rcases eq_or_lt_of_le ha with rfl | hgt + · -- `a = δ`: one step + have h := hu.slope_anti_adjacent hmemh hmemb (by linarith) hlt + simpa only [slopeBound, show δ - δ / 2 = δ / 2 by ring] using h + · -- `a > δ`: chain two steps through `δ` + have h1 := hu.slope_anti_adjacent hmemd hmemb hgt hlt + have h2 := hu.slope_anti_adjacent hmemh hmema (by linarith) hgt + simp only [slopeBound, show δ - δ / 2 = δ / 2 by ring] at h2 ⊢ + linarith + rw [div_le_iff₀ (by linarith)] at hkey + linarith + +/-- **A concave function is Lipschitz away from the left endpoint**, with an explicit +constant and no appeal to differentiability. -/ +theorem ConcaveOn.abs_sub_le_slopeBound_mul {u : ℝ → ℝ} (hu : ConcaveOn ℝ (Ioi 0) u) + (hmono : MonotoneOn u (Ioi 0)) {δ : ℝ} (hδ : 0 < δ) {x y : ℝ} + (hx : δ ≤ x) (hy : δ ≤ y) : + |u x - u y| ≤ slopeBound u δ * |x - y| := by + rcases le_total y x with h | h + · rw [abs_of_nonneg (sub_nonneg.mpr (hmono (mem_Ioi.mpr (by linarith)) + (mem_Ioi.mpr (by linarith)) h)), abs_of_nonneg (by linarith)] + exact hu.sub_le_slopeBound_mul hδ hy h + · rw [abs_sub_comm, abs_sub_comm x y, + abs_of_nonneg (sub_nonneg.mpr (hmono (mem_Ioi.mpr (by linarith)) + (mem_Ioi.mpr (by linarith)) h)), abs_of_nonneg (by linarith)] + exact hu.sub_le_slopeBound_mul hδ hx h + +/-! ### Scaling a concave function + +For a concave, strictly positive `z`, the ratio `α / z (α x)` rises with `α`. This is Lemma 4 +of Light (2018), one of the three ingredients of his Theorem 1 (savings rise with the interest +rate when relative risk aversion is at most one). + +Like the increments lemma above, it needs no derivative: `α₁ x` is a convex combination of `0` +and `α₂ x` with weight `α₁ / α₂`, so concavity gives the inequality directly, and strict +positivity at the origin is what turns it from weak into the stated form. +-/ + +/-- **Light (2018), Lemma 4**, on any convex set containing the origin. In the application `z` +is the consumption function, which is concave on the asset region and nowhere else, so the +original statement over `[0, ∞)` would not reach it. Positivity is needed only at the origin +and at the far point: concavity carries it to everything in between. -/ +theorem ConcaveOn.div_le_div_of_scale {z : ℝ → ℝ} {s : Set ℝ} (hz : ConcaveOn ℝ s z) + (h0 : (0 : ℝ) ∈ s) (hz0 : 0 < z 0) {α₁ α₂ x : ℝ} (h1 : 0 < α₁) (h12 : α₁ ≤ α₂) + (hm2 : α₂ * x ∈ s) (hp2 : 0 < z (α₂ * x)) : + α₁ / z (α₁ * x) ≤ α₂ / z (α₂ * x) := by + have h2 : 0 < α₂ := lt_of_lt_of_le h1 h12 + -- `α₁ x` is a convex combination of `0` and `α₂ x` + have hcomb : (1 - α₁ / α₂) • (0 : ℝ) + (α₁ / α₂) • (α₂ * x) = α₁ * x := by + simp only [smul_eq_mul, mul_zero, zero_add] + field_simp + have hw1 : (0 : ℝ) ≤ 1 - α₁ / α₂ := by + rw [sub_nonneg, div_le_one h2]; exact h12 + have hw2 : (0 : ℝ) < α₁ / α₂ := div_pos h1 h2 + have hcc := hz.2 h0 hm2 hw1 hw2.le (by ring) + rw [hcomb] at hcc + simp only [smul_eq_mul] at hcc + -- concavity carries positivity from the endpoints to `α₁ x` + have hp1 : 0 < z (α₁ * x) := by + nlinarith [mul_nonneg hw1 hz0.le, mul_pos hw2 hp2] + rw [div_le_div_iff₀ hp1 hp2] + have hkey : α₁ * z (α₂ * x) ≤ α₂ * z (α₁ * x) := by + have hmul := mul_le_mul_of_nonneg_left hcc h2.le + have hexp : α₂ * ((1 - α₁ / α₂) * z 0 + α₁ / α₂ * z (α₂ * x)) + = (α₂ - α₁) * z 0 + α₁ * z (α₂ * x) := by field_simp + rw [hexp] at hmul + have hz0' : 0 ≤ (α₂ - α₁) * z 0 := mul_nonneg (by linarith) hz0.le + linarith + linarith diff --git a/LeanPool/AiyagariEquilibrium/Analysis/DifferentiableSandwich.lean b/LeanPool/AiyagariEquilibrium/Analysis/DifferentiableSandwich.lean new file mode 100644 index 000000000..a04bac445 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Analysis/DifferentiableSandwich.lean @@ -0,0 +1,133 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Analysis.Convex.Deriv + +/-! +# The Differentiable Sandwich Lemma + +A function squeezed between two functions that are differentiable at a point, and agreeing +with both at that point, is itself differentiable there, with the same derivative. + +This is Lemma 1 of Clausen and Strub (2020), "Reverse Calculus and Nested Optimization", +*Journal of Economic Theory* 187, 105019, and it is the foundation of their approach to +envelope theorems. Their point is that it needs neither concavity nor interiority, so it +applies where Benveniste–Scheinkman does not — in particular where a constraint binds, which +in a Bewley economy is exactly the states that matter, since the borrowing constraint is where +the whole ergodic argument lives. + +The proof is two moves. The gap `U - L` is nonnegative near the point and vanishes at it, so +it has a local minimum there and its derivative is zero, which forces the two derivatives to +agree. Then the difference quotients of `F` are squeezed between those of `L` and `U`. + +Mathlib has the local-minimum step (`IsLocalMin.hasDerivAt_eq_zero`) but not this lemma. +-/ + +@[expose] public section + +open Filter Topology Asymptotics + +/-- **Differentiable Sandwich Lemma** (Clausen–Strub 2020, Lemma 1). If `F` is squeezed +between `L` and `U` near `c`, all three agree at `c`, and `L` and `U` are differentiable at +`c`, then the two derivatives coincide and `F` is differentiable at `c` with that derivative. -/ +theorem hasDerivAt_of_sandwich {F L U : ℝ → ℝ} {c l u : ℝ} {N : Set ℝ} (hN : N ∈ 𝓝 c) + (hLF : ∀ x ∈ N, L x ≤ F x) (hFU : ∀ x ∈ N, F x ≤ U x) + (hLc : L c = F c) (hUc : U c = F c) + (hL : HasDerivAt L l c) (hU : HasDerivAt U u c) : + l = u ∧ HasDerivAt F l c := by + -- the gap has a local minimum at `c`, so the derivatives agree + have hd : HasDerivAt (fun x => U x - L x) (u - l) c := hU.sub hL + have hmin : IsLocalMin (fun x => U x - L x) c := by + filter_upwards [hN] with x hx + have h1 := hLF x hx + have h2 := hFU x hx + simp only [hLc, hUc] + linarith + have hul : u - l = 0 := hmin.hasDerivAt_eq_zero hd + have hlu : l = u := by linarith + refine ⟨hlu, ?_⟩ + -- and the difference quotients of `F` are squeezed + have hU' : HasDerivAt U l c := by rwa [hlu] + rw [hasDerivAt_iff_isLittleO, isLittleO_iff] + intro ε hε + have hA := isLittleO_iff.mp (hasDerivAt_iff_isLittleO.mp hL) hε + have hB := isLittleO_iff.mp (hasDerivAt_iff_isLittleO.mp hU') hε + filter_upwards [hA, hB, hN] with y hya hyb hyn + rw [Real.norm_eq_abs] at hya hyb ⊢ + have h1 := hLF y hyn + have h2 := hFU y hyn + rw [abs_le] at hya hyb ⊢ + simp only [smul_eq_mul] at hya hyb ⊢ + rw [hLc] at hya + rw [hUc] at hyb + constructor <;> linarith [hya.1, hya.2, hyb.1, hyb.2] + +/-! ### The supporting line + +The upper half of the sandwich, for a concave function of one real variable. Mathlib has no +subgradient or supporting-hyperplane theory for convex or concave FUNCTIONS, but in one +dimension the slope API suffices: `ConcaveOn.slope_anti` says secant slopes through a point +are antitone, so every slope to the left is at least every slope to the right, and the +infimum of the left slopes separates them. + +Clausen and Strub note that concavity is needed only for this half — the lower bound in the +sandwich need not be concave, which is where they improve on Rockafellar's antecedent. For a +value function that matters: concavity is available here, and the lazy agent's lower bound +would not have to be concave in the state. +-/ + +/-- **A concave function has a supporting line at any point with neighbours on both sides.** +The slope is the infimum of the secant slopes to the left. -/ +theorem ConcaveOn.exists_supportingLine {f : ℝ → ℝ} {s : Set ℝ} (hf : ConcaveOn ℝ s f) + {x : ℝ} (hx : x ∈ s) (hlt : ∃ y ∈ s, y < x) (hgt : ∃ y ∈ s, x < y) : + ∃ m : ℝ, ∀ y ∈ s, f y ≤ f x + m * (y - x) := by + classical + have hanti := hf.slope_anti hx + obtain ⟨y₁, hy₁s, hy₁⟩ := hlt + obtain ⟨y₂, hy₂s, hy₂⟩ := hgt + -- every slope to the left dominates every slope to the right + have hcross : ∀ z ∈ s, z < x → ∀ y ∈ s, x < y → slope f x y ≤ slope f x z := by + intro z hzs hz y hys hy + exact hanti ⟨hzs, by simp [ne_of_lt hz]⟩ ⟨hys, by simp [ne_of_gt hy]⟩ (le_of_lt (hz.trans hy)) + set Lset : Set ℝ := slope f x '' {y ∈ s | y < x} with hLdef + have hne : Lset.Nonempty := ⟨slope f x y₁, ⟨y₁, ⟨hy₁s, hy₁⟩, rfl⟩⟩ + have hbdd : BddBelow Lset := by + refine ⟨slope f x y₂, ?_⟩ + rintro _ ⟨z, ⟨hzs, hz⟩, rfl⟩ + exact hcross z hzs hz y₂ hy₂s hy₂ + refine ⟨sInf Lset, fun y hy => ?_⟩ + rcases lt_trichotomy y x with h | h | h + · -- to the left: the slope is at least the infimum + have hmem : slope f x y ∈ Lset := ⟨y, ⟨hy, h⟩, rfl⟩ + have hle : sInf Lset ≤ slope f x y := csInf_le hbdd hmem + rw [slope_def_field, le_div_iff_of_neg (by linarith)] at hle + linarith + · subst h; simp + · -- to the right: the slope is a lower bound for the left slopes, hence below the infimum + have hle : slope f x y ≤ sInf Lset := by + refine le_csInf hne ?_ + rintro _ ⟨z, ⟨hzs, hz⟩, rfl⟩ + exact hcross z hzs hz y hy h + rw [slope_def_field, div_le_iff₀ (by linarith)] at hle + linarith + +/-- **A concave function with a differentiable lower bound touching at a point is +differentiable there**, with the lower bound's derivative. + +This is the form the envelope theorem uses: concavity supplies the upper half of the sandwich +automatically, so only the lower bound has to be produced, and Clausen and Strub produce it +with the lazy agent — freeze the optimal choice and vary the state, which is differentiable +because the state then enters only through the reward. -/ +theorem ConcaveOn.hasDerivAt_of_lowerBound {f L : ℝ → ℝ} {s : Set ℝ} {x l : ℝ} + (hf : ConcaveOn ℝ s f) (hx : x ∈ s) (hs : s ∈ 𝓝 x) + (hlt : ∃ y ∈ s, y < x) (hgt : ∃ y ∈ s, x < y) + (hLf : ∀ y ∈ s, L y ≤ f y) (hLx : L x = f x) (hL : HasDerivAt L l x) : + HasDerivAt f l x := by + obtain ⟨m, hm⟩ := hf.exists_supportingLine hx hlt hgt + have hU : HasDerivAt (fun y => f x + m * (y - x)) m x := by + simpa using (((hasDerivAt_id x).sub_const x).const_mul m).const_add (f x) + exact (hasDerivAt_of_sandwich hs hLf hm hLx (by simp) hL hU).2 diff --git a/LeanPool/AiyagariEquilibrium/Analysis/HARA.lean b/LeanPool/AiyagariEquilibrium/Analysis/HARA.lean new file mode 100644 index 000000000..68f436d83 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Analysis/HARA.lean @@ -0,0 +1,150 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Analysis.RelativeRiskAversion +public import LeanPool.AiyagariEquilibrium.Models.CRRA + +/-! +# HARA utility + +Hyperbolic absolute risk aversion: absolute risk tolerance `-u' / u''` is affine in consumption. +[Toda's November 2020 draft](https://arxiv.org/pdf/2009.13564v2) studies concavity uniformly +over arbitrary return and income laws, under its regularity hypotheses with borrowing permitted. +That necessity result does not exclude concavity outside HARA in a particular capped model. + +## The definition + +Stating "risk tolerance is affine" needs second derivatives. Since risk tolerance `a + c` scaled +by `1/γ` integrates to a shifted CRRA, the family can instead be given by its members, which is +derivative-free and is what every proof below actually uses: + + `u c = k · (a + c) ^ (1 - γ) / (1 - γ) + l`, and the `γ = 1` logarithm, + +with `k > 0`. This is the `b ≠ 0` branch of HARA -- the one that contains CRRA, and the one +macroeconomics uses. The quadratic branch (`b < 0`) is excluded because it satiates, and that +exclusion is now a theorem rather than a remark: `Models.Quadratic.no_incomeFluctuation_quadUtility` +shows no economy in this development can have quadratic utility, for any bliss point, since the +structure asks for monotone utility on the whole of `dom` and a quadratic falls beyond its bliss +point. `Models.Quadratic` also records what the branch would give if it could be hosted — Hall's +certainty equivalence, the aggregator being the plain weighted mean. + +CARA (`b = 0`) is NOT excluded, and an earlier version of this docstring was wrong about why it +might be: `u c = -exp (-α c) / α` is bounded above by `0` and equals `-1/α` at zero consumption, +so a capped model admits it -- `Models.IncomeFluctuationCARA.caraWitness` is one. What CARA does +not have is Light's condition, since relative risk aversion `α c` is unbounded +(`not_monotoneOn_mul_deriv_caraUtility`). It is covered separately because its Carroll--Kimball +step needs a different aggregator, the soft minimum of `Analysis.SoftMin` rather than a power +mean; see `Models.IncomeFluctuationCARA`. + +The shift `a` is the subsistence level of Stone and Geary. `a = 0` is CRRA exactly. + +## The result that matters + +`relativeRiskAversionLeOne_haraUtility`: HARA with a NONNEGATIVE shift and `γ ≤ 1` satisfies +Light's condition. Relative risk aversion of the shifted family is `γ c / (a + c)`, which for +`a ≥ 0` is at most `γ`; a negative shift (genuine subsistence) pushes it above `γ` and can break +the condition however small `γ` is, so the sign of the shift is not a technicality. + +The proof is derivative-free in the sense that matters: it factors + + `c · u' c = c ^ (1 - γ) · (c / (a + c)) ^ γ` + +into two nonnegative nondecreasing factors, which is exactly `u' c · c` nondecreasing, which is +relative risk aversion at most one. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +/-- Shifted CRRA: the `b ≠ 0` branch of HARA, with subsistence level `a`. -/ +noncomputable def haraUtility (γ a c : ℝ) : ℝ := crraUtility γ (a + c) + +@[simp] theorem haraUtility_zero_shift (γ : ℝ) : haraUtility γ 0 = crraUtility γ := by + funext c; simp [haraUtility] + +/-- **HARA**, as a class of functions: a positive affine transformation of a shifted CRRA. -/ +def IsHARA (u : ℝ → ℝ) : Prop := + ∃ γ a k l : ℝ, 0 < γ ∧ 0 < k ∧ ∀ c, u c = k * haraUtility γ a c + l + +theorem isHARA_haraUtility {γ : ℝ} (hγ : 0 < γ) (a : ℝ) : IsHARA (haraUtility γ a) := + ⟨γ, a, 1, 0, hγ, one_pos, fun c => by ring⟩ + +/-- **CRRA is HARA**, with no shift. -/ +theorem isHARA_crraUtility {γ : ℝ} (hγ : 0 < γ) : IsHARA (crraUtility γ) := by + simpa using isHARA_haraUtility hγ 0 + +/-- **Log utility is HARA**, as the `γ = 1` member. -/ +theorem isHARA_log : IsHARA Real.log := by + refine ⟨1, 0, 1, 0, one_pos, one_pos, fun c => ?_⟩ + simp [haraUtility] + +theorem hasDerivAt_haraUtility (γ : ℝ) {a c : ℝ} (hac : 0 < a + c) : + HasDerivAt (haraUtility γ a) ((a + c) ^ (-γ)) c := by + have h := (hasDerivAt_crraUtility γ hac).comp c ((hasDerivAt_id c).const_add a) + rw [mul_one] at h + exact h + +/-! ### Light's condition -/ + +/-- The factorisation used for Light's condition: when the shift is nonnegative and +`0 < γ ≤ 1`, both factors are nonnegative and nondecreasing on positive consumption. -/ +theorem mul_rpow_neg_eq {γ a c : ℝ} (hc : 0 < c) (hac : 0 < a + c) : + c * (a + c) ^ (-γ) = c ^ (1 - γ) * (c / (a + c)) ^ γ := by + rw [Real.div_rpow hc.le hac.le, Real.rpow_neg hac.le] + rw [show c ^ (1 - γ) * (c ^ γ / (a + c) ^ γ) + = (c ^ (1 - γ) * c ^ γ) / (a + c) ^ γ from by ring] + rw [← Real.rpow_add hc, show (1 : ℝ) - γ + γ = 1 from by ring, Real.rpow_one] + field_simp + +/-- **HARA with a nonnegative shift and `γ ≤ 1` satisfies Light's condition.** -/ +theorem relativeRiskAversionLeOne_haraUtility {γ a : ℝ} (hγ0 : 0 < γ) (hγ1 : γ ≤ 1) + (ha : 0 ≤ a) : RelativeRiskAversionLeOne (haraUtility γ a) := by + refine relativeRiskAversionLeOne_of_monotoneOn + (du := fun c => (a + c) ^ (-γ)) + (fun c hc => hasDerivAt_haraUtility γ (by have := mem_Ioi.mp hc; linarith)) ?_ + intro c₁ h₁ c₂ h₂ h₁₂ + have hc₁ : (0 : ℝ) < c₁ := h₁ + have hc₂ : (0 : ℝ) < c₂ := h₂ + have hac₁ : (0 : ℝ) < a + c₁ := by linarith + have hac₂ : (0 : ℝ) < a + c₂ := by linarith + change c₁ * (a + c₁) ^ (-γ) ≤ c₂ * (a + c₂) ^ (-γ) + rw [mul_rpow_neg_eq hc₁ hac₁, mul_rpow_neg_eq hc₂ hac₂] + -- the first factor rises because `1 - γ ≥ 0` + have hfst : c₁ ^ (1 - γ) ≤ c₂ ^ (1 - γ) := Real.rpow_le_rpow hc₁.le h₁₂ (by linarith) + -- the second because `c / (a + c)` rises when the shift is nonnegative + have hratio : c₁ / (a + c₁) ≤ c₂ / (a + c₂) := by + rw [div_le_div_iff₀ hac₁ hac₂] + nlinarith [ha, hc₁, h₁₂] + have hsnd : (c₁ / (a + c₁)) ^ γ ≤ (c₂ / (a + c₂)) ^ γ := + Real.rpow_le_rpow (by positivity) hratio hγ0.le + exact mul_le_mul hfst hsnd (by positivity) (by positivity) + +/-- The same for the whole class, since a positive affine transformation preserves convexity in +log consumption. -/ +theorem IsHARA.relativeRiskAversionLeOne {u : ℝ → ℝ} {γ a k l : ℝ} (hγ0 : 0 < γ) (hγ1 : γ ≤ 1) + (ha : 0 ≤ a) (hk : 0 < k) (hu : ∀ c, u c = k * haraUtility γ a c + l) : + RelativeRiskAversionLeOne u := by + have hbase := relativeRiskAversionLeOne_haraUtility hγ0 hγ1 ha + have hfun : (fun t => u (Real.exp t)) + = fun t => k * haraUtility γ a (Real.exp t) + l := by + funext t; exact hu _ + simp only [RelativeRiskAversionLeOne, hfun] + exact (hbase.smul hk.le).add_const l + +/-- **A negative shift breaks it**, however small `γ` is: relative risk aversion `γ c / (a + c)` +exceeds one as consumption approaches the subsistence level from above. Recorded to show that +`0 ≤ a` in the theorem above is doing work. -/ +theorem haraUtility_rra_gt_one_of_neg_shift {γ a c : ℝ} (hac : 0 < a + c) + (hnear : (a + c) < γ * c) : + 1 < γ * c / (a + c) := by + rw [lt_div_iff₀ hac] + linarith + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Analysis/PowerMean.lean b/LeanPool/AiyagariEquilibrium/Analysis/PowerMean.lean new file mode 100644 index 000000000..4144ea780 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Analysis/PowerMean.lean @@ -0,0 +1,322 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Algebra.BigOperators.Field +public import Mathlib.Analysis.MeanInequalitiesPow + +/-! +# Weighted power means with a negative exponent + +The analytic engine of Carroll and Kimball (1996). For CRRA utility `u' c = c ^ (-γ)` the +first-order condition of a consumption–saving problem reads + + `c = (β R) ^ (-1/γ) · (∑ π z' · c' z' ^ (-γ)) ^ (-1/γ)`, + +so the consumption chosen against a given stock of end-of-period assets is a positive multiple +of the weighted power mean, with exponent `-γ`, of next period's consumptions. Carroll and +Kimball's induction therefore needs exactly one fact: **a power mean with a negative exponent +is concave and nondecreasing**, so it carries concavity of next period's consumption functions +to concavity of this period's. + +## The proof + +`powerMean w p` is positively homogeneous of degree one, so concavity is the statement that the +set `{x > 0 : powerMean w p x ≥ 1}` is convex. With `p < 0` that set is `{x > 0 : ∑ w x ^ p ≤ 1}` +— a sublevel set of `∑ w x ^ p`, which is convex because `t ↦ t ^ p` is. So the whole thing comes +down to convexity of a single real function of one variable, and the scaling is bookkeeping. + +`convexOn_rpow_of_neg` supplies that one function: Mathlib has `x ^ p` convex for `1 ≤ p` and +concave for `0 < p < 1`, but not the negative case, and it is proved here from concavity of +`log` and convexity of `exp`. + +Kimball's risk-tolerance aggregation theorem is the differential form of the same fact. This is +the integrated form, and it needs no derivatives. +-/ + +@[expose] public section + +open Set Finset + +namespace LeanEconomics + +/-! ### `x ^ p` is convex for a negative exponent -/ + +/-- **A negative power is convex on the positive half-line.** `x ^ p = exp (p log x)`: `log` is +concave, multiplying by `p < 0` flips that to convex, and `exp` is convex and increasing. -/ +theorem convexOn_rpow_of_neg {p : ℝ} (hp : p < 0) : + ConvexOn ℝ (Ioi (0 : ℝ)) fun x : ℝ => x ^ p := by + refine ⟨convex_Ioi 0, fun x hx y hy a b ha hb hab => ?_⟩ + have hx0 : (0 : ℝ) < x := hx + have hy0 : (0 : ℝ) < y := hy + have hm : (0 : ℝ) < a * x + b * y := by + rcases eq_or_lt_of_le ha with h | h + · have hb1 : b = 1 := by linarith + rw [← h, hb1]; simpa using hy0 + · nlinarith + -- concavity of `log` + have hlog : a * Real.log x + b * Real.log y ≤ Real.log (a * x + b * y) := by + have := strictConcaveOn_log_Ioi.concaveOn.2 hx hy ha hb hab + simpa using this + -- multiplying by `p < 0` reverses it + have hstep : p * Real.log (a * x + b * y) ≤ a * (p * Real.log x) + b * (p * Real.log y) := by + nlinarith + calc (a * x + b * y) ^ p + = Real.exp (p * Real.log (a * x + b * y)) := by + rw [Real.rpow_def_of_pos hm]; ring_nf + _ ≤ Real.exp (a * (p * Real.log x) + b * (p * Real.log y)) := Real.exp_le_exp.mpr hstep + _ ≤ a * Real.exp (p * Real.log x) + b * Real.exp (p * Real.log y) := by + simpa using convexOn_exp.2 (mem_univ (p * Real.log x)) (mem_univ (p * Real.log y)) ha hb hab + _ = a • x ^ p + b • y ^ p := by + rw [Real.rpow_def_of_pos hx0, Real.rpow_def_of_pos hy0] + simp only [smul_eq_mul] + ring_nf + +/-! ### The power mean -/ + +variable {ι : Type*} [Fintype ι] + +/-- A convex combination of two positive numbers is positive. -/ +theorem pos_of_convex_comb {u v θ φ : ℝ} (hu : 0 < u) (hv : 0 < v) (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) + (hθφ : θ + φ = 1) : 0 < θ * u + φ * v := by + rcases eq_or_lt_of_le hθ with h | h + · have hφ1 : φ = 1 := by linarith + rw [← h, hφ1]; simpa using hv + · nlinarith + +/-- The weighted power mean of `x` with exponent `p`. -/ +noncomputable def powerMean (w : ι → ℝ) (p : ℝ) (x : ι → ℝ) : ℝ := + (∑ i, w i * x i ^ p) ^ (1 / p) + +variable {w : ι → ℝ} {p : ℝ} {x y : ι → ℝ} + +theorem sum_rpow_pos (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) (hx : ∀ i, 0 < x i) : + 0 < ∑ i, w i * x i ^ p := by + obtain ⟨i₀, -, hi₀⟩ : ∃ i ∈ Finset.univ, 0 < w i := by + by_contra h + simp only [not_exists, not_and, not_lt] at h + have hzero : ∑ i, w i = 0 := + Finset.sum_eq_zero fun i hi => le_antisymm (h i hi) (hw i) + rw [hw1] at hzero + norm_num at hzero + refine Finset.sum_pos' (fun i _ => mul_nonneg (hw i) (Real.rpow_nonneg (hx i).le _)) + ⟨i₀, Finset.mem_univ _, mul_pos hi₀ (Real.rpow_pos_of_pos (hx i₀) _)⟩ + +theorem powerMean_pos (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) (hx : ∀ i, 0 < x i) : + 0 < powerMean w p x := + Real.rpow_pos_of_pos (sum_rpow_pos hw hw1 hx) _ + +/-- The mean raised back to the exponent recovers the weighted sum. -/ +theorem powerMean_rpow (hp : p ≠ 0) (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) + (hx : ∀ i, 0 < x i) : powerMean w p x ^ p = ∑ i, w i * x i ^ p := by + have hS : 0 < ∑ i, w i * x i ^ p := sum_rpow_pos hw hw1 hx + simp only [powerMean] + rw [← Real.rpow_mul hS.le, one_div_mul_cancel hp, Real.rpow_one] + +/-- **The power mean is positively homogeneous of degree one.** -/ +theorem powerMean_smul (hp : p ≠ 0) {c : ℝ} (hc : 0 < c) (hw : ∀ i, 0 ≤ w i) + (hw1 : ∑ i, w i = 1) (hx : ∀ i, 0 < x i) : + powerMean w p (fun i => c * x i) = c * powerMean w p x := by + have hS : 0 < ∑ i, w i * x i ^ p := sum_rpow_pos hw hw1 hx + have hrw : ∑ i, w i * (c * x i) ^ p = c ^ p * ∑ i, w i * x i ^ p := by + rw [Finset.mul_sum] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Real.mul_rpow hc.le (hx i).le]; ring + simp only [powerMean, hrw] + rw [Real.mul_rpow (Real.rpow_nonneg hc.le _) hS.le, ← Real.rpow_mul hc.le, + mul_one_div_cancel hp, Real.rpow_one] + +/-- **Normalising by the mean makes the weighted sum exactly one.** This is the statement that +the level set `{powerMean = 1}` is where the scaling argument lands. -/ +theorem sum_rpow_div_powerMean (hp : p ≠ 0) (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) + (hx : ∀ i, 0 < x i) : ∑ i, w i * (x i / powerMean w p x) ^ p = 1 := by + have hS : 0 < ∑ i, w i * x i ^ p := sum_rpow_pos hw hw1 hx + have hM : 0 < powerMean w p x := powerMean_pos hw hw1 hx + have hrw : ∀ i, w i * (x i / powerMean w p x) ^ p + = (powerMean w p x ^ p)⁻¹ * (w i * x i ^ p) := by + intro i + rw [Real.div_rpow (hx i).le hM.le]; ring + rw [Finset.sum_congr rfl fun i _ => hrw i, ← Finset.mul_sum, powerMean_rpow hp hw hw1 hx] + exact inv_mul_cancel₀ hS.ne' + +/-- **A power mean with a negative exponent is nondecreasing.** -/ +theorem powerMean_mono (hp : p < 0) (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) + (hx : ∀ i, 0 < x i) (hy : ∀ i, 0 < y i) (hxy : ∀ i, x i ≤ y i) : + powerMean w p x ≤ powerMean w p y := by + have h1 : ∑ i, w i * y i ^ p ≤ ∑ i, w i * x i ^ p := + Finset.sum_le_sum fun i _ => mul_le_mul_of_nonneg_left + (Real.rpow_le_rpow_of_nonpos (hx i) (hxy i) hp.le) (hw i) + exact Real.rpow_le_rpow_of_nonpos (sum_rpow_pos hw hw1 hy) h1 + (le_of_lt (div_neg_of_pos_of_neg one_pos hp)) + +/-- **The gradient sum of a power mean is at most `M / min x`.** The quantity +`∑ w x^{-γ-1}`, which is what a common shift costs when propagated through a power mean, is bounded +termwise by `∑ w x^{-γ} / min x`. + +This is worth recording as a WARNING rather than a tool. Normalised, it says the gradient sum +`A = ∑ w (x/M)^{-γ-1}` is at most `M / min x` — and `M / min x` is exactly the multiplier that +bounding the expectation by its smallest term produces. Measured in the overlapping-generations +life cycle, `A` runs from `1.04` to `1.19` while `M / min x` runs from `4.7` to `5.9`, so this bound +discards the entire advantage of keeping the certainty equivalent. A usable bound on `A` has to see +the WEIGHTS — the far states carry little probability — and not merely the support. -/ +theorem sum_mul_rpow_pred_le {γ xmin : ℝ} (hw : ∀ i, 0 ≤ w i) (hx : ∀ i, 0 < x i) + (hmin : 0 < xmin) (hle : ∀ i, xmin ≤ x i) : + ∑ i, w i * x i ^ (-γ - 1) ≤ (∑ i, w i * x i ^ (-γ)) / xmin := by + rw [Finset.sum_div] + refine Finset.sum_le_sum fun i _ => ?_ + have hsplit : x i ^ (-γ - 1) = x i ^ (-γ) * (x i)⁻¹ := by + rw [show (-γ - 1 : ℝ) = (-γ) + (-1) from by ring, Real.rpow_add (hx i), Real.rpow_neg_one] + have hinv : (x i)⁻¹ ≤ xmin⁻¹ := by + simpa [one_div] using one_div_le_one_div_of_le hmin (hle i) + have hnn : 0 ≤ w i * x i ^ (-γ) := mul_nonneg (hw i) (Real.rpow_nonneg (hx i).le _) + calc w i * x i ^ (-γ - 1) = (w i * x i ^ (-γ)) * (x i)⁻¹ := by rw [hsplit]; ring + _ ≤ (w i * x i ^ (-γ)) * xmin⁻¹ := mul_le_mul_of_nonneg_left hinv hnn + _ = w i * x i ^ (-γ) / xmin := by rw [div_eq_mul_inv] + +/-- **The gradient sum is one plus the variance of the risk tilt.** Write `u i = (x i / M) ^ (-γ)` +for the tilt. By the definition of the power mean its weighted mean is exactly one, and then the +gradient sum `∑ w u ^ (1 + 1/γ)` is, at `γ = 1` where the exponent is exactly two, one plus the +weighted variance of `u`. + +This is the bound the minimum cannot give. `sum_mul_rpow_pred_le` sees only the support of `x` and +returns `M / min x`, measured at `4.7` to `5.9`; the variance sees the WEIGHTS, and is measured at +`0.041`. For `γ > 1` the exponent `1 + 1/γ` falls below two and Lyapunov's inequality gives +`∑ w u ^ (1+1/γ) ≤ (1 + Var)^{(γ+1)/(2γ)}`, which is an identity at `γ = 1` and loses accuracy +above it: measured `1.076` against a true `1.050` at `γ = 1.5`, and `1.26` against `1.08` at +`γ = 3`, against an allowance of about `1.06` throughout. So this route closes at logarithmic +utility and not above (`WriteUps/WriteUpOLG/numerics/lyap.m`). -/ +theorem sum_mul_sq_eq_one_add_variance {w u : ι → ℝ} (hw1 : ∑ i, w i = 1) + (hu : ∑ i, w i * u i = 1) : + ∑ i, w i * u i ^ 2 = 1 + ∑ i, w i * (u i - 1) ^ 2 := by + have hexp : ∀ i, w i * (u i - 1) ^ 2 = w i * u i ^ 2 - 2 * (w i * u i) + w i := by + intro i; ring + rw [Finset.sum_congr rfl fun i _ => hexp i] + rw [Finset.sum_add_distrib, Finset.sum_sub_distrib, ← Finset.mul_sum, hu, hw1] + ring + +/-- **The sharp moment bound.** For weights summing to one, `u ≥ 0` with `∑ w u = 1` and +`1 < p < 2`, `∑ w u^p ≤ (∑ w u²)^(p-1)`. It is Jensen for the concave `x ↦ x^(p-1)` under the +tilted weights `w u`, which sum to one by hypothesis, and it is attained by the two-point law on +`{0, ∑ w u²}`. With `p = 1 + 1/γ` it bounds the gradient sum of the two-rate chain by +`(1 + Var)^(1/γ)`, against Lyapunov's `(1 + Var)^((γ+1)/(2γ))`. -/ +theorem sum_mul_rpow_le_rpow_sum_mul_sq {w u : ι → ℝ} {p : ℝ} (hp1 : 1 < p) (hp2 : p < 2) + (hw : ∀ i, 0 ≤ w i) (hu : ∀ i, 0 ≤ u i) (hmean : ∑ i, w i * u i = 1) : + ∑ i, w i * u i ^ p ≤ (∑ i, w i * u i ^ 2) ^ (p - 1) := by + have hp0 : p - 1 ≠ 0 := sub_ne_zero.2 (ne_of_gt hp1) + have hq : (1 : ℝ) ≤ 1 / (p - 1) := by + rw [le_div_iff₀ (by linarith)]; linarith + have h := Real.arith_mean_le_rpow_mean Finset.univ (fun i => w i * u i) + (fun i => u i ^ (p - 1)) (fun i _ => mul_nonneg (hw i) (hu i)) hmean + (fun i _ => Real.rpow_nonneg (hu i) _) hq + have hL : ∀ i, w i * u i * u i ^ (p - 1) = w i * u i ^ p := by + intro i + have e : u i ^ p = u i ^ (1 + (p - 1)) := by congr 1; ring + rw [e, Real.rpow_add' (hu i) (by linarith : (1 : ℝ) + (p - 1) ≠ 0), Real.rpow_one] + ring + have hR : ∀ i, w i * u i * (u i ^ (p - 1)) ^ (1 / (p - 1)) = w i * u i ^ 2 := by + intro i + rw [one_div, Real.rpow_rpow_inv (hu i) hp0] + ring + simp only [hL, hR, one_div_one_div] at h + exact h + +/-- **A power mean with a negative exponent is concave.** + +Homogeneity turns concavity into the convexity of `{x > 0 : ∑ w x ^ p ≤ 1}`, and that is a +sublevel set of a convex function. This is Kimball's risk-tolerance aggregation in integrated +form: no derivative appears anywhere. -/ +theorem powerMean_concave (hp : p < 0) (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) + (hx : ∀ i, 0 < x i) (hy : ∀ i, 0 < y i) + {θ φ : ℝ} (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) (hθφ : θ + φ = 1) : + θ * powerMean w p x + φ * powerMean w p y + ≤ powerMean w p (fun i => θ * x i + φ * y i) := by + have hp0 : p ≠ 0 := ne_of_lt hp + have hA : 0 < powerMean w p x := powerMean_pos hw hw1 hx + have hB : 0 < powerMean w p y := powerMean_pos hw hw1 hy + set A := powerMean w p x with hAdef + set B := powerMean w p y with hBdef + set d : ℝ := θ * A + φ * B with hddef + have hd : 0 < d := pos_of_convex_comb hA hB hθ hφ hθφ + have hz : ∀ i, 0 < θ * x i + φ * y i := fun i => pos_of_convex_comb (hx i) (hy i) hθ hφ hθφ + -- the weights that split the normalised combination + set μ : ℝ := θ * A / d with hμdef + set ν : ℝ := φ * B / d with hνdef + have hμ : 0 ≤ μ := by rw [hμdef]; positivity + have hν : 0 ≤ ν := by rw [hνdef]; positivity + have hμν : μ + ν = 1 := by + have hsum : μ + ν = (θ * A + φ * B) / d := by rw [hμdef, hνdef]; ring + rw [hsum, ← hddef, div_self hd.ne'] + -- convexity of `t ^ p`, one coordinate at a time + have hpt : ∀ i, ((θ * x i + φ * y i) / d) ^ p ≤ μ * (x i / A) ^ p + ν * (y i / B) ^ p := by + intro i + have hkey : (θ * x i + φ * y i) / d = μ * (x i / A) + ν * (y i / B) := by + rw [hμdef, hνdef]; field_simp + rw [hkey] + have := (convexOn_rpow_of_neg hp).2 (show x i / A ∈ Ioi (0 : ℝ) by + simpa using div_pos (hx i) hA) + (show y i / B ∈ Ioi (0 : ℝ) by simpa using div_pos (hy i) hB) hμ hν hμν + simpa using this + -- summing, the normalised combination sits inside the unit sublevel set + have hsum : ∑ i, w i * ((θ * x i + φ * y i) / d) ^ p ≤ 1 := by + calc ∑ i, w i * ((θ * x i + φ * y i) / d) ^ p + ≤ ∑ i, w i * (μ * (x i / A) ^ p + ν * (y i / B) ^ p) := + Finset.sum_le_sum fun i _ => mul_le_mul_of_nonneg_left (hpt i) (hw i) + _ = μ * (∑ i, w i * (x i / A) ^ p) + ν * ∑ i, w i * (y i / B) ^ p := by + rw [Finset.mul_sum, Finset.mul_sum, ← Finset.sum_add_distrib] + exact Finset.sum_congr rfl fun i _ => by ring + _ = 1 := by + rw [hAdef, hBdef, sum_rpow_div_powerMean hp0 hw hw1 hx, + sum_rpow_div_powerMean hp0 hw hw1 hy, mul_one, mul_one, hμν] + -- and the mean of a vector in that set is at least one + have hT : 0 < ∑ i, w i * ((θ * x i + φ * y i) / d) ^ p := + sum_rpow_pos hw hw1 fun i => div_pos (hz i) hd + have hge : (1 : ℝ) ≤ powerMean w p (fun i => (θ * x i + φ * y i) / d) := + Real.one_le_rpow_of_pos_of_le_one_of_nonpos hT hsum + (le_of_lt (div_neg_of_pos_of_neg one_pos hp)) + -- rescale + have hscale : powerMean w p (fun i => θ * x i + φ * y i) + = d * powerMean w p (fun i => (θ * x i + φ * y i) / d) := by + rw [← powerMean_smul hp0 hd hw hw1 fun i => div_pos (hz i) hd] + congr 1 + funext i + field_simp + rw [hscale] + calc d = d * 1 := (mul_one d).symm + _ ≤ d * powerMean w p (fun i => (θ * x i + φ * y i) / d) := + mul_le_mul_of_nonneg_left hge hd.le + +/-- **The Carroll–Kimball step.** A negative power mean of concave positive functions is +concave: concavity of next period's consumption functions carries to the consumption chosen +against a given stock of end-of-period assets. -/ +theorem concaveOn_powerMean_comp {D : Set ℝ} (hD : Convex ℝ D) (hp : p < 0) + (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) {f : ι → ℝ → ℝ} + (hpos : ∀ i, ∀ a ∈ D, 0 < f i a) (hconc : ∀ i, ConcaveOn ℝ D (f i)) : + ConcaveOn ℝ D (fun a => powerMean w p (fun i => f i a)) := by + refine ⟨hD, fun a ha b hb θ φ hθ hφ hθφ => ?_⟩ + have hm : θ • a + φ • b ∈ D := hD ha hb hθ hφ hθφ + have hstep : ∀ i, θ * f i a + φ * f i b ≤ f i (θ • a + φ • b) := by + intro i + have := (hconc i).2 ha hb hθ hφ hθφ + simpa using this + have hmid : ∀ i, 0 < θ * f i a + φ * f i b := fun i => + pos_of_convex_comb (hpos i a ha) (hpos i b hb) hθ hφ hθφ + calc θ • powerMean w p (fun i => f i a) + φ • powerMean w p (fun i => f i b) + = θ * powerMean w p (fun i => f i a) + φ * powerMean w p (fun i => f i b) := by + simp only [smul_eq_mul] + _ ≤ powerMean w p (fun i => θ * f i a + φ * f i b) := + powerMean_concave hp hw hw1 (fun i => hpos i a ha) (fun i => hpos i b hb) hθ hφ hθφ + _ ≤ powerMean w p (fun i => f i (θ • a + φ • b)) := + powerMean_mono hp hw hw1 hmid (fun i => hpos i _ hm) hstep + +/-- **And nondecreasing**, which the endogenous-gridpoint transfer also needs. -/ +theorem monotoneOn_powerMean_comp {D : Set ℝ} (hp : p < 0) + (hw : ∀ i, 0 ≤ w i) (hw1 : ∑ i, w i = 1) {f : ι → ℝ → ℝ} + (hpos : ∀ i, ∀ a ∈ D, 0 < f i a) (hmono : ∀ i, MonotoneOn (f i) D) : + MonotoneOn (fun a => powerMean w p (fun i => f i a)) D := fun a ha b hb hab => + powerMean_mono hp hw hw1 (fun i => hpos i a ha) (fun i => hpos i b hb) + fun i => hmono i ha hb hab + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Analysis/RelativeRiskAversion.lean b/LeanPool/AiyagariEquilibrium/Analysis/RelativeRiskAversion.lean new file mode 100644 index 000000000..af185d854 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Analysis/RelativeRiskAversion.lean @@ -0,0 +1,323 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Analysis.DecreasingIncrements +public import Mathlib.Analysis.Convex.Deriv +public import Mathlib.Analysis.SpecialFunctions.Pow.Deriv + +/-! +# Relative risk aversion at most one + +Numbered Light references use the October 31, 2018 author draft, printed page 13: +https://www.researchgate.net/publication/314238962 + +Light (2018) proves that individual saving rises with the interest rate — his Theorem 1, the +household half of the uniqueness argument — under the assumption that `u'(c) · c` is +non-decreasing. With `u' > 0` that is exactly relative risk aversion `-c u''/u' ≤ 1`, because + + `d/dc [c u'(c)] = u'(c) · (1 - RRA(c))`. + +Economically it is the condition under which the substitution effect of a higher interest rate +dominates the income effect. It is the boundary of the region where the model is known to have +a unique equilibrium: Açıkgöz (2018) exhibits multiplicity at `γ = 6.5`, and Walsh and Young +(2025) report never seeing multiplicity below about `γ = 1.49`. + +## Stating it without derivatives + +`u'(c) · c` is the derivative of `t ↦ u(eᵗ)`, so the assumption says precisely that **`u` is +convex as a function of log consumption**, and that is how it is defined here. The definition +needs no differentiability, and neither does its main consequence: increments of `u` across a +fixed PROPORTIONAL change in consumption grow as consumption grows, + + `u(α c₁) - u(c₁) ≤ u(α c₂) - u(c₂)` for `0 < c₁ ≤ c₂` and `α ≥ 1`, + +which is `sub_le_sub_of_scale` below. This is the multiplicative twin of +`ConcaveOn.sub_le_sub_of_shift`, and it is the form the comparative-statics argument uses, since +a change in the interest rate scales the return on saving rather than shifting it. + +Where derivatives are available the two formulations agree, and `relativeRiskAversionLeOne_iff` +proves that. The examples at the end pin the boundary down: CRRA satisfies the condition exactly +when `γ ≤ 1`, log utility sits on the boundary (convex in log consumption because it is AFFINE +there), and for `γ > 1` it fails — indeed `u ∘ exp` is then strictly concave, so the condition +fails as badly as it can. +-/ + +@[expose] public section + +open Set + +namespace LeanEconomics + +/-- **Relative risk aversion at most one**, stated without derivatives: `u` is convex as a +function of log consumption. Where `u` is differentiable with `u' > 0` this is equivalent to +`-c u''(c) / u'(c) ≤ 1`; see `relativeRiskAversionLeOne_iff`. -/ +def RelativeRiskAversionLeOne (u : ℝ → ℝ) : Prop := + ConvexOn ℝ univ fun t => u (Real.exp t) + +/-- **The derivative-free content of `u'(c) · c` increasing.** The gain from a fixed +PROPORTIONAL rise in consumption is larger at higher consumption. + +This is the multiplicative counterpart of `ConcaveOn.sub_le_sub_of_shift`, and it is the form +the comparative statics uses: a higher interest rate scales the payoff to saving. -/ +theorem RelativeRiskAversionLeOne.sub_le_sub_of_scale {u : ℝ → ℝ} + (hu : RelativeRiskAversionLeOne u) {c₁ c₂ α : ℝ} (h₁ : 0 < c₁) (h₁₂ : c₁ ≤ c₂) (hα : 1 ≤ α) : + u (α * c₁) - u c₁ ≤ u (α * c₂) - u c₂ := by + have h₂ : 0 < c₂ := lt_of_lt_of_le h₁ h₁₂ + have hα0 : 0 < α := lt_of_lt_of_le one_pos hα + have hconv : ConvexOn ℝ (univ : Set ℝ) fun t => u (Real.exp t) := hu + -- the concave shift lemma applied to the negation, in log consumption + have hcon : ConcaveOn ℝ (univ : Set ℝ) (-fun t => u (Real.exp t)) := hconv.neg + have hlog : Real.log c₁ ≤ Real.log c₂ := Real.log_le_log h₁ h₁₂ + have hΔ : 0 ≤ Real.log α := Real.log_nonneg hα + have key := hcon.sub_le_sub_of_shift (mem_univ (Real.log c₁)) + (mem_univ (Real.log c₂ + Real.log α)) hlog hΔ + have e1 : Real.exp (Real.log c₁ + Real.log α) = α * c₁ := by + rw [Real.exp_add, Real.exp_log h₁, Real.exp_log hα0]; ring + have e2 : Real.exp (Real.log c₂ + Real.log α) = α * c₂ := by + rw [Real.exp_add, Real.exp_log h₂, Real.exp_log hα0]; ring + simp only [Pi.neg_apply, e1, e2, Real.exp_log h₁, Real.exp_log h₂] at key + linarith + +/-! ### The equivalence with `u'(c) · c` increasing -/ + +/-- Where `u` is differentiable on `(0, ∞)`, `t ↦ u(eᵗ)` is differentiable with derivative +`eᵗ u'(eᵗ)` — which is `u'(c) · c` read at `c = eᵗ`. -/ +theorem hasDerivAt_comp_exp {u du : ℝ → ℝ} (hd : ∀ c ∈ Ioi (0 : ℝ), HasDerivAt u (du c) c) + (t : ℝ) : + HasDerivAt (fun s => u (Real.exp s)) (Real.exp t * du (Real.exp t)) t := by + have h := (hd _ (mem_Ioi.mpr (Real.exp_pos t))).comp t (Real.hasDerivAt_exp t) + simpa [Function.comp_def, mul_comm] using h + +/-- If `u'(c) · c` is non-decreasing then relative risk aversion is at most one. -/ +theorem relativeRiskAversionLeOne_of_monotoneOn {u du : ℝ → ℝ} + (hd : ∀ c ∈ Ioi (0 : ℝ), HasDerivAt u (du c) c) + (hm : MonotoneOn (fun c => c * du c) (Ioi 0)) : + RelativeRiskAversionLeOne u := by + refine Monotone.convexOn_univ_of_deriv + (fun t => (hasDerivAt_comp_exp hd t).differentiableAt) ?_ + intro s t hst + rw [(hasDerivAt_comp_exp hd s).deriv, (hasDerivAt_comp_exp hd t).deriv] + exact hm (mem_Ioi.mpr (Real.exp_pos s)) (mem_Ioi.mpr (Real.exp_pos t)) (Real.exp_le_exp.mpr hst) + +/-- Conversely, relative risk aversion at most one makes `u'(c) · c` non-decreasing. -/ +theorem RelativeRiskAversionLeOne.monotoneOn_mul_deriv {u du : ℝ → ℝ} + (hu : RelativeRiskAversionLeOne u) (hd : ∀ c ∈ Ioi (0 : ℝ), HasDerivAt u (du c) c) : + MonotoneOn (fun c => c * du c) (Ioi 0) := by + have hconv : ConvexOn ℝ (univ : Set ℝ) fun t => u (Real.exp t) := hu + intro a ha b hb hab + rcases eq_or_lt_of_le hab with rfl | hlt + · exact le_rfl + have ha0 : 0 < a := mem_Ioi.mp ha + have hb0 : 0 < b := mem_Ioi.mp hb + have hlog : Real.log a < Real.log b := Real.log_lt_log ha0 hlt + -- the derivative at the left end of a secant is below its slope, and at the right end above + have h1 := hconv.le_slope_of_hasDerivAt (mem_univ _) (mem_univ _) hlog + (hasDerivAt_comp_exp hd (Real.log a)) + have h2 := hconv.slope_le_of_hasDerivAt (mem_univ _) (mem_univ _) hlog + (hasDerivAt_comp_exp hd (Real.log b)) + rw [Real.exp_log ha0] at h1 + rw [Real.exp_log hb0] at h2 + exact h1.trans h2 + +/-- **Relative risk aversion at most one is exactly `u'(c) · c` non-decreasing.** -/ +theorem relativeRiskAversionLeOne_iff {u du : ℝ → ℝ} + (hd : ∀ c ∈ Ioi (0 : ℝ), HasDerivAt u (du c) c) : + RelativeRiskAversionLeOne u ↔ MonotoneOn (fun c => c * du c) (Ioi 0) := + ⟨fun h => h.monotoneOn_mul_deriv hd, relativeRiskAversionLeOne_of_monotoneOn hd⟩ + +/-! ### Where the boundary is + +CRRA utility in log consumption is `eᵏᵗ / k` with `k = 1 - γ`, so everything below turns on the +sign of `k`. -/ + +/-- CRRA utility written in log consumption. -/ +theorem crra_comp_exp (k : ℝ) : + (fun t => Real.exp t ^ k / k) = fun t => Real.exp (k * t) / k := by + funext t + rw [← Real.exp_mul, mul_comm] + +theorem hasDerivAt_expScaled {k : ℝ} (hk : k ≠ 0) (t : ℝ) : + HasDerivAt (fun s => Real.exp (k * s) / k) (Real.exp (k * t)) t := by + have h : HasDerivAt (fun s => Real.exp (k * s)) (Real.exp (k * t) * k) t := by + simpa using ((hasDerivAt_id t).const_mul k).exp + simpa [mul_div_assoc, div_self hk] using h.div_const k + +theorem deriv_expScaled {k : ℝ} (hk : k ≠ 0) : + deriv (fun s => Real.exp (k * s) / k) = fun t => Real.exp (k * t) := + funext fun t => (hasDerivAt_expScaled hk t).deriv + +/-- **CRRA with `γ < 1` has relative risk aversion at most one.** In log consumption the +utility is `e^{(1-γ)t} / (1-γ)`, whose derivative `e^{(1-γ)t}` rises because `1 - γ > 0`. -/ +theorem relativeRiskAversionLeOne_crra {γ : ℝ} (hγ : γ < 1) : + RelativeRiskAversionLeOne fun c => c ^ (1 - γ) / (1 - γ) := by + have hk : 0 < 1 - γ := by linarith + change ConvexOn ℝ univ fun t => Real.exp t ^ (1 - γ) / (1 - γ) + rw [crra_comp_exp] + refine Monotone.convexOn_univ_of_deriv + (fun t => (hasDerivAt_expScaled hk.ne' t).differentiableAt) ?_ + rw [deriv_expScaled hk.ne'] + exact fun s t hst => Real.exp_le_exp.mpr (by nlinarith) + +/-- **Log utility sits exactly on the boundary**: in log consumption it is the identity, which +is affine, hence convex — but only barely, since it is concave too. Log utility is `γ = 1`. -/ +theorem relativeRiskAversionLeOne_log : RelativeRiskAversionLeOne Real.log := by + have h : (fun t => Real.log (Real.exp t)) = _root_.id := by funext t; simp + change ConvexOn ℝ univ fun t => Real.log (Real.exp t) + rw [h] + exact convexOn_id convex_univ + +/-- **CRRA with `γ > 1` fails, and fails strictly.** In log consumption the utility is +`e^{(1-γ)t} / (1-γ)` with a negative exponent and a negative divisor, whose derivative +`e^{(1-γ)t}` strictly falls, so it is strictly CONCAVE in log consumption. + +This is the reason the uniqueness literature stops at `γ ≤ 1`: beyond it the household's +response to the interest rate is not signed by this argument, and Açıkgöz (2018) and Walsh and +Young (2025) find calibrations where capital supply genuinely bends back. -/ +theorem strictConcaveOn_crra_comp_exp {γ : ℝ} (hγ : 1 < γ) : + StrictConcaveOn ℝ univ fun t => (Real.exp t : ℝ) ^ (1 - γ) / (1 - γ) := by + have hk : 1 - γ < 0 := by linarith + rw [crra_comp_exp] + refine StrictAnti.strictConcaveOn_univ_of_deriv + (Continuous.div_const (by fun_prop) _) ?_ + rw [deriv_expScaled hk.ne] + exact fun s t hst => Real.exp_lt_exp.mpr (by nlinarith) + +theorem not_relativeRiskAversionLeOne_crra {γ : ℝ} (hγ : 1 < γ) : + ¬ RelativeRiskAversionLeOne fun c => c ^ (1 - γ) / (1 - γ) := by + intro h + have hconv : ConvexOn ℝ (univ : Set ℝ) fun t => Real.exp t ^ (1 - γ) / (1 - γ) := h + have hconc := strictConcaveOn_crra_comp_exp hγ + -- the midpoint of `0` and `1` separates them + have h1 := hconv.2 (mem_univ (0 : ℝ)) (mem_univ (1 : ℝ)) (by norm_num : (0:ℝ) ≤ 1/2) + (by norm_num : (0:ℝ) ≤ 1/2) (by norm_num) + have h2 := hconc.2 (mem_univ (0 : ℝ)) (mem_univ (1 : ℝ)) (by norm_num : (0:ℝ) ≠ 1) + (by norm_num : (0:ℝ) < 1/2) (by norm_num : (0:ℝ) < 1/2) (by norm_num) + simp only [smul_eq_mul] at h1 h2 + linarith + +/-! ### The step of Light's Theorem 1 that all of this serves + +Light's Theorem 1 compares a household facing gross return `α₁` with one facing `α₂ ≥ α₁`, and +its pivotal inequality is that the marginal value of wealth, `α · u'(c)`, is larger at the +higher return. Written out, `α u'(c) = (α / c) · (u'(c) · c)`, and the two factors are exactly +the two conditions: `α / c(α x)` rises with `α` because the consumption function is CONCAVE +(Lemma 4, `ConcaveOn.div_le_div_of_scale`), and `u'(c) c` rises with `c` because relative risk +aversion is at most one. Neither factor alone signs the product; splitting it this way is the +whole trick. + +Note what the consumption function has to supply: concavity, positivity at zero assets, and +monotonicity. The first is Light's Lemma 5 and is the deep one — he cites Jensen (2017), and it +is a Carroll–Kimball result that needs more than concavity of `u` and of the value function. +-/ + +/-- **The pivotal inequality of Light (2018) Theorem 1.** For a concave, positive, increasing +consumption function `c` and marginal utility `du` with relative risk aversion at most one, the +marginal value of wealth `α · u'(c(α x))` is non-decreasing in the gross return `α`. -/ +theorem mul_marginal_le_of_scale {c du : ℝ → ℝ} {s : Set ℝ} + (hc : ConcaveOn ℝ s c) (h0 : (0 : ℝ) ∈ s) (hc0 : 0 < c 0) (hmono : MonotoneOn c s) + (hrra : MonotoneOn (fun y => y * du y) (Ioi 0)) (hdu : ∀ y ∈ Ioi (0 : ℝ), 0 ≤ du y) + {α₁ α₂ x : ℝ} (h1 : 0 < α₁) (h12 : α₁ ≤ α₂) (hx : 0 < x) + (hm1 : α₁ * x ∈ s) (hm2 : α₂ * x ∈ s) : + α₁ * du (c (α₁ * x)) ≤ α₂ * du (c (α₂ * x)) := by + have h2 : 0 < α₂ := lt_of_lt_of_le h1 h12 + have hp1 : 0 < c (α₁ * x) := lt_of_lt_of_le hc0 (hmono h0 hm1 (by positivity)) + have hp2 : 0 < c (α₂ * x) := lt_of_lt_of_le hc0 (hmono h0 hm2 (by positivity)) + have hcle : c (α₁ * x) ≤ c (α₂ * x) := + hmono hm1 hm2 (mul_le_mul_of_nonneg_right h12 hx.le) + -- the two factors, each signed by one of the two hypotheses + have hdiv := hc.div_le_div_of_scale h0 hc0 h1 h12 hm2 hp2 + have hprod := hrra (mem_Ioi.mpr hp1) (mem_Ioi.mpr hp2) hcle + have hnn : 0 ≤ c (α₁ * x) * du (c (α₁ * x)) := mul_nonneg hp1.le (hdu _ (mem_Ioi.mpr hp1)) + have hmul := mul_le_mul hdiv hprod hnn (div_pos h2 hp2).le + have e1 : α₁ / c (α₁ * x) * (c (α₁ * x) * du (c (α₁ * x))) = α₁ * du (c (α₁ * x)) := by + field_simp + have e2 : α₂ / c (α₂ * x) * (c (α₂ * x) * du (c (α₂ * x))) = α₂ * du (c (α₂ * x)) := by + field_simp + rwa [e1, e2] at hmul + +/-- The pivotal inequality on a bounded asset interval, with the asset rescaled by the +ratio of two positive gross returns. -/ +theorem mul_marginal_le_of_ratio {c du : ℝ → ℝ} {cap R₁ R₂ a : ℝ} + (hc : ConcaveOn ℝ (Icc 0 cap) c) (hc0 : 0 < c 0) + (hmono : MonotoneOn c (Icc 0 cap)) + (hrra : MonotoneOn (fun y => y * du y) (Ioi 0)) (hdu : ∀ y ∈ Ioi 0, 0 ≤ du y) + (hR₁ : 0 < R₁) (hR : R₁ ≤ R₂) (ha : a ∈ Ioo 0 cap) : + R₁ * du (c (R₁ / R₂ * a)) ≤ R₂ * du (c a) := by + have hR₂ : 0 < R₂ := lt_of_lt_of_le hR₁ hR + have hscaled : R₁ / R₂ * a ∈ Icc 0 cap := + ⟨mul_nonneg (div_pos hR₁ hR₂).le ha.1.le, + (mul_le_of_le_one_left ha.1.le ((div_le_one hR₂).mpr hR)).trans ha.2.le⟩ + have e₁ : R₁ * (a / R₂) = R₁ / R₂ * a := by ring + have e₂ : R₂ * (a / R₂) = a := mul_div_cancel₀ a hR₂.ne' + simpa only [e₁, e₂] using + mul_marginal_le_of_scale hc ⟨le_rfl, ha.1.le.trans ha.2.le⟩ hc0 hmono hrra hdu + hR₁ hR (div_pos ha.1 hR₂) (by rwa [e₁]) (by rw [e₂]; exact ⟨ha.1.le, ha.2.le⟩) + +/-! ### Beyond relative risk aversion one + +For `u'(c) = c^{-γ}` with `γ > 1`, `c · u'(c) = c^{1-γ}` FALLS, so the second factor above goes +the wrong way — but only by the factor `(c(α₂x)/c(α₁x))^{γ-1}`, and the first factor bounds that +ratio: concavity through a positive value at zero gives the chord bound +`c(α₂x)/c(α₁x) ≤ 1 + (1 - c(0)/c(α₁x))(α₂/α₁ - 1)`. So the pivotal inequality survives wherever + + `(1 - c(0)/c(α₁x)) · (ρ - 1) ≤ ρ^{1/γ} - 1`, `ρ = α₂/α₁`, + +which for returns close together says consumption at `α₁x` is at most about `γ/(γ-1)` times +consumption at zero assets. For `γ ≤ 1` the condition is automatic, since `ρ^{1/γ} ≥ ρ`; +for `γ > 1` it is a restriction on the RANGE of the consumption function, not on the utility. -/ + +/-- **The chord bound.** A concave function on a set containing `0` grows past `y₁` at most at +the average rate it had over `[0, y₁]`. -/ +theorem ConcaveOn.le_add_chord {c : ℝ → ℝ} {s : Set ℝ} (hc : ConcaveOn ℝ s c) (h0 : (0 : ℝ) ∈ s) + {y₁ y₂ : ℝ} (hy1 : 0 < y₁) (hy12 : y₁ < y₂) (hm2 : y₂ ∈ s) : + c y₂ ≤ c y₁ + (c y₁ - c 0) * ((y₂ - y₁) / y₁) := by + have h := hc.slope_anti_adjacent h0 hm2 hy1 hy12 + rw [sub_zero] at h + have hy21 : 0 < y₂ - y₁ := by linarith + rw [div_le_div_iff₀ hy21 hy1] at h + have e : (c y₁ - c 0) * ((y₂ - y₁) / y₁) = (c y₁ - c 0) * (y₂ - y₁) / y₁ := by ring + rw [e, ← sub_le_iff_le_add', le_div_iff₀ hy1] + exact h + +/-- **Light's pivotal inequality beyond relative risk aversion one**, for `u'(c) = c^{-γ}` and +any `γ > 0`: `α₁ · c(α₁x)^{-γ} ≤ α₂ · c(α₂x)^{-γ}` for `α₂ = ρ α₁ ≥ α₁`, provided +`(1 - c(0)/c(α₁x))(ρ - 1) ≤ ρ^{1/γ} - 1`. -/ +theorem mul_rpow_neg_le_of_scale {c : ℝ → ℝ} {s : Set ℝ} {γ : ℝ} (hγ : 0 < γ) + (hc : ConcaveOn ℝ s c) (h0 : (0 : ℝ) ∈ s) (hc0 : 0 < c 0) (hmono : MonotoneOn c s) + {α₁ ρ x : ℝ} (h1 : 0 < α₁) (hρ : 1 ≤ ρ) (hx : 0 < x) + (hm1 : α₁ * x ∈ s) (hm2 : ρ * α₁ * x ∈ s) + (hθ : (1 - c 0 / c (α₁ * x)) * (ρ - 1) ≤ ρ ^ (1 / γ) - 1) : + α₁ * (c (α₁ * x)) ^ (-γ) ≤ ρ * α₁ * (c (ρ * α₁ * x)) ^ (-γ) := by + have hρ0 : 0 < ρ := by linarith + have hp1 : 0 < c (α₁ * x) := lt_of_lt_of_le hc0 (hmono h0 hm1 (by positivity)) + have hp2 : 0 < c (ρ * α₁ * x) := lt_of_lt_of_le hc0 (hmono h0 hm2 (by positivity)) + -- the ratio of consumptions is at most `ρ^{1/γ}` + have hratio : c (ρ * α₁ * x) ≤ ρ ^ (1 / γ) * c (α₁ * x) := by + rcases eq_or_lt_of_le hρ with hρ1 | hρ1 + · subst hρ1 + simp + · have hlt : α₁ * x < ρ * α₁ * x := by + have hpos : 0 < (ρ - 1) * (α₁ * x) := mul_pos (by linarith) (mul_pos h1 hx) + linarith [hpos] + have hchord := ConcaveOn.le_add_chord hc h0 (by positivity : 0 < α₁ * x) hlt hm2 + have e : (ρ * α₁ * x - α₁ * x) / (α₁ * x) = ρ - 1 := by field_simp + rw [e] at hchord + have hθ' := mul_le_mul_of_nonneg_right hθ hp1.le + have e2 : (1 - c 0 / c (α₁ * x)) * (ρ - 1) * c (α₁ * x) + = (c (α₁ * x) - c 0) * (ρ - 1) := by field_simp + rw [e2] at hθ' + linarith + -- hence `c₂^γ ≤ ρ c₁^γ`, which is the claim + have hpow : (c (ρ * α₁ * x)) ^ γ ≤ ρ * (c (α₁ * x)) ^ γ := by + have := Real.rpow_le_rpow hp2.le hratio hγ.le + rwa [Real.mul_rpow (by positivity) hp1.le, ← Real.rpow_mul hρ0.le, + one_div_mul_cancel hγ.ne', Real.rpow_one] at this + rw [Real.rpow_neg hp1.le, Real.rpow_neg hp2.le, ← div_eq_mul_inv, ← div_eq_mul_inv, + div_le_div_iff₀ (Real.rpow_pos_of_pos hp1 _) (Real.rpow_pos_of_pos hp2 _)] + nlinarith [mul_le_mul_of_nonneg_left hpow h1.le] + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Distribution/Feller.lean b/LeanPool/AiyagariEquilibrium/Distribution/Feller.lean new file mode 100644 index 000000000..4cee5d2ea --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Distribution/Feller.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation +public import Mathlib.MeasureTheory.Measure.ProbabilityMeasure + +/-! +# The agent distribution: state space and Markov operator + +Step one towards a stationary agent distribution for the income fluctuation problem. + +## The compact state space + +The model is posed with state space `ℝ × Z`, but a distribution has to live somewhere +compact -- Prokhorov's theorem, which supplies the compactness of the space of probability +measures, needs a compact underlying space. The household never leaves +`[0, assetCap] × Z`, so that is where the distribution lives, and `State` is that region as +a space in its own right rather than as a subset of a larger one. + +Taking it as a subtype is forced. A *subset* of `ℝ × Z` would not be a compact space, and +the earlier concavity work could not use a subtype because it is not a module over `ℝ`. +Both choices are right in their own place: concavity needs the linear structure of the +ambient space, the distribution needs compactness of the region. + +## The Markov operator + +`markovOp h s` is the expected value of `h` at next period's state, given that the household +is at `s` today and follows its optimal policy. The content of the definition is that it +lands in the *bounded continuous* functions again: + +* continuity is the **Feller property**, and comes from continuity of the optimal policy, + which is what the whole concavity and uniqueness development was for; +* boundedness is immediate from `∑ prob = 1`. + +Feller is what will make the induced map on probability measures continuous, and continuity +of that map is what a fixed point argument needs. + +## The operator on measures + +`push μ` is where the distribution `μ` is a period later. It is assembled from +`Measure.withDensity` and `Measure.map` rather than from `Measure.bind`: Mathlib has +`lintegral_bind` but no Bochner `integral_bind`, whereas both of the former have their +Bochner lemmas, and it is the Bochner integral that the weak topology is stated with. + +`integral_push` is the duality `∫ h d(push μ) = ∫ (markovOp h) dμ`, and it is what makes +the whole thing work: weak convergence is tested against bounded continuous functions, so +weak continuity of `push` reduces immediately to `markovOp h` being one. +-/ + +@[expose] public section + +open scoped NNReal ENNReal +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The compact region the agent distribution lives on: assets in `[0, assetCap]` paired +with an income state. -/ +abbrev State (_P : IncomeFluctuation Z assetFloor assetCap) : Type _ := + ↥(Icc assetFloor assetCap) × Z + +instance : CompactSpace P.State := + haveI : CompactSpace ↥(Icc assetFloor assetCap) := isCompact_iff_compactSpace.mp isCompact_Icc + inferInstanceAs (CompactSpace (_ × _)) + +/-- The region, read back as a state of the original model. -/ +def incl (s : P.State) : ℝ × Z := ((s.1 : ℝ), s.2) + +theorem continuous_incl : Continuous P.incl := + (continuous_subtype_val.comp continuous_fst).prodMk continuous_snd + +theorem incl_mem (s : P.State) : (P.incl s).1 ∈ Icc assetFloor assetCap := s.1.2 + +/-- Next period's assets under the optimal policy, as a point of the region. -/ +noncomputable def nextAssets (s : P.State) : ↥(Icc assetFloor assetCap) := + ⟨P.policy (P.incl s), P.policy_mem_region _⟩ + +/-- Next period's state if the income shock `z'` occurs. -/ +noncomputable def nextState (s : P.State) (z' : Z) : P.State := (P.nextAssets s, z') + +/-- **The policy moves the household continuously within the region.** This is the Feller +input, and it is exactly what the concavity and uniqueness development bought. -/ +theorem continuous_nextAssets : Continuous P.nextAssets := + (P.continuousOn_policy.comp_continuous P.continuous_incl P.incl_mem).subtype_mk _ + +theorem continuous_nextState (z' : Z) : Continuous fun s => P.nextState s z' := + P.continuous_nextAssets.prodMk continuous_const + +/-- The probability of moving to income state `z'`, as a function of today's state. -/ +noncomputable def prob (s : P.State) (z' : Z) : ℝ := P.transitionMatrix s.2 z' + +theorem prob_nonneg (s : P.State) (z' : Z) : 0 ≤ P.prob s z' := P.transitionMatrix_nonneg _ _ + +theorem prob_sum (s : P.State) : ∑ z', P.prob s z' = 1 := P.transitionMatrix_sum _ + +theorem continuous_prob (z' : Z) : Continuous fun s => P.prob s z' := + (continuous_of_discreteTopology (f := fun z => P.transitionMatrix z z')).comp continuous_snd + +/-- The expected value of `h` next period, as a bare function. -/ +noncomputable def markovFn (h : P.State →ᵇ ℝ) (s : P.State) : ℝ := + ∑ z', P.prob s z' * h (P.nextState s z') + +theorem continuous_markovFn (h : P.State →ᵇ ℝ) : Continuous (P.markovFn h) := + continuous_finsetSum _ fun z' _ => + (P.continuous_prob z').mul (h.continuous.comp (P.continuous_nextState z')) + +theorem abs_markovFn_le (h : P.State →ᵇ ℝ) (s : P.State) : |P.markovFn h s| ≤ ‖h‖ := by + calc |P.markovFn h s| ≤ ∑ z', |P.prob s z' * h (P.nextState s z')| := + Finset.abs_sum_le_sum_abs _ _ + _ = ∑ z', P.prob s z' * |h (P.nextState s z')| := by + refine Finset.sum_congr rfl fun z' _ => ?_ + rw [abs_mul, abs_of_nonneg (P.prob_nonneg s z')] + _ ≤ ∑ z', P.prob s z' * ‖h‖ := by + refine Finset.sum_le_sum fun z' _ => mul_le_mul_of_nonneg_left ?_ (P.prob_nonneg s z') + simpa [Real.norm_eq_abs] using h.norm_coe_le_norm (P.nextState s z') + _ = ‖h‖ := by rw [← Finset.sum_mul, P.prob_sum, one_mul] + +/-- **The Markov operator.** `markovOp h` is the expected value of `h` next period. That it +is again bounded and *continuous* is the Feller property. -/ +noncomputable def markovOp (h : P.State →ᵇ ℝ) : P.State →ᵇ ℝ := + BoundedContinuousFunction.ofNormedAddCommGroup (P.markovFn h) (P.continuous_markovFn h) ‖h‖ + fun s => by simpa [Real.norm_eq_abs] using P.abs_markovFn_le h s + +@[simp] +theorem markovOp_apply (h : P.State →ᵇ ℝ) (s : P.State) : + P.markovOp h s = ∑ z', P.prob s z' * h (P.nextState s z') := rfl + +theorem norm_markovOp_le (h : P.State →ᵇ ℝ) : ‖P.markovOp h‖ ≤ ‖h‖ := + (norm_le (norm_nonneg h)).2 fun s => by + rw [Real.norm_eq_abs] + exact P.abs_markovFn_le h s + +/-- The operator is monotone: a larger payoff tomorrow is worth more today. -/ +theorem markovOp_mono {h k : P.State →ᵇ ℝ} (hk : ⇑h ≤ ⇑k) : ⇑(P.markovOp h) ≤ ⇑(P.markovOp k) := by + intro s + refine Finset.sum_le_sum fun z' _ => mul_le_mul_of_nonneg_left ?_ (P.prob_nonneg s z') + exact hk _ + +/-- The operator is unital: a constant payoff is worth its own value. This is `∑ prob = 1`, +and it is what will make the induced map send probability measures to probability +measures. -/ +theorem markovOp_const (c : ℝ) : P.markovOp (const P.State c) = const P.State c := by + ext s + simp only [markovOp_apply, const_apply, ← Finset.sum_mul, P.prob_sum, one_mul] + +/-! ### The operator on measures -/ + +open MeasureTheory + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- The reweighting density for shock `z'`, as an `ℝ≥0`-valued function. -/ +noncomputable def dens (z' : Z) (s : P.State) : ℝ≥0 := (P.prob s z').toNNReal + +theorem measurable_dens (z' : Z) : Measurable (P.dens z') := + (continuous_real_toNNReal.comp (P.continuous_prob z')).measurable + +omit [MeasurableSpace Z] [BorelSpace Z] in +@[simp] +theorem coe_dens (z' : Z) (s : P.State) : ((P.dens z' s : ℝ≥0) : ℝ) = P.prob s z' := + Real.coe_toNNReal _ (P.prob_nonneg s z') + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem sum_dens (s : P.State) : ∑ z', ((P.dens z' s : ℝ≥0) : ℝ≥0∞) = 1 := by + have hz : ∀ z' : Z, ((P.dens z' s : ℝ≥0) : ℝ≥0∞) = ENNReal.ofReal (P.prob s z') := + fun _ => rfl + rw [Finset.sum_congr rfl fun z' _ => hz z', + ← ENNReal.ofReal_sum_of_nonneg fun z' _ => P.prob_nonneg s z', P.prob_sum s, + ENNReal.ofReal_one] + +/-- **One period of the agent distribution's motion.** Each income state `z'` reweights the +distribution by the probability of moving there and pushes it along the policy. -/ +noncomputable def push (μ : Measure P.State) : Measure P.State := + Measure.sum fun z' => + (μ.withDensity fun s => (P.dens z' s : ℝ≥0∞)).map fun s => P.nextState s z' + +theorem measurable_nextState (z' : Z) : Measurable fun s => P.nextState s z' := + (P.continuous_nextState z').measurable + +/-- `push` preserves total mass, because the transition probabilities sum to one. -/ +instance isProbabilityMeasure_push (μ : Measure P.State) [IsProbabilityMeasure μ] : + IsProbabilityMeasure (P.push μ) := by + constructor + rw [push, Measure.sum_apply _ MeasurableSet.univ] + have hterm : ∀ z' : Z, + ((μ.withDensity fun s => (P.dens z' s : ℝ≥0∞)).map fun s => P.nextState s z') univ + = ∫⁻ s, (P.dens z' s : ℝ≥0∞) ∂μ := by + intro z' + rw [Measure.map_apply (P.measurable_nextState z') MeasurableSet.univ, + Set.preimage_univ, withDensity_apply _ MeasurableSet.univ, Measure.restrict_univ] + rw [tsum_fintype] + simp only [hterm] + rw [← lintegral_finsetSum _ fun z' _ => ((P.measurable_dens z').coe_nnreal_ennreal)] + simp only [P.sum_dens] + simp + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- Each transition probability is at most one. -/ +theorem prob_le_one (s : P.State) (z' : Z) : P.prob s z' ≤ 1 := by + rw [← P.prob_sum s] + exact Finset.single_le_sum (fun i _ => P.prob_nonneg s i) (Finset.mem_univ z') + +theorem integrable_term (μ : Measure P.State) [IsProbabilityMeasure μ] (h : P.State →ᵇ ℝ) + (z' : Z) : Integrable (fun s => P.prob s z' * h (P.nextState s z')) μ := by + refine (integrable_const ‖h‖).mono' ?_ ?_ + · exact ((P.continuous_prob z').mul + (h.continuous.comp (P.continuous_nextState z'))).measurable.aestronglyMeasurable + · filter_upwards with s + have h1 := P.prob_le_one s z' + have h2 : |h (P.nextState s z')| ≤ ‖h‖ := by + simpa [Real.norm_eq_abs] using h.norm_coe_le_norm (P.nextState s z') + rw [Real.norm_eq_abs, abs_mul, abs_of_nonneg (P.prob_nonneg s z')] + nlinarith [P.prob_nonneg s z', abs_nonneg (h (P.nextState s z'))] + +/-- **Duality.** The integral of `h` against tomorrow's distribution is the integral of the +expected value of `h` against today's. This is the bridge between the two operators, and it +is what turns the Feller property into weak continuity. -/ +theorem integral_push (μ : Measure P.State) [IsProbabilityMeasure μ] (h : P.State →ᵇ ℝ) : + ∫ s, h s ∂(P.push μ) = ∫ s, P.markovOp h s ∂μ := by + have hterm : ∀ z' : Z, + ∫ s, h s ∂((μ.withDensity fun s => (P.dens z' s : ℝ≥0∞)).map fun s => P.nextState s z') + = ∫ s, P.prob s z' * h (P.nextState s z') ∂μ := by + intro z' + rw [integral_map (P.measurable_nextState z').aemeasurable + h.continuous.measurable.aestronglyMeasurable, + integral_withDensity_eq_integral_smul (P.measurable_dens z')] + refine integral_congr_ae (Filter.Eventually.of_forall fun s => ?_) + calc (P.dens z' s : ℝ≥0) • h (P.nextState s z') + = ((P.dens z' s : ℝ≥0) : ℝ) • h (P.nextState s z') := NNReal.smul_def _ _ + _ = P.prob s z' * h (P.nextState s z') := by rw [smul_eq_mul, P.coe_dens z' s] + have hint : Integrable (fun s => h s) (P.push μ) := h.integrable _ + rw [push] + rw [integral_sum_measure hint, tsum_fintype] + simp only [hterm] + rw [← integral_finsetSum _ fun z' _ => P.integrable_term μ h z'] + rfl + +/-- **The agent distribution operator**, on probability measures. -/ +noncomputable def pushProb (μ : ProbabilityMeasure P.State) : ProbabilityMeasure P.State := + ⟨P.push (μ : Measure P.State), P.isProbabilityMeasure_push _⟩ + +@[simp] +theorem coe_pushProb (μ : ProbabilityMeasure P.State) : + ((P.pushProb μ : ProbabilityMeasure P.State) : Measure P.State) + = P.push (μ : Measure P.State) := rfl + +/-- **The distribution operator is weakly continuous.** This is the Feller property in the +form a fixed point argument needs, and it falls straight out of the duality: weak +convergence is tested against bounded continuous functions, and `markovOp h` is one. -/ +theorem continuous_pushProb : Continuous P.pushProb := by + rw [continuous_iff_continuousAt] + intro μ + rw [ContinuousAt, ProbabilityMeasure.tendsto_iff_forall_integral_tendsto] + intro h + have hdual : ∀ ν : ProbabilityMeasure P.State, + ∫ s, h s ∂((P.pushProb ν : ProbabilityMeasure P.State) : Measure P.State) + = ∫ s, P.markovOp h s ∂(ν : Measure P.State) := fun ν => P.integral_push _ h + simp only [hdual] + exact ProbabilityMeasure.tendsto_iff_forall_integral_tendsto.mp + (tendsto_id : Filter.Tendsto id (𝓝 μ) (𝓝 μ)) (P.markovOp h) + +/-- A **stationary agent distribution** is a fixed point of the operator. -/ +def IsStationary (μ : ProbabilityMeasure P.State) : Prop := P.pushProb μ = μ + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Distribution/Stationary.lean b/LeanPool/AiyagariEquilibrium/Distribution/Stationary.lean new file mode 100644 index 000000000..4175dba77 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Distribution/Stationary.lean @@ -0,0 +1,174 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Distribution.Feller +public import Mathlib.MeasureTheory.Measure.Prokhorov + +/-! +# Existence of a stationary agent distribution + +Krylov–Bogolyubov, for the income fluctuation problem: average the iterates of the +distribution operator, and any cluster point of those averages is stationary. + +The two ingredients are both already in place. The space of probability measures over the +compact state space is compact, by Prokhorov; and the operator is weakly continuous, by the +Feller property. What the averaging adds is that the *difference* between an average and +its image telescopes, so it vanishes in the limit even though the individual iterates need +not converge at all. +-/ + +@[expose] public section + +open scoped NNReal ENNReal +open Set Filter Topology BoundedContinuousFunction MeasureTheory + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The state space is nonempty: a household at the borrowing limit is in it. This has to be +given by hand, since `assetFloor ≤ assetCap` is data carried by `P` rather than an instance. -/ +instance instNonemptyState : Nonempty P.State := + ⟨⟨⟨assetFloor, ⟨le_refl assetFloor, P.assetFloor_le_assetCap⟩⟩, Classical.ofNonempty⟩⟩ + +/-- The average of the first `n + 1` iterates of the distribution operator. -/ +noncomputable def cesaro (μ₀ : ProbabilityMeasure P.State) (n : ℕ) : Measure P.State := + (((n : ℝ≥0∞) + 1))⁻¹ • ∑ k ∈ Finset.range (n + 1), + ((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State) + +instance isProbabilityMeasure_cesaro (μ₀ : ProbabilityMeasure P.State) (n : ℕ) : + IsProbabilityMeasure (P.cesaro μ₀ n) := by + constructor + have hsum : (∑ k ∈ Finset.range (n + 1), + ((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State)) univ + = (n : ℝ≥0∞) + 1 := by + rw [Measure.finsetSum_apply] + simp [measure_univ] + rw [cesaro, Measure.smul_apply, hsum, smul_eq_mul] + refine ENNReal.inv_mul_cancel (by positivity) (by simp) + +/-- The integral against a Cesàro average is the average of the integrals. -/ +theorem integral_cesaro (μ₀ : ProbabilityMeasure P.State) (n : ℕ) (h : P.State →ᵇ ℝ) : + ∫ s, h s ∂(P.cesaro μ₀ n) + = (↑(n + 1))⁻¹ * ∑ k ∈ Finset.range (n + 1), + ∫ s, h s ∂((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State) := by + rw [cesaro, integral_smul_measure, integral_finsetSum_measure] + · push_cast + rw [ENNReal.toReal_inv, ENNReal.toReal_add (by simp) (by simp)] + simp [smul_eq_mul] + · intro k _ + exact h.integrable _ + +/-- Integration against a bounded continuous function is weakly continuous. -/ +theorem continuous_integral_bcf (h : P.State →ᵇ ℝ) : + Continuous fun ν : ProbabilityMeasure P.State => ∫ s, h s ∂(ν : Measure P.State) := by + rw [continuous_iff_continuousAt] + intro μ + exact ProbabilityMeasure.tendsto_iff_forall_integral_tendsto.mp + (tendsto_id : Filter.Tendsto id (𝓝 μ) (𝓝 μ)) h + +omit [BorelSpace Z] in +theorem abs_integral_le (ν : Measure P.State) [IsProbabilityMeasure ν] (h : P.State →ᵇ ℝ) : + |∫ s, h s ∂ν| ≤ ‖h‖ := by + rw [← Real.norm_eq_abs] + refine (norm_integral_le_of_norm_le_const (C := ‖h‖) ?_).trans_eq ?_ + · filter_upwards with s using h.norm_coe_le_norm s + · simp + +/-- Applying the operator to the test function advances the measure by one period. -/ +theorem integral_markovOp_iterate (μ₀ : ProbabilityMeasure P.State) (k : ℕ) + (h : P.State →ᵇ ℝ) : + ∫ s, P.markovOp h s ∂((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State) + = ∫ s, h s ∂((P.pushProb^[k + 1] μ₀ : ProbabilityMeasure P.State) : Measure P.State) := by + rw [Function.iterate_succ_apply', P.coe_pushProb] + exact (P.integral_push _ h).symm + +/-- **The telescoping estimate.** The average and its image differ by a single boundary +term over `n + 1`, which is the whole point of averaging: the individual iterates need not +converge, but this difference must vanish. -/ +theorem integral_cesaro_sub (μ₀ : ProbabilityMeasure P.State) (n : ℕ) (h : P.State →ᵇ ℝ) : + ∫ s, P.markovOp h s ∂(P.cesaro μ₀ n) - ∫ s, h s ∂(P.cesaro μ₀ n) + = (↑(n + 1))⁻¹ * + (∫ s, h s ∂((P.pushProb^[n + 1] μ₀ : ProbabilityMeasure P.State) : Measure P.State) + - ∫ s, h s ∂(μ₀ : Measure P.State)) := by + rw [P.integral_cesaro μ₀ n (P.markovOp h), P.integral_cesaro μ₀ n h, ← mul_sub] + congr 1 + rw [← Finset.sum_sub_distrib] + have hstep : ∀ k ∈ Finset.range (n + 1), + (∫ s, P.markovOp h s ∂((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State)) + - ∫ s, h s ∂((P.pushProb^[k] μ₀ : ProbabilityMeasure P.State) : Measure P.State) + = (fun j => ∫ s, h s + ∂((P.pushProb^[j] μ₀ : ProbabilityMeasure P.State) : Measure P.State)) (k + 1) + - (fun j => ∫ s, h s + ∂((P.pushProb^[j] μ₀ : ProbabilityMeasure P.State) : Measure P.State)) k := + fun k _ => by rw [P.integral_markovOp_iterate] + rw [Finset.sum_congr rfl hstep, + Finset.sum_range_sub (fun j => ∫ s, h s + ∂((P.pushProb^[j] μ₀ : ProbabilityMeasure P.State) : Measure P.State))] + simp + +theorem tendsto_cesaro_sub (μ₀ : ProbabilityMeasure P.State) (h : P.State →ᵇ ℝ) : + Tendsto (fun n => ∫ s, P.markovOp h s ∂(P.cesaro μ₀ n) - ∫ s, h s ∂(P.cesaro μ₀ n)) + atTop (𝓝 0) := by + have hbound : ∀ n : ℕ, + ‖∫ s, P.markovOp h s ∂(P.cesaro μ₀ n) - ∫ s, h s ∂(P.cesaro μ₀ n)‖ + ≤ (((n : ℝ) + 1))⁻¹ * (2 * ‖h‖) := by + intro n + rw [Real.norm_eq_abs, P.integral_cesaro_sub μ₀ n h, abs_mul] + push_cast + rw [abs_of_nonneg (by positivity : (0 : ℝ) ≤ ((n : ℝ) + 1)⁻¹)] + refine mul_le_mul_of_nonneg_left ?_ (by positivity) + refine (abs_sub _ _).trans ?_ + have h1 := P.abs_integral_le + ((P.pushProb^[n + 1] μ₀ : ProbabilityMeasure P.State) : Measure P.State) h + have h2 := P.abs_integral_le ((μ₀ : ProbabilityMeasure P.State) : Measure P.State) h + linarith + refine squeeze_zero_norm hbound ?_ + simpa using (tendsto_one_div_add_atTop_nhds_zero_nat.mul_const (2 * ‖h‖)) + +/-- **Krylov–Bogolyubov: a stationary agent distribution exists.** Average the iterates and +take a cluster point, which exists because the space of probability measures over the +compact state space is compact. The telescoping estimate makes that cluster point +stationary. -/ +theorem exists_isStationary (μ₀ : ProbabilityMeasure P.State) : + ∃ μ : ProbabilityMeasure P.State, P.IsStationary μ := by + set ν : ℕ → ProbabilityMeasure P.State := fun n => ⟨P.cesaro μ₀ n, inferInstance⟩ with hν + obtain ⟨μ, hμ⟩ := exists_clusterPt_of_compactSpace (Filter.map ν atTop) + refine ⟨μ, ?_⟩ + have key : ∀ h : P.State →ᵇ ℝ, + ∫ s, h s ∂((P.pushProb μ : ProbabilityMeasure P.State) : Measure P.State) + = ∫ s, h s ∂(μ : Measure P.State) := by + intro h + set g : ProbabilityMeasure P.State → ℝ := fun ρ => + ∫ s, P.markovOp h s ∂(ρ : Measure P.State) - ∫ s, h s ∂(ρ : Measure P.State) with hg + have hgc : Continuous g := + (P.continuous_integral_bcf _).sub (P.continuous_integral_bcf _) + have hgt : Filter.map (g ∘ ν) atTop ≤ 𝓝 0 := P.tendsto_cesaro_sub μ₀ h + have hcl : ClusterPt (g μ) (Filter.map (g ∘ ν) atTop) := by + rw [← Filter.map_map] + exact hμ.map hgc.continuousAt le_rfl + have hz : g μ = 0 := by + by_contra hne + have hdisj : Disjoint (𝓝 (g μ)) (𝓝 (0 : ℝ)) := disjoint_nhds_nhds.mpr hne + have : (𝓝 (g μ) ⊓ Filter.map (g ∘ ν) atTop).NeBot := hcl + exact (this.mono (inf_le_inf_left _ hgt)).ne (_root_.disjoint_iff.mp hdisj) + have := P.integral_push (μ : Measure P.State) h + simp only [hg, sub_eq_zero] at hz + rw [P.coe_pushProb, this, hz] + have hfin : (P.pushProb μ).toFiniteMeasure = μ.toFiniteMeasure := + FiniteMeasure.ext_of_forall_integral_eq key + exact ProbabilityMeasure.toMeasure_injective + (congrArg (fun x : FiniteMeasure P.State => (x : Measure P.State)) hfin) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Distribution/Uniqueness.lean b/LeanPool/AiyagariEquilibrium/Distribution/Uniqueness.lean new file mode 100644 index 000000000..f84ab0a7f --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Distribution/Uniqueness.lean @@ -0,0 +1,560 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Distribution.Stationary +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationMonotone + +/-! +# Uniqueness of the stationary agent distribution + +A Doeblin argument, made available by the borrowing constraint. + +The asset transition is deterministic given the shock, so the laws started from two +different asset levels have disjoint supports and no minorization against a spread-out +measure can hold. An ATOM escapes that obstruction: if a long enough run of bad income +shocks drives every household to exactly the borrowing constraint, then every `N`-step law +charges the single point `s₀ = (0, z₀)` with probability at least `ε`, and that is a genuine +Doeblin condition. + +The contraction is run on the function side, where `markovOp` already lives, rather than on +signed measures. The oscillation `supF h - infF h` contracts by `1 - ε` every `N` steps, so +for two stationary distributions `∫ h dμ - ∫ h dν` is squeezed to zero for every bounded +continuous `h`, and extensionality finishes. +-/ + +@[expose] public section + +open scoped NNReal ENNReal +open Set Filter Topology BoundedContinuousFunction MeasureTheory + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### The range of a bounded continuous function -/ + +theorem bddAbove_range (h : P.State →ᵇ ℝ) : BddAbove (Set.range h) := by + refine ⟨‖h‖, ?_⟩ + rintro _ ⟨s, rfl⟩ + exact (abs_le.mp (h.norm_coe_le_norm s)).2 + +theorem bddBelow_range (h : P.State →ᵇ ℝ) : BddBelow (Set.range h) := by + refine ⟨-‖h‖, ?_⟩ + rintro _ ⟨s, rfl⟩ + exact (abs_le.mp (h.norm_coe_le_norm s)).1 + +/-- The supremum of a bounded continuous function on the state space. -/ +noncomputable def supF (h : P.State →ᵇ ℝ) : ℝ := ⨆ s, h s + +/-- The infimum of a bounded continuous function on the state space. -/ +noncomputable def infF (h : P.State →ᵇ ℝ) : ℝ := ⨅ s, h s + +theorem le_supF (h : P.State →ᵇ ℝ) (s : P.State) : h s ≤ P.supF h := + le_ciSup (f := fun t => h t) (P.bddAbove_range h) s + +theorem infF_le (h : P.State →ᵇ ℝ) (s : P.State) : P.infF h ≤ h s := + ciInf_le (f := fun t => h t) (P.bddBelow_range h) s + +theorem infF_le_supF (h : P.State →ᵇ ℝ) : P.infF h ≤ P.supF h := + le_trans (P.infF_le h Classical.ofNonempty) (P.le_supF h Classical.ofNonempty) + +/-- If every gap is at most `C`, the oscillation is at most `C`. -/ +theorem sup_sub_inf_le {h : P.State →ᵇ ℝ} {C : ℝ} (hC : ∀ s t, h s - h t ≤ C) : + P.supF h - P.infF h ≤ C := by + have hkey : ∀ s, h s ≤ C + P.infF h := by + intro s + have : h s - C ≤ P.infF h := + le_ciInf (f := fun t => h t) fun t => by linarith [hC s t] + linarith + have : P.supF h ≤ C + P.infF h := ciSup_le hkey + linarith + +/-! ### The Markov operator preserves bounds -/ + +theorem infF_le_markovOp (h : P.State →ᵇ ℝ) (s : P.State) : P.infF h ≤ P.markovOp h s := by + rw [P.markovOp_apply] + calc P.infF h = ∑ z', P.prob s z' * P.infF h := by + rw [← Finset.sum_mul, P.prob_sum, one_mul] + _ ≤ ∑ z', P.prob s z' * h (P.nextState s z') := + Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (P.infF_le h _) (P.prob_nonneg s z') + +theorem markovOp_le_supF (h : P.State →ᵇ ℝ) (s : P.State) : P.markovOp h s ≤ P.supF h := by + rw [P.markovOp_apply] + calc ∑ z', P.prob s z' * h (P.nextState s z') + ≤ ∑ z', P.prob s z' * P.supF h := + Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (P.le_supF h _) (P.prob_nonneg s z') + _ = P.supF h := by rw [← Finset.sum_mul, P.prob_sum, one_mul] + +theorem infF_le_iterate (h : P.State →ᵇ ℝ) (n : ℕ) (s : P.State) : + P.infF h ≤ (P.markovOp^[n] h) s := by + induction n generalizing s with + | zero => exact P.infF_le h s + | succ n ih => + rw [Function.iterate_succ_apply'] + exact le_trans (le_ciInf fun t => ih t) (P.infF_le_markovOp _ s) + +theorem iterate_le_supF (h : P.State →ᵇ ℝ) (n : ℕ) (s : P.State) : + (P.markovOp^[n] h) s ≤ P.supF h := by + induction n generalizing s with + | zero => exact P.le_supF h s + | succ n ih => + rw [Function.iterate_succ_apply'] + exact le_trans (P.markovOp_le_supF _ s) (ciSup_le fun t => ih t) + +/-! ### The Doeblin minorization -/ + +/-- One period under the worst income shock `z₀`. -/ +noncomputable def badStep (z₀ : Z) (s : P.State) : P.State := P.nextState s z₀ + +/-- **Minorization, lower half.** After `n` periods, a share `p₀ⁿ` of the mass sits at the +state reached by `n` consecutive bad shocks, and the rest is worth at least `infF h`. -/ +theorem iterate_lower {z₀ : Z} {p₀ : ℝ} (hp0 : 0 ≤ p₀) (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) + (h : P.State →ᵇ ℝ) (n : ℕ) (s : P.State) : + p₀ ^ n * h ((P.badStep z₀)^[n] s) + (1 - p₀ ^ n) * P.infF h ≤ (P.markovOp^[n] h) s := by + classical + induction n generalizing s with + | zero => simp + | succ n ih => + rw [Function.iterate_succ_apply' P.markovOp n h, P.markovOp_apply, + Function.iterate_succ_apply (P.badStep z₀) n s, pow_succ] + have hq0 : 0 ≤ P.prob s z₀ := P.prob_nonneg s z₀ + have hsplit : ∑ z', P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') + = P.prob s z₀ * (P.markovOp^[n] h) (P.badStep z₀ s) + + ∑ z' ∈ Finset.univ.erase z₀, + P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') := + (Finset.add_sum_erase _ _ (Finset.mem_univ z₀)).symm + have hprobrest : ∑ z' ∈ Finset.univ.erase z₀, P.prob s z' = 1 - P.prob s z₀ := by + have hs := P.prob_sum s + rw [← Finset.add_sum_erase _ _ (Finset.mem_univ z₀)] at hs + linarith + have hrest : (1 - P.prob s z₀) * P.infF h + ≤ ∑ z' ∈ Finset.univ.erase z₀, + P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') := by + calc (1 - P.prob s z₀) * P.infF h + = ∑ z' ∈ Finset.univ.erase z₀, P.prob s z' * P.infF h := by + rw [← Finset.sum_mul, hprobrest] + _ ≤ _ := Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (P.infF_le_iterate h n _) (P.prob_nonneg s z') + have hIH := mul_le_mul_of_nonneg_left (ih (P.badStep z₀ s)) hq0 + have hHI : P.infF h ≤ h ((P.badStep z₀)^[n] (P.badStep z₀ s)) := P.infF_le h _ + have hgap : 0 ≤ (P.prob s z₀ - p₀) * p₀ ^ n := + mul_nonneg (sub_nonneg.mpr (hp s)) (pow_nonneg hp0 n) + rw [hsplit] + nlinarith [hIH, hrest, hHI, hgap, pow_nonneg hp0 n, hp s] + +/-- **Minorization, upper half.** -/ +theorem iterate_upper {z₀ : Z} {p₀ : ℝ} (hp0 : 0 ≤ p₀) (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) + (h : P.State →ᵇ ℝ) (n : ℕ) (s : P.State) : + (P.markovOp^[n] h) s ≤ p₀ ^ n * h ((P.badStep z₀)^[n] s) + (1 - p₀ ^ n) * P.supF h := by + classical + induction n generalizing s with + | zero => simp + | succ n ih => + rw [Function.iterate_succ_apply' P.markovOp n h, P.markovOp_apply, + Function.iterate_succ_apply (P.badStep z₀) n s, pow_succ] + have hq0 : 0 ≤ P.prob s z₀ := P.prob_nonneg s z₀ + have hsplit : ∑ z', P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') + = P.prob s z₀ * (P.markovOp^[n] h) (P.badStep z₀ s) + + ∑ z' ∈ Finset.univ.erase z₀, + P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') := + (Finset.add_sum_erase _ _ (Finset.mem_univ z₀)).symm + have hprobrest : ∑ z' ∈ Finset.univ.erase z₀, P.prob s z' = 1 - P.prob s z₀ := by + have hs := P.prob_sum s + rw [← Finset.add_sum_erase _ _ (Finset.mem_univ z₀)] at hs + linarith + have hrest : ∑ z' ∈ Finset.univ.erase z₀, + P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') + ≤ (1 - P.prob s z₀) * P.supF h := by + calc ∑ z' ∈ Finset.univ.erase z₀, + P.prob s z' * (P.markovOp^[n] h) (P.nextState s z') + ≤ ∑ z' ∈ Finset.univ.erase z₀, P.prob s z' * P.supF h := + Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (P.iterate_le_supF h n _) (P.prob_nonneg s z') + _ = (1 - P.prob s z₀) * P.supF h := by rw [← Finset.sum_mul, hprobrest] + have hIH := mul_le_mul_of_nonneg_left (ih (P.badStep z₀ s)) hq0 + have hHI : h ((P.badStep z₀)^[n] (P.badStep z₀ s)) ≤ P.supF h := P.le_supF h _ + have hgap : 0 ≤ (P.prob s z₀ - p₀) * p₀ ^ n := + mul_nonneg (sub_nonneg.mpr (hp s)) (pow_nonneg hp0 n) + rw [hsplit] + nlinarith [hIH, hrest, hHI, hgap, pow_nonneg hp0 n, hp s] + +/-! ### The oscillation contracts -/ + +/-- **One Doeblin cycle contracts the oscillation** by the factor `1 - p₀ᴺ`. Both households +are compared against the *same* value `h s₀`, which is what `hbad` buys. -/ +theorem osc_markovOp_le {z₀ : Z} {s₀ : P.State} {p₀ : ℝ} {N : ℕ} (hp0 : 0 ≤ p₀) + (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) (hbad : ∀ s, (P.badStep z₀)^[N] s = s₀) + (h : P.State →ᵇ ℝ) : + P.supF (P.markovOp^[N] h) - P.infF (P.markovOp^[N] h) + ≤ (1 - p₀ ^ N) * (P.supF h - P.infF h) := by + refine P.sup_sub_inf_le fun s t => ?_ + have hs := P.iterate_upper hp0 hp h N s + have ht := P.iterate_lower hp0 hp h N t + rw [hbad s] at hs + rw [hbad t] at ht + linarith + +theorem osc_cycle_le {z₀ : Z} {s₀ : P.State} {p₀ : ℝ} {N : ℕ} (hp0 : 0 ≤ p₀) + (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) (hbad : ∀ s, (P.badStep z₀)^[N] s = s₀) + (k : ℕ) (h : P.State →ᵇ ℝ) : + P.supF ((P.markovOp^[N])^[k] h) - P.infF ((P.markovOp^[N])^[k] h) + ≤ (1 - p₀ ^ N) ^ k * (P.supF h - P.infF h) := by + have hp1 : p₀ ≤ 1 := le_trans (hp Classical.ofNonempty) (P.prob_le_one _ _) + have hfac : (0 : ℝ) ≤ 1 - p₀ ^ N := by + have : p₀ ^ N ≤ 1 := pow_le_one₀ hp0 hp1 + linarith + induction k generalizing h with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply] + calc P.supF ((P.markovOp^[N])^[k] (P.markovOp^[N] h)) + - P.infF ((P.markovOp^[N])^[k] (P.markovOp^[N] h)) + ≤ (1 - p₀ ^ N) ^ k + * (P.supF (P.markovOp^[N] h) - P.infF (P.markovOp^[N] h)) := ih _ + _ ≤ (1 - p₀ ^ N) ^ k * ((1 - p₀ ^ N) * (P.supF h - P.infF h)) := + mul_le_mul_of_nonneg_left (P.osc_markovOp_le hp0 hp hbad h) (by positivity) + _ = (1 - p₀ ^ N) ^ (k + 1) * (P.supF h - P.infF h) := by ring + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-! ### Stationarity is preserved along the iteration -/ + +theorem integral_iterate_eq {μ : Measure P.State} [IsProbabilityMeasure μ] + (hμ : P.push μ = μ) (n : ℕ) (h : P.State →ᵇ ℝ) : + ∫ s, (P.markovOp^[n] h) s ∂μ = ∫ s, h s ∂μ := by + induction n generalizing h with + | zero => simp + | succ n ih => + rw [Function.iterate_succ_apply, ih (P.markovOp h)] + have hint := P.integral_push μ h + rw [hμ] at hint + exact hint.symm + +theorem integral_cycle_eq {μ : Measure P.State} [IsProbabilityMeasure μ] + (hμ : P.push μ = μ) (N k : ℕ) (h : P.State →ᵇ ℝ) : + ∫ s, ((P.markovOp^[N])^[k] h) s ∂μ = ∫ s, h s ∂μ := by + induction k generalizing h with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply, ih, P.integral_iterate_eq hμ] + +theorem integral_le_supF (μ : Measure P.State) [IsProbabilityMeasure μ] (h : P.State →ᵇ ℝ) : + ∫ s, h s ∂μ ≤ P.supF h := by + calc ∫ s, h s ∂μ ≤ ∫ _s, P.supF h ∂μ := + integral_mono (h.integrable μ) (integrable_const _) fun s => P.le_supF h s + _ = P.supF h := by simp + +theorem infF_le_integral (μ : Measure P.State) [IsProbabilityMeasure μ] (h : P.State →ᵇ ℝ) : + P.infF h ≤ ∫ s, h s ∂μ := by + calc P.infF h = ∫ _s, P.infF h ∂μ := by simp + _ ≤ ∫ s, h s ∂μ := + integral_mono (integrable_const _) (h.integrable μ) fun s => P.infF_le h s + +/-! ### Uniqueness -/ + +/-- **The stationary agent distribution is unique**, given a Doeblin condition: some income +state `z₀` is always reachable with probability at least `p₀ > 0`, and `N` consecutive `z₀` +shocks drive *every* state to the single state `s₀`. + +In the income fluctuation problem `s₀` is the borrowing constraint. The atom is what makes a +Doeblin argument possible at all: the asset transition is deterministic, so laws started from +different assets have disjoint supports and no minorization against a spread-out measure can +hold — but they all charge the constraint. -/ +theorem stationary_unique {z₀ : Z} {s₀ : P.State} {p₀ : ℝ} {N : ℕ} (hp0 : 0 < p₀) + (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) (hbad : ∀ s, (P.badStep z₀)^[N] s = s₀) + {μ ν : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) (hν : P.IsStationary ν) : + μ = ν := by + have hp1 : p₀ ≤ 1 := le_trans (hp Classical.ofNonempty) (P.prob_le_one _ _) + have hεpos : 0 < p₀ ^ N := pow_pos hp0 N + have hεle : p₀ ^ N ≤ 1 := pow_le_one₀ hp0.le hp1 + have hμ' : P.push (μ : Measure P.State) = (μ : Measure P.State) := + congrArg (fun x : ProbabilityMeasure P.State => (x : Measure P.State)) hμ + have hν' : P.push (ν : Measure P.State) = (ν : Measure P.State) := + congrArg (fun x : ProbabilityMeasure P.State => (x : Measure P.State)) hν + have key : ∀ h : P.State →ᵇ ℝ, + ∫ s, h s ∂(μ : Measure P.State) = ∫ s, h s ∂(ν : Measure P.State) := by + intro h + set D := ∫ s, h s ∂(μ : Measure P.State) - ∫ s, h s ∂(ν : Measure P.State) with hD + have hbound : ∀ k : ℕ, |D| ≤ (1 - p₀ ^ N) ^ k * (P.supF h - P.infF h) := by + intro k + set g := (P.markovOp^[N])^[k] h with hg + have hμg : ∫ s, g s ∂(μ : Measure P.State) = ∫ s, h s ∂(μ : Measure P.State) := + P.integral_cycle_eq hμ' N k h + have hνg : ∫ s, g s ∂(ν : Measure P.State) = ∫ s, h s ∂(ν : Measure P.State) := + P.integral_cycle_eq hν' N k h + have hgap := P.osc_cycle_le hp0.le hp hbad k h + have h1 := P.integral_le_supF (μ : Measure P.State) g + have h2 := P.infF_le_integral (ν : Measure P.State) g + have h3 := P.integral_le_supF (ν : Measure P.State) g + have h4 := P.infF_le_integral (μ : Measure P.State) g + rw [abs_le] + constructor <;> [linarith; linarith] + have hlim : Filter.Tendsto + (fun k : ℕ => (1 - p₀ ^ N) ^ k * (P.supF h - P.infF h)) atTop (𝓝 0) := by + have : Filter.Tendsto (fun k : ℕ => (1 - p₀ ^ N) ^ k) atTop (𝓝 0) := + tendsto_pow_atTop_nhds_zero_of_lt_one (by linarith) (by linarith) + simpa using this.mul_const (P.supF h - P.infF h) + have : |D| ≤ 0 := + le_of_tendsto_of_tendsto' tendsto_const_nhds hlim hbound + have : D = 0 := by + have := abs_nonneg D + have hz : |D| = 0 := le_antisymm ‹|D| ≤ 0› (abs_nonneg D) + exact abs_eq_zero.mp hz + linarith [hD ▸ this] + have hfin : (μ : ProbabilityMeasure P.State).toFiniteMeasure + = (ν : ProbabilityMeasure P.State).toFiniteMeasure := + FiniteMeasure.ext_of_forall_integral_eq key + exact ProbabilityMeasure.toMeasure_injective + (congrArg (fun x : FiniteMeasure P.State => (x : Measure P.State)) hfin) + +/-! ### Discharging the Doeblin condition in the model + +The abstract condition asks that `N` bad shocks drive *every* state to the borrowing +constraint. Monotonicity of the policy reduces that to the single worst case: if the richest +household is exhausted in `N` periods, everyone is, because the asset map is increasing and +bounded below by zero. -/ + +/-- The asset map under the worst income shock. -/ +noncomputable def gBad (z₀ : Z) (x : ↥(Icc assetFloor assetCap)) : ↥(Icc assetFloor assetCap) := + P.nextAssets (x, z₀) + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem gBad_mono (z₀ : Z) : Monotone (P.gBad z₀) := fun x y hxy => + P.policy_mono x.2 y.2 hxy + +/-- The richest state. -/ +def topState : ↥(Icc assetFloor assetCap) := ⟨assetCap, ⟨P.assetFloor_le_assetCap, le_rfl⟩⟩ + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem le_topState (x : ↥(Icc assetFloor assetCap)) : x ≤ P.topState := x.2.2 + +/-- The bottom state: the borrowing constraint. -/ +def botState : ↥(Icc assetFloor assetCap) := ⟨assetFloor, ⟨le_rfl, P.assetFloor_le_assetCap⟩⟩ + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem botState_le (x : ↥(Icc assetFloor assetCap)) : P.botState ≤ x := x.2.1 + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **Monotonicity reduces exhaustion to the worst case.** -/ +theorem gBad_iterate_eq_bot {z₀ : Z} {N : ℕ} (hexh : (P.gBad z₀)^[N] P.topState = P.botState) + (x : ↥(Icc assetFloor assetCap)) : (P.gBad z₀)^[N] x = P.botState := + le_antisymm (hexh ▸ (P.gBad_mono z₀).iterate N (P.le_topState x)) (P.botState_le _) + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem badStep_iterate_succ (z₀ : Z) (n : ℕ) (s : P.State) : + (P.badStep z₀)^[n + 1] s = ((P.gBad z₀)^[n] (P.nextAssets s), z₀) := by + induction n generalizing s with + | zero => rfl + | succ n ih => + rw [Function.iterate_succ_apply, ih (P.badStep z₀ s)] + rfl + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The Doeblin condition holds** once the richest household is exhausted by `N` bad +shocks: after one more period every state has collapsed to the borrowing constraint. -/ +theorem badStep_iterate_eq {z₀ : Z} {N : ℕ} (hexh : (P.gBad z₀)^[N] P.topState = P.botState) + (s : P.State) : (P.badStep z₀)^[N + 1] s = (P.botState, z₀) := by + rw [P.badStep_iterate_succ z₀ N s, P.gBad_iterate_eq_bot hexh] + +/-- **Uniqueness of the stationary agent distribution for the income fluctuation problem.** + +The two economic assumptions are that the worst income state `z₀` is reachable from every +income state (`hreach`), and that a household starting with the maximum assets is driven to +the borrowing constraint by `N` consecutive draws of `z₀` (`hexh`). Monotonicity of the +policy then carries the second from the richest household to every household. -/ +theorem stationary_unique_of_exhausts {z₀ : Z} {N : ℕ} + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + (hexh : (P.gBad z₀)^[N] P.topState = P.botState) + {μ ν : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) (hν : P.IsStationary ν) : + μ = ν := by + obtain ⟨z₁, -, hmin⟩ := Finset.exists_min_image Finset.univ + (fun z => P.transitionMatrix z z₀) ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + exact P.stationary_unique (p₀ := P.transitionMatrix z₁ z₀) (hreach z₁) + (fun s => hmin _ (Finset.mem_univ _)) (P.badStep_iterate_eq hexh) hμ hν + +/-- **Existence and uniqueness together.** -/ +theorem existsUnique_isStationary {z₀ : Z} {N : ℕ} + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + (hexh : (P.gBad z₀)^[N] P.topState = P.botState) : + ∃! μ : ProbabilityMeasure P.State, P.IsStationary μ := by + obtain ⟨μ, hμ⟩ := P.exists_isStationary + ⟨Measure.dirac (Classical.ofNonempty : P.State), inferInstance⟩ + exact ⟨μ, hμ, fun ν hν => P.stationary_unique_of_exhausts hreach hexh hν hμ⟩ + +/-! ### Convergence + +Uniqueness says there is one stationary distribution; convergence says the economy finds it. +The same oscillation contraction gives both. Iterating the operator flattens any test +function geometrically, and a flat test function cannot tell two distributions apart, so the +iterates of ANY initial distribution become indistinguishable from the stationary one. + +This is what makes the stationary distribution computable rather than merely unique: it is +the limit of iterating from wherever you start. -/ + +theorem integral_pushProb_iterate (μ : ProbabilityMeasure P.State) (m : ℕ) (h : P.State →ᵇ ℝ) : + ∫ s, h s ∂((P.pushProb^[m] μ : ProbabilityMeasure P.State) : Measure P.State) + = ∫ s, (P.markovOp^[m] h) s ∂(μ : Measure P.State) := by + induction m generalizing h with + | zero => simp + | succ m ih => + rw [Function.iterate_succ_apply', P.coe_pushProb, P.integral_push, ih, + Function.iterate_succ_apply] + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- Oscillation cannot grow under the operator. -/ +theorem osc_iterate_le (h : P.State →ᵇ ℝ) (j : ℕ) : + P.supF (P.markovOp^[j] h) - P.infF (P.markovOp^[j] h) ≤ P.supF h - P.infF h := by + have h1 : P.supF (P.markovOp^[j] h) ≤ P.supF h := + ciSup_le fun s => P.iterate_le_supF h j s + have h2 : P.infF h ≤ P.infF (P.markovOp^[j] h) := + le_ciInf fun s => P.infF_le_iterate h j s + linarith + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The oscillation of an iterated test function vanishes.** -/ +theorem tendsto_osc_iterate {z₀ : Z} {s₀ : P.State} {p₀ : ℝ} {N : ℕ} (hp0 : 0 < p₀) + (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) (hbad : ∀ s, (P.badStep z₀)^[N] s = s₀) + (h : P.State →ᵇ ℝ) : + Tendsto (fun m => P.supF (P.markovOp^[m] h) - P.infF (P.markovOp^[m] h)) atTop (𝓝 0) := by + have hp1 : p₀ ≤ 1 := le_trans (hp Classical.ofNonempty) (P.prob_le_one _ _) + have hεpos : 0 < p₀ ^ N := pow_pos hp0 N + have hεle : p₀ ^ N ≤ 1 := pow_le_one₀ hp0.le hp1 + rw [Metric.tendsto_atTop] + intro γ hγ + have hlim : Tendsto (fun k : ℕ => (1 - p₀ ^ N) ^ k * (P.supF h - P.infF h)) atTop (𝓝 0) := by + have := tendsto_pow_atTop_nhds_zero_of_lt_one (by linarith) (by linarith : 1 - p₀ ^ N < 1) + simpa using this.mul_const (P.supF h - P.infF h) + obtain ⟨k, hk⟩ := (hlim.eventually (gt_mem_nhds hγ)).exists + refine ⟨N * k, fun m hm => ?_⟩ + obtain ⟨j, rfl⟩ := Nat.exists_eq_add_of_le hm + have hsplit : P.markovOp^[N * k + j] h = P.markovOp^[j] ((P.markovOp^[N])^[k] h) := by + rw [add_comm, Function.iterate_add_apply, Function.iterate_mul] + rw [Real.dist_eq, hsplit, sub_zero, + abs_of_nonneg (by linarith [P.infF_le_supF (P.markovOp^[j] ((P.markovOp^[N])^[k] h))])] + exact lt_of_le_of_lt (le_trans (P.osc_iterate_le _ j) + (P.osc_cycle_le hp0.le hp hbad k h)) hk + +/-- **Convergence to the stationary distribution.** From any starting point, the iterates of +the distribution operator converge weakly to the stationary distribution. -/ +theorem tendsto_pushProb_iterate {z₀ : Z} {s₀ : P.State} {p₀ : ℝ} {N : ℕ} (hp0 : 0 < p₀) + (hp : ∀ s : P.State, p₀ ≤ P.prob s z₀) (hbad : ∀ s, (P.badStep z₀)^[N] s = s₀) + (μ₀ : ProbabilityMeasure P.State) {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) : + Tendsto (fun m => P.pushProb^[m] μ₀) atTop (𝓝 μ) := by + have hμ' : P.push (μ : Measure P.State) = (μ : Measure P.State) := + congrArg (fun x : ProbabilityMeasure P.State => (x : Measure P.State)) hμ + rw [ProbabilityMeasure.tendsto_iff_forall_integral_tendsto] + intro h + rw [tendsto_iff_dist_tendsto_zero] + refine squeeze_zero (fun _ => dist_nonneg) (fun m => ?_) + (P.tendsto_osc_iterate hp0 hp hbad h) + -- the gap is at most the oscillation of the iterated test function + have hleft : ∫ s, h s ∂((P.pushProb^[m] μ₀ : ProbabilityMeasure P.State) : Measure P.State) + = ∫ s, (P.markovOp^[m] h) s ∂(μ₀ : Measure P.State) := P.integral_pushProb_iterate μ₀ m h + have hright : ∫ s, h s ∂(μ : Measure P.State) + = ∫ s, (P.markovOp^[m] h) s ∂(μ : Measure P.State) := + (P.integral_iterate_eq hμ' m h).symm + rw [Real.dist_eq, hleft, hright] + have h1 := P.integral_le_supF (μ₀ : Measure P.State) (P.markovOp^[m] h) + have h2 := P.infF_le_integral (μ₀ : Measure P.State) (P.markovOp^[m] h) + have h3 := P.integral_le_supF (μ : Measure P.State) (P.markovOp^[m] h) + have h4 := P.infF_le_integral (μ : Measure P.State) (P.markovOp^[m] h) + rw [abs_le] + constructor <;> linarith + +/-! ### Exhaustion from strict dissaving + +`badStep_iterate_eq` takes exhaustion as a hypothesis in the awkward form `gBad^[N] top = bot` +-- an exact hit on the borrowing constraint after a specified number of periods. That is hard +to check directly, and in the earlier calibrations it was obtained only by making the policy +identically zero. + +In Açıkgöz's April 2016 author draft, Proposition 5 (printed page 9; proof in Appendix E, +printed page 32) uses two natural facts: +the constraint binds near zero, and assets strictly decline above that at the worst income +state. A continuous strictly declining map on a compact interval has a UNIFORM minimum +decline, so finitely many periods carry the richest household below the threshold, and one +more step pins it to the constraint. + +In that edition, Proposition 3 (printed page 8) supplies worst-state decline, while +Proposition 4 (the same page) supplies a finite asset bound. The source is +https://mpra.ub.uni-muenchen.de/71066/1/MPRA_paper_71066.pdf. +The lemma below uses only continuity, binding near the constraint and strict decline on an +already compact interval; the Euler arguments establishing these inputs appear separately. + -/ + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem exists_exhaust_of_decline {z₀ : Z} {a₀ : ℝ} (ha₀ : assetFloor < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc assetFloor a₀, P.policy (a, z₀) = assetFloor) + (hdecl : ∀ a ∈ Icc a₀ assetCap, P.policy (a, z₀) < a) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + classical + -- the minimal decline over the compact interval above the threshold + have hne : (Icc a₀ assetCap).Nonempty := ⟨a₀, ⟨le_rfl, hle⟩⟩ + have hcont : ContinuousOn (fun a : ℝ => a - P.policy (a, z₀)) (Icc a₀ assetCap) := by + refine continuousOn_id.sub (P.continuousOn_policy.comp + (continuous_id.prodMk continuous_const).continuousOn fun a ha => ?_) + exact ⟨le_trans ha₀.le ha.1, ha.2⟩ + obtain ⟨am, hamem, hamin⟩ := isCompact_Icc.exists_isMinOn hne hcont + set Δ : ℝ := am - P.policy (am, z₀) with hΔdef + have hΔpos : 0 < Δ := by rw [hΔdef]; linarith [hdecl am hamem] + have hstep : ∀ a ∈ Icc a₀ assetCap, P.policy (a, z₀) ≤ a - Δ := fun a ha => by + have h := hamin ha + rw [Set.mem_ofPred_eq] at h + rw [hΔdef] + linarith + -- after `k` steps either the threshold is reached or assets have fallen by `k·Δ` + have key : ∀ k : ℕ, (((P.gBad z₀)^[k] P.topState : ↥(Icc assetFloor assetCap)) : ℝ) ≤ a₀ ∨ + (((P.gBad z₀)^[k] P.topState : ↥(Icc assetFloor assetCap)) : ℝ) ≤ assetCap - k * Δ := by + intro k + induction k with + | zero => right; simp [topState] + | succ k ih => + rw [Function.iterate_succ_apply'] + have hval : (((P.gBad z₀) ((P.gBad z₀)^[k] P.topState) : + ↥(Icc assetFloor assetCap)) : ℝ) + = P.policy ((((P.gBad z₀)^[k] P.topState : ↥(Icc assetFloor assetCap)) : ℝ), z₀) := rfl + rcases ih with h | h + · -- already at or below the threshold: the next step is exactly zero + left + rw [hval, hzero _ ⟨((P.gBad z₀)^[k] P.topState).2.1, h⟩] + exact ha₀.le + · rcases le_total (((P.gBad z₀)^[k] P.topState : ↥(Icc assetFloor assetCap)) : ℝ) a₀ + with hlow | hhigh + · left + rw [hval, hzero _ ⟨((P.gBad z₀)^[k] P.topState).2.1, hlow⟩] + exact ha₀.le + · right + rw [hval] + have hmem : (((P.gBad z₀)^[k] P.topState : ↥(Icc assetFloor assetCap)) : ℝ) + ∈ Icc a₀ assetCap := ⟨hhigh, ((P.gBad z₀)^[k] P.topState).2.2⟩ + have hdec := hstep _ hmem + push_cast + rw [show assetCap - ((k : ℝ) + 1) * Δ = (assetCap - (k : ℝ) * Δ) - Δ by ring] + linarith + -- finitely many steps carry assets below the threshold + obtain ⟨N, hN⟩ := exists_nat_gt ((assetCap - a₀) / Δ) + refine ⟨N + 1, ?_⟩ + have hNle : (((P.gBad z₀)^[N] P.topState : ↥(Icc assetFloor assetCap)) : ℝ) ≤ a₀ := by + rcases key N with h | h + · exact h + · rw [div_lt_iff₀ hΔpos] at hN + linarith + refine Subtype.ext ?_ + rw [Function.iterate_succ_apply'] + change P.policy ((((P.gBad z₀)^[N] P.topState : ↥(Icc assetFloor assetCap)) : ℝ), z₀) + = assetFloor + exact hzero _ ⟨((P.gBad z₀)^[N] P.topState).2.1, hNle⟩ + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/DynamicProgramming/Bellman.lean b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Bellman.lean new file mode 100644 index 000000000..c3cb4e51d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Bellman.lean @@ -0,0 +1,335 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Blackwell +public import LeanPool.AiyagariEquilibrium.Topology.Berge + +/-! +# The Bellman operator of a deterministic dynamic program + +The operator + + `T v s = sup { r (s, a) + β * v (g (s, a)) | a ∈ Γ s }` + +is shown to map bounded continuous functions to bounded continuous functions, and to +satisfy Blackwell's sufficient conditions. Combined with `LeanEconomics.Blackwell` this +gives the value function of the program, its uniqueness, and convergence of value function +iteration. + +## The formulation + +A program is given by a feasible correspondence `Γ : S → Set A` with nonempty compact +values that is continuous -- upper and lower hemicontinuous -- together with a bounded +continuous reward `r : (S × A) →ᵇ ℝ`, a continuous law of motion `g : C(S × A, S)`, and a +discount factor `β < 1`. + +The feasible set moves with the state, which is what a budget constraint does. The price of +that generality is the continuity of `T v`, and it is paid by Berge's maximum theorem in +`LeanEconomics.Topology.Berge`: `continuous_maxValue` is exactly the statement that the +value of a parametrised maximisation problem is continuous in the parameter. + +Verifying that a particular `Γ` is hemicontinuous is real work, done per model. The two +programs in `LeanEconomics.DynamicProgramming.Examples` discharge it in the two cheapest +ways: a constraint set that does not move, and one pinned to a single continuously varying +point. + +## Main definitions + +* `LeanEconomics.DynamicProgram` : the data of a deterministic dynamic program. +* `LeanEconomics.DynamicProgram.bellman` : its Bellman operator on `S →ᵇ ℝ`. +* `LeanEconomics.DynamicProgram.valueFunction` : the value function. + +## Main results + +* `LeanEconomics.DynamicProgram.blackwell` : the Bellman operator satisfies Blackwell's + conditions, hence is a contraction. +* `LeanEconomics.DynamicProgram.bellman_valueFunction`, + `LeanEconomics.DynamicProgram.eq_valueFunction` : the value function is the unique + solution of the Bellman equation. +* `LeanEconomics.DynamicProgram.exists_optimal_action` : the supremum is attained, so a + greedy policy exists. + +## References + +* Stokey, Lucas and Prescott, *Recursive Methods in Economic Dynamics*, chapter 4. +-/ + +@[expose] public section + +open scoped NNReal +open Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +variable {S A : Type*} [TopologicalSpace S] [TopologicalSpace A] + +/-- A **deterministic dynamic program**: the agent observes a state, picks an action from +the feasible set at that state, collects a reward, and moves to the state the law of motion +dictates. -/ +structure DynamicProgram (S A : Type*) [TopologicalSpace S] [TopologicalSpace A] where + /-- The actions available to the agent, as a function of the state. -/ + feasible : S → Set A + /-- Each feasible set is compact, so that the supremum over it is attained. -/ + isCompact_feasible : ∀ s, IsCompact (feasible s) + /-- The agent always has something to do. -/ + feasible_nonempty : ∀ s, (feasible s).Nonempty + /-- The feasible set does not explode as the state moves. -/ + upperHemicontinuous_feasible : UpperHemicontinuous feasible + /-- The feasible set does not collapse as the state moves. -/ + lowerHemicontinuous_feasible : LowerHemicontinuous feasible + /-- The one-period reward, bounded and continuous in state and action jointly. -/ + reward : (S × A) →ᵇ ℝ + /-- The law of motion. -/ + transition : C(S × A, S) + /-- The discount factor. -/ + discount : ℝ≥0 + /-- Future rewards are discounted strictly, which is what makes the operator a + contraction. -/ + discount_lt_one : discount < 1 + +namespace DynamicProgram + +variable (D : DynamicProgram S A) + +/-- What the agent gets from action `a` in state `s`: this period's reward plus the +discounted continuation value at the state the action leads to. -/ +noncomputable def objective (v : S →ᵇ ℝ) (s : S) (a : A) : ℝ := + D.reward (s, a) + D.discount * v (D.transition (s, a)) + +theorem continuous_uncurry_objective (v : S →ᵇ ℝ) : Continuous ↿(D.objective v) := + D.reward.continuous.add (continuous_const.mul (v.continuous.comp D.transition.continuous)) + +/-- The objective is bounded uniformly in the state and the action, by the size of the +reward plus the discounted size of the continuation value. -/ +theorem abs_objective_le (v : S →ᵇ ℝ) (s : S) (a : A) : + |D.objective v s a| ≤ ‖D.reward‖ + D.discount * ‖v‖ := by + have h₁ : |D.reward (s, a)| ≤ ‖D.reward‖ := by + simpa [Real.norm_eq_abs] using D.reward.norm_coe_le_norm (s, a) + have h₂ : |v (D.transition (s, a))| ≤ ‖v‖ := by + simpa [Real.norm_eq_abs] using v.norm_coe_le_norm (D.transition (s, a)) + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + rw [abs_le] at h₁ h₂ + simp only [objective, abs_le] + constructor <;> + nlinarith [h₁.1, h₁.2, h₂.1, h₂.2, mul_le_mul_of_nonneg_left h₂.1 hβ, + mul_le_mul_of_nonneg_left h₂.2 hβ] + +/-- The Bellman operator as a bare function: the value of the one-period problem, in the +sense of `LeanEconomics.maxValue`. -/ +noncomputable def bellmanFn (v : S →ᵇ ℝ) (s : S) : ℝ := + maxValue (D.objective v) D.feasible s + +theorem le_bellmanFn (v : S →ᵇ ℝ) {s : S} {a : A} (ha : a ∈ D.feasible s) : + D.objective v s a ≤ D.bellmanFn v s := + le_maxValue (D.continuous_uncurry_objective v) (D.isCompact_feasible s) ha + +theorem bellmanFn_le (v : S →ᵇ ℝ) {s : S} {c : ℝ} + (h : ∀ a ∈ D.feasible s, D.objective v s a ≤ c) : D.bellmanFn v s ≤ c := + maxValue_le (D.feasible_nonempty s) h + +theorem abs_bellmanFn_le (v : S →ᵇ ℝ) (s : S) : + |D.bellmanFn v s| ≤ ‖D.reward‖ + D.discount * ‖v‖ := by + obtain ⟨a, ha⟩ := D.feasible_nonempty s + rw [abs_le] + refine ⟨?_, D.bellmanFn_le v fun b _ => (le_abs_self _).trans (D.abs_objective_le v s b)⟩ + exact le_trans (neg_le_of_abs_le (D.abs_objective_le v s a)) (D.le_bellmanFn v ha) + +/-- The **Bellman operator** on bounded continuous functions. Continuity of the image is +Berge's maximum theorem; boundedness is `abs_bellmanFn_le`. -/ +noncomputable def bellman (v : S →ᵇ ℝ) : S →ᵇ ℝ := + BoundedContinuousFunction.ofNormedAddCommGroup (D.bellmanFn v) + (continuous_maxValue (D.continuous_uncurry_objective v) D.feasible_nonempty + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible) + (‖D.reward‖ + D.discount * ‖v‖) + fun s => by simpa [Real.norm_eq_abs] using D.abs_bellmanFn_le v s + +@[simp] +theorem bellman_apply (v : S →ᵇ ℝ) (s : S) : + D.bellman v s = maxValue (D.objective v) D.feasible s := rfl + +/-- **The Bellman operator satisfies Blackwell's sufficient conditions.** Monotonicity +holds because a larger continuation value raises the objective at every action; +discounting holds because the continuation value enters multiplied by `β`. -/ +theorem blackwell : Blackwell D.discount D.bellman where + monotone := by + intro v w hvw s + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + have key : ∀ a ∈ D.feasible s, D.objective v s a ≤ D.bellmanFn w s := by + intro a ha + refine le_trans ?_ (D.le_bellmanFn w ha) + have hx : v (D.transition (s, a)) ≤ w (D.transition (s, a)) := by + simpa using hvw (D.transition (s, a)) + simp only [objective] + nlinarith + exact D.bellmanFn_le v key + discounting := by + intro v c _ s + have key : ∀ a ∈ D.feasible s, D.objective (v + const S c) s a + ≤ D.bellmanFn v s + D.discount * c := by + intro a ha + have h : D.objective (v + const S c) s a = D.objective v s a + D.discount * c := by + simp only [objective, coe_add, const_apply, Pi.add_apply] + ring + rw [h] + have := D.le_bellmanFn v (s := s) ha + linarith + exact D.bellmanFn_le (v + const S c) key + +/-- The supremum defining the Bellman operator is **attained**: in every state there is an +optimal action. This is what makes a greedy policy well defined. -/ +theorem exists_optimal_action (v : S →ᵇ ℝ) (s : S) : + ∃ a ∈ D.feasible s, D.bellman v s = D.objective v s a ∧ + ∀ b ∈ D.feasible s, D.objective v s b ≤ D.objective v s a := by + obtain ⟨a, ha, hmax⟩ := + argmax_nonempty (D.continuous_uncurry_objective v) (D.feasible_nonempty s) + (D.isCompact_feasible s) + exact ⟨a, ha, + maxValue_eq (D.continuous_uncurry_objective v) (D.isCompact_feasible s) ⟨ha, hmax⟩, + fun b hb => isMaxOn_iff.mp hmax b hb⟩ + +/-- The maximiser correspondence of the one-period problem is upper hemicontinuous, by the +second half of Berge's theorem. -/ +theorem upperHemicontinuous_argmax (v : S →ᵇ ℝ) : + UpperHemicontinuous (argmax (D.objective v) D.feasible) := fun s => + upperHemicontinuousAt_argmax (D.continuous_uncurry_objective v) D.feasible_nonempty + D.isCompact_feasible (D.upperHemicontinuous_feasible s) (D.lowerHemicontinuous_feasible s) + +section Monotone + +variable [Preorder S] + +/-- The Bellman operator preserves monotonicity when the feasible set grows with the state, +the reward rises with it, and the law of motion is monotone in it. -/ +theorem monotone_bellman + (hfeas : ∀ s s', s ≤ s' → D.feasible s ⊆ D.feasible s') + (hreward : ∀ a, Monotone fun s => D.reward (s, a)) + (htrans : ∀ a, Monotone fun s => D.transition (s, a)) + (v : S →ᵇ ℝ) (hv : Monotone ⇑v) : Monotone ⇑(D.bellman v) := by + intro s s' hss + refine D.bellmanFn_le v fun a ha => ?_ + refine le_trans ?_ (D.le_bellmanFn v (hfeas s s' hss ha)) + have h₁ : D.reward (s, a) ≤ D.reward (s', a) := hreward a hss + have h₂ : v (D.transition (s, a)) ≤ v (D.transition (s', a)) := hv (htrans a hss) + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + simp only [objective] + nlinarith + +end Monotone + +section Concave + +variable [AddCommMonoid S] [Module ℝ S] [AddCommMonoid A] [Module ℝ A] + +/-- The Bellman operator preserves concavity on a convex set `s`, given that the feasible +graph is convex over `s`, the reward is concave along it, the law of motion is affine, and +`s` is forward invariant. + +The mixed action is feasible at the mixed state, so it is *available*; the value at the +mixed state is at least what it delivers, which by concavity of the reward and of the +continuation value is at least the mixture of the two optimal values. -/ +theorem concaveOn_bellman {s : Set S} (hs : Convex ℝ s) + (hgraph : ∀ x ∈ s, ∀ y ∈ s, ∀ ax ∈ D.feasible x, ∀ ay ∈ D.feasible y, + ∀ θ φ : ℝ, 0 ≤ θ → 0 ≤ φ → θ + φ = 1 → + θ • ax + φ • ay ∈ D.feasible (θ • x + φ • y)) + (hreward : ∀ x ∈ s, ∀ y ∈ s, ∀ ax ∈ D.feasible x, ∀ ay ∈ D.feasible y, + ∀ θ φ : ℝ, 0 ≤ θ → 0 ≤ φ → θ + φ = 1 → + θ • D.reward (x, ax) + φ • D.reward (y, ay) + ≤ D.reward (θ • x + φ • y, θ • ax + φ • ay)) + (htrans : ∀ (x y : S) (ax ay : A) (θ φ : ℝ), + D.transition (θ • x + φ • y, θ • ax + φ • ay) + = θ • D.transition (x, ax) + φ • D.transition (y, ay)) + (hinv : ∀ x ∈ s, ∀ a ∈ D.feasible x, D.transition (x, a) ∈ s) + (v : S →ᵇ ℝ) (hv : ConcaveOn ℝ s ⇑v) : ConcaveOn ℝ s ⇑(D.bellman v) := by + refine ⟨hs, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + obtain ⟨ax, hax, heqx, -⟩ := D.exists_optimal_action v x + obtain ⟨ay, hay, heqy, -⟩ := D.exists_optimal_action v y + have hle := D.le_bellmanFn v (hgraph x hx y hy ax hax ay hay θ φ hθ hφ hθφ) + have hr := hreward x hx y hy ax hax ay hay θ φ hθ hφ hθφ + have hvg := hv.2 (hinv x hx ax hax) (hinv y hy ay hay) hθ hφ hθφ + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + have hkey : θ • D.objective v x ax + φ • D.objective v y ay + ≤ D.objective v (θ • x + φ • y) (θ • ax + φ • ay) := by + simp only [objective] + rw [htrans] + have hscaled := mul_le_mul_of_nonneg_left hvg hβ + simp only [smul_eq_mul] at hr hscaled ⊢ + linarith + rw [heqx, heqy] + exact le_trans hkey hle + +end Concave + +section ValueFunction + +/-- The **value function** of the program: the unique fixed point of its Bellman +operator. -/ +noncomputable def valueFunction : S →ᵇ ℝ := + D.blackwell.valueFunction D.discount_lt_one + +/-- The value function satisfies the **Bellman equation**. -/ +theorem bellman_valueFunction : D.bellman D.valueFunction = D.valueFunction := + D.blackwell.isFixedPt_valueFunction D.discount_lt_one + +/-- The Bellman equation has **no other** bounded continuous solution. -/ +theorem eq_valueFunction {v : S →ᵇ ℝ} (hv : D.bellman v = v) : v = D.valueFunction := + D.blackwell.eq_valueFunction D.discount_lt_one hv + +/-- **Value function iteration converges** to the value function from any starting +guess. -/ +theorem tendsto_iterate_valueFunction (v : S →ᵇ ℝ) : + Tendsto (fun n => D.bellman^[n] v) atTop (𝓝 D.valueFunction) := + D.blackwell.tendsto_iterate_valueFunction D.discount_lt_one v + +/-- The geometric error bound for value function iteration on a dynamic program. -/ +theorem norm_iterate_sub_valueFunction_le (v : S →ᵇ ℝ) (n : ℕ) : + ‖D.bellman^[n] v - D.valueFunction‖ + ≤ (D.discount : ℝ) ^ n / (1 - D.discount) * ‖D.bellman v - v‖ := + D.blackwell.norm_iterate_sub_valueFunction_le D.discount_lt_one v n + +/-- In every state, the value function is attained by some feasible action: the **optimal +policy** exists, and the value function is the reward it collects plus the discounted value +of the state it leads to. -/ +theorem exists_optimal_policy (s : S) : + ∃ a ∈ D.feasible s, D.valueFunction s + = D.reward (s, a) + D.discount * D.valueFunction (D.transition (s, a)) := by + obtain ⟨a, ha, heq, -⟩ := D.exists_optimal_action D.valueFunction s + rw [D.bellman_valueFunction] at heq + exact ⟨a, ha, heq⟩ + +/-- **The value function is increasing in the state** when the feasible set grows with the +state, the reward rises with it, and the law of motion is monotone in it. -/ +theorem monotone_valueFunction [Preorder S] + (hfeas : ∀ s s', s ≤ s' → D.feasible s ⊆ D.feasible s') + (hreward : ∀ a, Monotone fun s => D.reward (s, a)) + (htrans : ∀ a, Monotone fun s => D.transition (s, a)) : + Monotone ⇑D.valueFunction := + D.blackwell.monotone_valueFunction D.discount_lt_one + fun v hv => D.monotone_bellman hfeas hreward htrans v hv + +/-- **The value function is concave on `s`** under those conditions. -/ +theorem concaveOn_valueFunction [AddCommMonoid S] [Module ℝ S] [AddCommMonoid A] [Module ℝ A] + {s : Set S} (hs : Convex ℝ s) + (hgraph : ∀ x ∈ s, ∀ y ∈ s, ∀ ax ∈ D.feasible x, ∀ ay ∈ D.feasible y, + ∀ θ φ : ℝ, 0 ≤ θ → 0 ≤ φ → θ + φ = 1 → + θ • ax + φ • ay ∈ D.feasible (θ • x + φ • y)) + (hreward : ∀ x ∈ s, ∀ y ∈ s, ∀ ax ∈ D.feasible x, ∀ ay ∈ D.feasible y, + ∀ θ φ : ℝ, 0 ≤ θ → 0 ≤ φ → θ + φ = 1 → + θ • D.reward (x, ax) + φ • D.reward (y, ay) + ≤ D.reward (θ • x + φ • y, θ • ax + φ • ay)) + (htrans : ∀ (x y : S) (ax ay : A) (θ φ : ℝ), + D.transition (θ • x + φ • y, θ • ax + φ • ay) + = θ • D.transition (x, ax) + φ • D.transition (y, ay)) + (hinv : ∀ x ∈ s, ∀ a ∈ D.feasible x, D.transition (x, a) ∈ s) : + ConcaveOn ℝ s ⇑D.valueFunction := + Blackwell.concaveOn_valueFunction hs D.blackwell D.discount_lt_one + fun v hv => D.concaveOn_bellman hs hgraph hreward htrans hinv v hv + +end ValueFunction + +end DynamicProgram + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/DynamicProgramming/Blackwell.lean b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Blackwell.lean new file mode 100644 index 000000000..6aaf27f93 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Blackwell.lean @@ -0,0 +1,477 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Topology.ContinuousMap.Bounded.Normed +public import Mathlib.Topology.MetricSpace.Contracting + +/-! +# Blackwell's sufficient conditions + +An operator on bounded continuous functions that is *monotone* and *discounts* is a +contraction. This is the standard route to showing that a Bellman operator has a unique +fixed point: verifying monotonicity and discounting is elementary, whereas verifying the +contraction property directly is not. + +The setting is the space `S →ᵇ ℝ` of bounded continuous real-valued functions on a state +space `S`, with the supremum norm. It is already a complete metric space, which is what +the Banach fixed point theorem needs. + +## Main definitions + +* `LeanEconomics.Blackwell β T` : `T` is monotone and discounts at rate `β`. +* `LeanEconomics.Blackwell.valueFunction` : the unique fixed point of `T`. + +## Main results + +* `LeanEconomics.Blackwell.contractingWith` : Blackwell's conditions imply `T` is a + `β`-contraction. +* `LeanEconomics.Blackwell.isFixedPt_valueFunction`, + `LeanEconomics.Blackwell.eq_valueFunction` : the value function is the unique solution + of the fixed point equation `T v = v`. +* `LeanEconomics.Blackwell.tendsto_iterate_valueFunction` : value function iteration + converges to it, from any starting guess. +* `LeanEconomics.Blackwell.norm_iterate_sub_valueFunction_le` : the geometric error bound + `‖Tⁿ v - v*‖ ≤ βⁿ / (1 - β) * ‖T v - v‖`, which is the stopping rule used in practice. + +## Examples + +* `LeanEconomics.Blackwell.affine` : the affine operator `v ↦ f + β • v` satisfies the + conditions. This is the degenerate case of a Bellman operator in which the agent has no + choice to make, and it witnesses that the hypotheses are consistent. + +## References + +* Stokey, Lucas and Prescott, *Recursive Methods in Economic Dynamics*. +* Blackwell, *Discounted dynamic programming*, Ann. Math. Statist. 36 (1965). +-/ + +@[expose] public section + +open scoped NNReal +open Filter Topology + +namespace BoundedContinuousFunction + +variable {S : Type*} [TopologicalSpace S] + +/-- Any bounded continuous function is dominated by any other one shifted up by the +distance between them. -/ +theorem le_add_const_norm_sub (v w : S →ᵇ ℝ) : v ≤ w + const S ‖v - w‖ := by + rw [← sub_le_iff_le_add'] + exact sub_nonneg.mp (norm_sub_nonneg (v - w)) + +end BoundedContinuousFunction + +namespace LeanEconomics + +open BoundedContinuousFunction + +variable {S : Type*} [TopologicalSpace S] + +/-- **Blackwell's sufficient conditions** for an operator on bounded continuous functions +to be a contraction of modulus `β`. + +`monotone` says a pointwise larger continuation value cannot lower the operator's value; +`discounting` says that raising the continuation value everywhere by a constant `c` raises +the operator's value by at most `β * c`. For a Bellman operator both are immediate: the +first because the objective is increasing in the continuation value, the second because +the continuation value enters multiplied by the discount factor. -/ +structure Blackwell (β : ℝ≥0) (T : (S →ᵇ ℝ) → (S →ᵇ ℝ)) : Prop where + /-- A pointwise larger continuation value gives a pointwise larger image. -/ + monotone : Monotone T + /-- Adding a constant `c ≥ 0` to the continuation value raises the image by at most + `β * c`. -/ + discounting : ∀ (v : S →ᵇ ℝ) (c : ℝ), 0 ≤ c → T (v + const S c) ≤ T v + const S (β * c) + +namespace Blackwell + +variable {β : ℝ≥0} {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} + +/-- The one-sided bound at the heart of Blackwell's argument: `T v` cannot exceed `T w` +by more than `β` times the distance between `v` and `w`. -/ +theorem apply_le_add_const (h : Blackwell β T) (v w : S →ᵇ ℝ) : + T v ≤ T w + const S (β * ‖v - w‖) := + (h.monotone (le_add_const_norm_sub v w)).trans (h.discounting w _ (norm_nonneg _)) + +/-- Blackwell's conditions make `T` Lipschitz with constant `β` in the supremum norm. -/ +theorem norm_sub_le (h : Blackwell β T) (v w : S →ᵇ ℝ) : ‖T v - T w‖ ≤ β * ‖v - w‖ := by + rw [norm_le (by positivity)] + intro x + have h₁ : T v x ≤ T w x + β * ‖v - w‖ := by simpa using h.apply_le_add_const v w x + have h₂ : T w x ≤ T v x + β * ‖w - v‖ := by simpa using h.apply_le_add_const w v x + rw [norm_sub_rev w v] at h₂ + rw [BoundedContinuousFunction.sub_apply, Real.norm_eq_abs, abs_le] + constructor <;> linarith + +theorem lipschitzWith (h : Blackwell β T) : LipschitzWith β T := + LipschitzWith.of_dist_le_mul fun v w => by + simpa only [dist_eq_norm] using h.norm_sub_le v w + +/-- **Blackwell's theorem**: a monotone operator that discounts at rate `β < 1` is a +contraction of modulus `β`. -/ +theorem contractingWith (h : Blackwell β T) (hβ : β < 1) : ContractingWith β T := + ⟨hβ, h.lipschitzWith⟩ + +section ValueFunction + +variable (h : Blackwell β T) (hβ : β < 1) + +/-- The **value function**: the unique fixed point of an operator satisfying Blackwell's +conditions, obtained from the Banach fixed point theorem. -/ +noncomputable def valueFunction : S →ᵇ ℝ := + ContractingWith.fixedPoint T (h.contractingWith hβ) + +/-- The value function solves the fixed point equation. -/ +theorem isFixedPt_valueFunction : T (h.valueFunction hβ) = h.valueFunction hβ := + (h.contractingWith hβ).fixedPoint_isFixedPt + +/-- The value function is the *only* solution of the fixed point equation. -/ +theorem eq_valueFunction {v : S →ᵇ ℝ} (hv : T v = v) : v = h.valueFunction hβ := + (h.contractingWith hβ).fixedPoint_unique hv + +/-- **Value function iteration converges**, from any starting guess. -/ +theorem tendsto_iterate_valueFunction (v : S →ᵇ ℝ) : + Tendsto (fun n => T^[n] v) atTop (𝓝 (h.valueFunction hβ)) := + (h.contractingWith hβ).tendsto_iterate_fixedPoint v + +/-- The geometric error bound for value function iteration: after `n` iterations from a +guess `v`, the distance to the value function is at most `βⁿ / (1 - β)` times the size of +the first update. This is the a priori bound behind the usual stopping rule. -/ +theorem norm_iterate_sub_valueFunction_le (v : S →ᵇ ℝ) (n : ℕ) : + ‖T^[n] v - h.valueFunction hβ‖ ≤ (β : ℝ) ^ n / (1 - β) * ‖T v - v‖ := by + have key := (h.contractingWith hβ).apriori_dist_iterate_fixedPoint_le v n + rw [dist_eq_norm, dist_eq_norm, norm_sub_rev v (T v)] at key + calc ‖T^[n] v - h.valueFunction hβ‖ ≤ ‖T v - v‖ * (β : ℝ) ^ n / (1 - β) := key + _ = (β : ℝ) ^ n / (1 - β) * ‖T v - v‖ := by ring + +end ValueFunction + +/-- The affine operator `v ↦ f + β • v`, a dynamic program with a single feasible action, +satisfies Blackwell's conditions. -/ +theorem affine (f : S →ᵇ ℝ) (β : ℝ≥0) : + Blackwell β (fun v => f + (β : ℝ) • v) where + monotone := by + intro v w hvw x + have hx : v x ≤ w x := by simpa using hvw x + have hβ : (0 : ℝ) ≤ β := β.coe_nonneg + have : f x + (β : ℝ) * v x ≤ f x + (β : ℝ) * w x := by nlinarith + simpa using this + discounting := by + intro v c _ + refine le_of_eq ?_ + ext x + simp only [coe_add, coe_smul, const_apply, Pi.add_apply, smul_eq_mul] + ring + +end Blackwell + +/-- **A dominated operator has a dominated fixed point.** If one Bellman operator never returns +more than another, its value function never does either. + +This is the workhorse of comparative statics: to compare two economies one only has to compare +their operators at a COMMON continuation value, which is a one-period calculation, and the +comparison of the infinite-horizon values follows. The two need not share a discount factor. +No policy comparison, no differentiability, +nothing about argmaxes -- the whole content is that value function iteration started from the +same place stays ordered. -/ +theorem Blackwell.valueFunction_le {β₁ β₂ : ℝ≥0} {T₁ T₂ : (S →ᵇ ℝ) → (S →ᵇ ℝ)} + (h₁ : Blackwell β₁ T₁) (h₂ : Blackwell β₂ T₂) (hβ₁ : β₁ < 1) (hβ₂ : β₂ < 1) + (hle : ∀ v : S →ᵇ ℝ, ⇑(T₁ v) ≤ ⇑(T₂ v)) (s : S) : + h₁.valueFunction hβ₁ s ≤ h₂.valueFunction hβ₂ s := by + have hiter : ∀ n : ℕ, ⇑(T₁^[n] (0 : S →ᵇ ℝ)) ≤ ⇑(T₂^[n] (0 : S →ᵇ ℝ)) := by + intro n + induction n with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply', Function.iterate_succ_apply'] + exact le_trans (hle _) (h₂.monotone ih) + have hl₁ := h₁.tendsto_iterate_valueFunction hβ₁ (0 : S →ᵇ ℝ) + have hl₂ := h₂.tendsto_iterate_valueFunction hβ₂ (0 : S →ᵇ ℝ) + have hp₁ : Filter.Tendsto (fun n => T₁^[n] (0 : S →ᵇ ℝ) s) Filter.atTop + (𝓝 (h₁.valueFunction hβ₁ s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp hl₁ + have hp₂ : Filter.Tendsto (fun n => T₂^[n] (0 : S →ᵇ ℝ) s) Filter.atTop + (𝓝 (h₂.valueFunction hβ₂ s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp hl₂ + exact le_of_tendsto_of_tendsto hp₁ hp₂ (Filter.Eventually.of_forall fun n => hiter n s) + +/-! ### Monotonicity of the value function + +If the operator preserves monotone functions then so does its fixed point, because the +monotone functions are a *closed* subset of `S →ᵇ ℝ` and value function iteration converges +in norm. Nothing about the fixed point itself is needed, only that the property is closed +and preserved. This is the standard route to comparative statics. -/ + +section Monotone + +variable [Preorder S] + +/-- The monotone bounded continuous functions form a closed set: a supremum-norm limit of +monotone functions is monotone, since evaluation at a point is continuous. -/ +theorem isClosed_monotone : IsClosed {v : S →ᵇ ℝ | Monotone ⇑v} := by + have heq : {v : S →ᵇ ℝ | Monotone ⇑v} + = ⋂ (x : S) (y : S) (_ : x ≤ y), {v : S →ᵇ ℝ | v x ≤ v y} := by + ext v; simp [Monotone] + rw [heq] + refine isClosed_iInter fun x => isClosed_iInter fun y => isClosed_iInter fun _ => ?_ + exact isClosed_le (BoundedContinuousFunction.lipschitz_eval_const x).continuous + (BoundedContinuousFunction.lipschitz_eval_const y).continuous + +/-- **The value function is monotone** whenever the operator preserves monotonicity. -/ +theorem Blackwell.monotone_valueFunction {β : ℝ≥0} {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} + (h : Blackwell β T) (hβ : β < 1) + (hT : ∀ v : S →ᵇ ℝ, Monotone ⇑v → Monotone ⇑(T v)) : + Monotone ⇑(h.valueFunction hβ) := by + have hiter : ∀ n : ℕ, Monotone ⇑(T^[n] (0 : S →ᵇ ℝ)) := by + intro n + induction n with + | zero => intro x y _; simp + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ ih + exact isClosed_monotone.mem_of_tendsto (h.tendsto_iterate_valueFunction hβ 0) + (Filter.Eventually.of_forall hiter) + +end Monotone + +/-! ### Concavity of the value function + +The same closed-set argument as for monotonicity, and the reason it is wanted is the agent +distribution. Defining a distribution over states needs the optimal policy to be a +*function*, and `exists_optimal_action` only gives existence of a maximiser. Uniqueness +comes from strict concavity of the objective in the action, which needs the continuation +value to be concave -- that is, the value function. So this is the first link in the chain +from the value function to the stationary distribution. -/ + +section Concave + +variable {S : Type*} [TopologicalSpace S] [AddCommMonoid S] [Module ℝ S] + +/-- The functions concave on a fixed convex set form a closed set. + +Stated on a set rather than the whole space deliberately. The household models clamp assets +with `max 0 a`, which is convex and so breaks concavity globally -- but `max 0 a = a` on +`[0, ā]`, so concavity does hold on the compact convex region the economics lives in, and +that is the region the agent distribution will be supported on. -/ +theorem isClosed_concaveOn {s : Set S} (hs : Convex ℝ s) : + IsClosed {v : S →ᵇ ℝ | ConcaveOn ℝ s ⇑v} := by + have heq : {v : S →ᵇ ℝ | ConcaveOn ℝ s ⇑v} + = ⋂ (p : S × S × ℝ × ℝ) (_ : p.1 ∈ s ∧ p.2.1 ∈ s ∧ 0 ≤ p.2.2.1 ∧ 0 ≤ p.2.2.2 ∧ + p.2.2.1 + p.2.2.2 = 1), + {v : S →ᵇ ℝ | p.2.2.1 • v p.1 + p.2.2.2 • v p.2.1 + ≤ v (p.2.2.1 • p.1 + p.2.2.2 • p.2.1)} := by + ext v + simp only [Set.mem_ofPred_eq, Set.mem_iInter] + constructor + · rintro ⟨-, hv⟩ p ⟨hx, hy, ha, hb, hab⟩ + exact hv hx hy ha hb hab + · intro hv + exact ⟨hs, fun x hx y hy a b ha hb hab => hv (x, y, a, b) ⟨hx, hy, ha, hb, hab⟩⟩ + rw [heq] + refine isClosed_iInter fun p => isClosed_iInter fun _ => ?_ + exact isClosed_le + ((((BoundedContinuousFunction.lipschitz_eval_const p.1).continuous).const_smul p.2.2.1).add + (((BoundedContinuousFunction.lipschitz_eval_const p.2.1).continuous).const_smul p.2.2.2)) + (BoundedContinuousFunction.lipschitz_eval_const + (p.2.2.1 • p.1 + p.2.2.2 • p.2.1)).continuous + +/-- **The value function is concave on `s`** whenever the operator preserves concavity +on `s`. -/ +theorem Blackwell.concaveOn_valueFunction {β : ℝ≥0} {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} {s : Set S} + (hs : Convex ℝ s) (h : Blackwell β T) (hβ : β < 1) + (hT : ∀ v : S →ᵇ ℝ, ConcaveOn ℝ s ⇑v → ConcaveOn ℝ s ⇑(T v)) : + ConcaveOn ℝ s ⇑(h.valueFunction hβ) := by + have hiter : ∀ n : ℕ, ConcaveOn ℝ s ⇑(T^[n] (0 : S →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact ⟨hs, fun x _ y _ a b _ _ _ => by simp⟩ + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ ih + exact (isClosed_concaveOn hs).mem_of_tendsto (h.tendsto_iterate_valueFunction hβ 0) + (Filter.Eventually.of_forall hiter) + +end Concave + +/-! ### Concavity along slices + +A stochastic household's state is a pair `(assets, income state)`, and that is not a module +over `ℝ` -- there is no sensible convex combination of income states. So concavity cannot be +stated on the state space itself. What is wanted, and what is true, is concavity in assets +for each fixed income state, which is concavity along a family of slices. It is still a +closed condition, so the same argument applies. -/ + +section ConcaveSlice + +variable {ι : Type*} {E : Type*} [AddCommMonoid E] [Module ℝ E] + +/-- Concavity along every slice is a closed condition. -/ +theorem isClosed_forall_concaveOn {s : Set E} (hs : Convex ℝ s) (g : ι → E → S) : + IsClosed {v : S →ᵇ ℝ | ∀ i, ConcaveOn ℝ s fun e => v (g i e)} := by + have heq : {v : S →ᵇ ℝ | ∀ i, ConcaveOn ℝ s fun e => v (g i e)} + = ⋂ (p : ι × E × E × ℝ × ℝ) + (_ : p.2.1 ∈ s ∧ p.2.2.1 ∈ s ∧ 0 ≤ p.2.2.2.1 ∧ 0 ≤ p.2.2.2.2 ∧ + p.2.2.2.1 + p.2.2.2.2 = 1), + {v : S →ᵇ ℝ | p.2.2.2.1 • v (g p.1 p.2.1) + p.2.2.2.2 • v (g p.1 p.2.2.1) + ≤ v (g p.1 (p.2.2.2.1 • p.2.1 + p.2.2.2.2 • p.2.2.1))} := by + ext v + simp only [Set.mem_ofPred_eq, Set.mem_iInter] + constructor + · intro hv p ⟨hx, hy, ha, hb, hab⟩ + exact (hv p.1).2 hx hy ha hb hab + · intro hv i + exact ⟨hs, fun x hx y hy a b ha hb hab => hv (i, x, y, a, b) ⟨hx, hy, ha, hb, hab⟩⟩ + rw [heq] + refine isClosed_iInter fun p => isClosed_iInter fun _ => ?_ + exact isClosed_le + (((BoundedContinuousFunction.lipschitz_eval_const + (g p.1 p.2.1)).continuous.const_smul p.2.2.2.1).add + ((BoundedContinuousFunction.lipschitz_eval_const + (g p.1 p.2.2.1)).continuous.const_smul p.2.2.2.2)) + (BoundedContinuousFunction.lipschitz_eval_const + (g p.1 (p.2.2.2.1 • p.2.1 + p.2.2.2.2 • p.2.2.1))).continuous + +/-- **The value function is concave along every slice** whenever the operator preserves +that. -/ +theorem Blackwell.forall_concaveOn_valueFunction {β : ℝ≥0} {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} + {s : Set E} (hs : Convex ℝ s) (g : ι → E → S) (h : Blackwell β T) (hβ : β < 1) + (hT : ∀ v : S →ᵇ ℝ, (∀ i, ConcaveOn ℝ s fun e => v (g i e)) → + ∀ i, ConcaveOn ℝ s fun e => (T v) (g i e)) : + ∀ i, ConcaveOn ℝ s fun e => (h.valueFunction hβ) (g i e) := by + have hiter : ∀ n : ℕ, ∀ i, ConcaveOn ℝ s fun e => (T^[n] (0 : S →ᵇ ℝ)) (g i e) := by + intro n + induction n with + | zero => exact fun _ => ⟨hs, fun x _ y _ a b _ _ _ => by simp⟩ + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ ih + exact (isClosed_forall_concaveOn hs g).mem_of_tendsto (h.tendsto_iterate_valueFunction hβ 0) + (Filter.Eventually.of_forall hiter) + +end ConcaveSlice + +section Lipschitz + +/-! +### Lipschitz value functions + +The third closed class, after monotone and concave. A value function that is Lipschitz in one +coordinate is what lets a marginal argument run without derivatives: it bounds the marginal +value of that coordinate uniformly, which is what a corner condition needs. + +As with concavity, the property is stated along a family of slices `g i`, since the state +space need not be a metric space in the coordinate of interest. +-/ + +variable {S : Type*} [TopologicalSpace S] {ι : Type*} + +theorem isClosed_forall_lipschitzOn (t : Set ℝ) (L : ℝ) (g : ι → ℝ → S) : + IsClosed {v : S →ᵇ ℝ | ∀ i, ∀ x ∈ t, ∀ y ∈ t, |v (g i x) - v (g i y)| ≤ L * |x - y|} := by + have heq : {v : S →ᵇ ℝ | ∀ i, ∀ x ∈ t, ∀ y ∈ t, |v (g i x) - v (g i y)| ≤ L * |x - y|} + = ⋂ (p : ι × ℝ × ℝ) (_ : p.2.1 ∈ t ∧ p.2.2 ∈ t), + {v : S →ᵇ ℝ | |v (g p.1 p.2.1) - v (g p.1 p.2.2)| ≤ L * |p.2.1 - p.2.2|} := by + ext v + simp only [Set.mem_ofPred_eq, Set.mem_iInter] + exact ⟨fun hv p hp => hv p.1 p.2.1 hp.1 p.2.2 hp.2, fun hv i x hx y hy => hv (i, x, y) ⟨hx, hy⟩⟩ + rw [heq] + refine isClosed_iInter fun p => isClosed_iInter fun _ => ?_ + exact isClosed_le + (((BoundedContinuousFunction.lipschitz_eval_const (g p.1 p.2.1)).continuous.sub + (BoundedContinuousFunction.lipschitz_eval_const (g p.1 p.2.2)).continuous).abs) + continuous_const + +/-- **The value function is Lipschitz along the slices** whenever the operator preserves that +property. The nonnegativity of `L` is what makes the zero function a valid starting point for +the iteration. -/ +theorem Blackwell.forall_lipschitzOn_valueFunction {β : ℝ≥0} {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} + (h : Blackwell β T) (hβ : β < 1) {t : Set ℝ} {L : ℝ} (hL : 0 ≤ L) {g : ι → ℝ → S} + (hT : ∀ v : S →ᵇ ℝ, + (∀ i, ∀ x ∈ t, ∀ y ∈ t, |v (g i x) - v (g i y)| ≤ L * |x - y|) → + ∀ i, ∀ x ∈ t, ∀ y ∈ t, |T v (g i x) - T v (g i y)| ≤ L * |x - y|) : + ∀ i, ∀ x ∈ t, ∀ y ∈ t, + |h.valueFunction hβ (g i x) - h.valueFunction hβ (g i y)| ≤ L * |x - y| := by + have hiter : ∀ n : ℕ, ∀ i, ∀ x ∈ t, ∀ y ∈ t, + |(T^[n] (0 : S →ᵇ ℝ)) (g i x) - (T^[n] (0 : S →ᵇ ℝ)) (g i y)| ≤ L * |x - y| := by + intro n + induction n with + | zero => + intro i x _ y _ + simpa using mul_nonneg hL (abs_nonneg _) + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ ih + exact isClosed_forall_lipschitzOn t L g |>.mem_of_tendsto + (h.tendsto_iterate_valueFunction hβ 0) (Filter.Eventually.of_forall hiter) + +end Lipschitz + +section IncreasingDifferences + +/-! +### Increasing differences + +The fourth closed class, after monotone, concave and Lipschitz. + +A function of two real coordinates has increasing differences when raising the second +coordinate raises the increment in the first. For a value function whose coordinates are +assets and the interest rate, that says the marginal value of wealth rises with the rate -- +and that is the derivative-free form of the condition driving Light (2018) Theorem 1, where +it appears as `f'(·, R₂) ≥ f'(·, R₁)` and is obtained from the envelope theorem. + +Stating it as increments rather than derivatives is what makes it expressible here at all, +since nothing in this development differentiates anything. Its natural home is the augmented +programme of `Models/IncomeFluctuationAugmented.lean`, where the rate is a state coordinate, +so the property is about a single function rather than a family. + +Whether the Bellman operator preserves it is a separate question and is NOT settled here; +Light's argument for the derivative version compares different asset levels across rates via +a rescaling, so it does not transcribe. +-/ + +variable {S : Type*} [TopologicalSpace S] {ι : Type*} + +theorem isClosed_forall_increasingDifferences (s t : Set ℝ) (g : ι → ℝ → ℝ → S) : + IsClosed {v : S →ᵇ ℝ | ∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + v (g i a₂ r₁) - v (g i a₁ r₁) ≤ v (g i a₂ r₂) - v (g i a₁ r₂)} := by + have heq : {v : S →ᵇ ℝ | ∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + v (g i a₂ r₁) - v (g i a₁ r₁) ≤ v (g i a₂ r₂) - v (g i a₁ r₂)} + = ⋂ (p : ι × ℝ × ℝ × ℝ × ℝ) + (_ : p.2.1 ∈ s ∧ p.2.2.1 ∈ s ∧ p.2.2.2.1 ∈ t ∧ p.2.2.2.2 ∈ t ∧ + p.2.1 ≤ p.2.2.1 ∧ p.2.2.2.1 ≤ p.2.2.2.2), + {v : S →ᵇ ℝ | + v (g p.1 p.2.2.1 p.2.2.2.1) - v (g p.1 p.2.1 p.2.2.2.1) + ≤ v (g p.1 p.2.2.1 p.2.2.2.2) - v (g p.1 p.2.1 p.2.2.2.2)} := by + ext v + simp only [Set.mem_ofPred_eq, Set.mem_iInter] + exact ⟨fun hv p hp => hv p.1 p.2.1 hp.1 p.2.2.1 hp.2.1 p.2.2.2.1 hp.2.2.1 p.2.2.2.2 hp.2.2.2.1 + hp.2.2.2.2.1 hp.2.2.2.2.2, + fun hv i a₁ h1 a₂ h2 r₁ h3 r₂ h4 h5 h6 => hv (i, a₁, a₂, r₁, r₂) ⟨h1, h2, h3, h4, h5, h6⟩⟩ + rw [heq] + refine isClosed_iInter fun p => isClosed_iInter fun _ => ?_ + exact isClosed_le + ((BoundedContinuousFunction.lipschitz_eval_const _).continuous.sub + (BoundedContinuousFunction.lipschitz_eval_const _).continuous) + ((BoundedContinuousFunction.lipschitz_eval_const _).continuous.sub + (BoundedContinuousFunction.lipschitz_eval_const _).continuous) + +/-- **The value function has increasing differences** whenever the operator preserves the +property. The zero function has it trivially, which is what lets the iteration start. -/ +theorem Blackwell.forall_increasingDifferences_valueFunction {β : ℝ≥0} + {T : (S →ᵇ ℝ) → (S →ᵇ ℝ)} (h : Blackwell β T) (hβ : β < 1) {s t : Set ℝ} {g : ι → ℝ → ℝ → S} + (hT : ∀ v : S →ᵇ ℝ, + (∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + v (g i a₂ r₁) - v (g i a₁ r₁) ≤ v (g i a₂ r₂) - v (g i a₁ r₂)) → + ∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + T v (g i a₂ r₁) - T v (g i a₁ r₁) ≤ T v (g i a₂ r₂) - T v (g i a₁ r₂)) : + ∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + h.valueFunction hβ (g i a₂ r₁) - h.valueFunction hβ (g i a₁ r₁) + ≤ h.valueFunction hβ (g i a₂ r₂) - h.valueFunction hβ (g i a₁ r₂) := by + have hiter : ∀ n : ℕ, ∀ i, ∀ a₁ ∈ s, ∀ a₂ ∈ s, ∀ r₁ ∈ t, ∀ r₂ ∈ t, a₁ ≤ a₂ → r₁ ≤ r₂ → + (T^[n] (0 : S →ᵇ ℝ)) (g i a₂ r₁) - (T^[n] (0 : S →ᵇ ℝ)) (g i a₁ r₁) + ≤ (T^[n] (0 : S →ᵇ ℝ)) (g i a₂ r₂) - (T^[n] (0 : S →ᵇ ℝ)) (g i a₁ r₂) := by + intro n + induction n with + | zero => intro i a₁ _ a₂ _ r₁ _ r₂ _ _ _; simp + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ ih + exact isClosed_forall_increasingDifferences s t g |>.mem_of_tendsto + (h.tendsto_iterate_valueFunction hβ 0) (Filter.Eventually.of_forall hiter) + +end IncreasingDifferences + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/DynamicProgramming/Extended.lean b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Extended.lean new file mode 100644 index 000000000..62a3af300 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Extended.lean @@ -0,0 +1,369 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Bellman +public import LeanPool.AiyagariEquilibrium.Topology.BergeEReal + +/-! +# Dynamic programs with extended-real rewards + +The reward may take the value `-∞`, as `log c` does at zero consumption. The value function +stays real-valued and bounded, so the contraction theory applies unchanged. + +## What this is for + +Every model in this library that handles utility unbounded below does it with a *floor*: +replace `u` by `max floor u`, which is bounded, and then prove separately that the floor +never binds at an optimum. That argument has to be redone per model, and it fails outright +once the state space is unbounded, because the bound on the value function that it needs is +itself inflated by the floor. Admitting `-∞` removes the device rather than working around +it. + +## Why the value function is still bounded + +Two hypotheses do it, and both are economically ordinary: + +* `reward_le` bounds the reward above on the feasible graph. Only there: off it the reward + may do anything, which matters because an action outside the budget set typically has no + sensible reward at all. +* `select` is a continuous feasible policy whose reward is bounded below by `rewardMin`. + This says the agent always has *some* decent option -- it never faces a situation where + every available action is catastrophic. Without it the value would be `-∞` and there + would be nothing to compute. + +Between them the supremum sits in `[rewardMin - β‖v‖, rewardMax + β‖v‖]`, so it is finite, +and `EReal.toReal` turns it back into a bounded continuous real function. + +## Main results + +* `LeanEconomics.ExtendedProgram.blackwell` : the operator is a contraction. +* `LeanEconomics.ExtendedProgram.exists_optimal_policy` : the value function satisfies the + Bellman equation, with the reward appearing as an `EReal` and no floor anywhere. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +variable {S A : Type*} [TopologicalSpace S] [TopologicalSpace A] + +/-- A dynamic program whose reward may be `-∞`. -/ +structure ExtendedProgram (S A : Type*) [TopologicalSpace S] [TopologicalSpace A] where + /-- The actions available at each state. -/ + feasible : S → Set A + isCompact_feasible : ∀ s, IsCompact (feasible s) + feasible_nonempty : ∀ s, (feasible s).Nonempty + upperHemicontinuous_feasible : UpperHemicontinuous feasible + lowerHemicontinuous_feasible : LowerHemicontinuous feasible + /-- The reward, continuous into the extended reals and possibly `-∞`. -/ + reward : C(S × A, EReal) + /-- An upper bound on the reward, needed only on the feasible graph. -/ + rewardMax : ℝ + reward_le : ∀ s, ∀ a ∈ feasible s, reward (s, a) ≤ (rewardMax : EReal) + /-- A continuous feasible policy. -/ + select : C(S, A) + select_mem : ∀ s, select s ∈ feasible s + /-- Its reward is bounded below: the agent always has a decent option. -/ + rewardMin : ℝ + le_reward_select : ∀ s, (rewardMin : EReal) ≤ reward (s, select s) + /-- The law of motion. -/ + transition : C(S × A, S) + /-- The factor applied to next-period value. -/ + discount : ℝ≥0 + discount_lt_one : discount < 1 + +/-! ### Extending a utility by `-∞` + +Retrofitting a model that uses a floor means turning its real-valued `u`, which is only +sensible on positive consumption, into a continuous `EReal`-valued reward. That is this +lemma, and it is the only shared cost of the retrofit: supply `u` falling to `-∞` at zero +and the extension is continuous. -/ + +/-- A function on the positives, extended by `-∞` to the whole line. -/ +noncomputable def extendBot (u : ℝ → ℝ) : ℝ → EReal := fun c => if 0 < c then (u c : EReal) else ⊥ + +@[simp] theorem extendBot_of_pos {u : ℝ → ℝ} {c : ℝ} (hc : 0 < c) : + extendBot u c = (u c : EReal) := by simp [extendBot, hc] + +@[simp] theorem extendBot_of_nonpos {u : ℝ → ℝ} {c : ℝ} (hc : c ≤ 0) : extendBot u c = ⊥ := by + simp [extendBot, not_lt.mpr hc] + +/-- The extension is monotone when `u` is monotone on the positives. -/ +theorem extendBot_mono {u : ℝ → ℝ} (hu : MonotoneOn u (Ioi 0)) : Monotone (extendBot u) := by + intro x y hxy + rcases le_or_gt x 0 with h | h + · rw [extendBot_of_nonpos h]; exact bot_le + · rw [extendBot_of_pos h, extendBot_of_pos (lt_of_lt_of_le h hxy), EReal.coe_le_coe_iff] + exact hu h (lt_of_lt_of_le h hxy) hxy + +/-- **The extension is continuous** exactly when `u` falls to `-∞` at zero consumption -- +which is what makes a floor necessary in the first place, so nothing extra is being +assumed. -/ +theorem continuous_extendBot {u : ℝ → ℝ} (hcont : ContinuousOn u (Ioi 0)) + (hbot : Tendsto u (𝓝[>] 0) atBot) : Continuous (extendBot u) := by + rw [continuous_iff_continuousAt] + intro c + rcases lt_trichotomy 0 c with h | h | h + · -- positive consumption: the extension agrees with `u` nearby + have hev : extendBot u =ᶠ[𝓝 c] fun x => (u x : EReal) := by + filter_upwards [lt_mem_nhds h] with x hx + exact extendBot_of_pos hx + have hcu : ContinuousAt (fun x => (u x : EReal)) c := + continuous_coe_real_ereal.continuousAt.comp (hcont.continuousAt (Ioi_mem_nhds h)) + exact hcu.congr hev.symm + · -- zero consumption: both sides fall to `⊥` + subst h + rw [ContinuousAt, extendBot_of_nonpos le_rfl, EReal.tendsto_nhds_bot_iff_real] + intro x + rw [← nhdsLE_sup_nhdsGT (0 : ℝ), Filter.eventually_sup] + constructor + · filter_upwards [self_mem_nhdsWithin] with a ha + rw [extendBot_of_nonpos ha] + exact EReal.bot_lt_coe x + · filter_upwards [hbot.eventually (Filter.eventually_lt_atBot x), self_mem_nhdsWithin] + with a ha ha' + rw [extendBot_of_pos ha'] + exact_mod_cast ha + · -- negative consumption: constantly `⊥` nearby + have hev : extendBot u =ᶠ[𝓝 c] fun _ => (⊥ : EReal) := by + filter_upwards [gt_mem_nhds h] with x hx + exact extendBot_of_nonpos hx.le + exact continuousAt_const.congr hev.symm + +namespace ExtendedProgram + +variable (D : ExtendedProgram S A) + +/-- The objective, in the extended reals. -/ +noncomputable def objectiveE (v : S →ᵇ ℝ) (s : S) (a : A) : EReal := + D.reward (s, a) + ((D.discount * v (D.transition (s, a)) : ℝ) : EReal) + +/-- The value of the one-period problem, in the extended reals. -/ +noncomputable def maxE (v : S →ᵇ ℝ) (s : S) : EReal := maxValueE (D.objectiveE v) D.feasible s + +/-- The upper bound on the one-period value. -/ +noncomputable def hiBound (v : S →ᵇ ℝ) : ℝ := D.rewardMax + D.discount * ‖v‖ + +/-- The lower bound on the one-period value, coming from the feasible selection. -/ +noncomputable def loBound (v : S →ᵇ ℝ) : ℝ := D.rewardMin - D.discount * ‖v‖ + +theorem continuous_uncurry_objectiveE (v : S →ᵇ ℝ) : Continuous ↿(D.objectiveE v) := by + have h1 : Continuous fun p : S × A => D.reward p := D.reward.continuous + have h2 : Continuous fun p : S × A => ((D.discount * v (D.transition p) : ℝ) : EReal) := + continuous_coe_real_ereal.comp + (continuous_const.mul (v.continuous.comp D.transition.continuous)) + rw [continuous_iff_continuousAt] + intro p + exact (EReal.continuousAt_add (Or.inr (EReal.coe_ne_bot _)) (Or.inr (EReal.coe_ne_top _))).comp + (h1.continuousAt.prodMk h2.continuousAt) + +theorem abs_discount_smul_le (v : S →ᵇ ℝ) (t : S) : + |(D.discount : ℝ) * v t| ≤ D.discount * ‖v‖ := by + rw [abs_mul, abs_of_nonneg D.discount.coe_nonneg] + exact mul_le_mul_of_nonneg_left + (by simpa [Real.norm_eq_abs] using v.norm_coe_le_norm t) D.discount.coe_nonneg + +theorem maxE_le (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≤ ((D.hiBound v : ℝ) : EReal) := by + refine maxValueE_le fun a ha => ?_ + have h1 : D.reward (s, a) ≤ ((D.rewardMax : ℝ) : EReal) := D.reward_le s a ha + have h2 : ((D.discount * v (D.transition (s, a)) : ℝ) : EReal) + ≤ ((D.discount * ‖v‖ : ℝ) : EReal) := by + rw [EReal.coe_le_coe_iff] + exact (abs_le.mp (D.abs_discount_smul_le v (D.transition (s, a)))).2 + calc D.objectiveE v s a ≤ ((D.rewardMax : ℝ) : EReal) + ((D.discount * ‖v‖ : ℝ) : EReal) := + add_le_add h1 h2 + _ = ((D.hiBound v : ℝ) : EReal) := by rw [hiBound, EReal.coe_add] + +theorem le_maxE (v : S →ᵇ ℝ) (s : S) : ((D.loBound v : ℝ) : EReal) ≤ D.maxE v s := by + refine le_trans ?_ (le_maxValueE (D.select_mem s)) + have h1 : ((D.rewardMin : ℝ) : EReal) ≤ D.reward (s, D.select s) := D.le_reward_select s + have h2 : ((-(D.discount * ‖v‖) : ℝ) : EReal) + ≤ ((D.discount * v (D.transition (s, D.select s)) : ℝ) : EReal) := by + rw [EReal.coe_le_coe_iff] + exact (abs_le.mp (D.abs_discount_smul_le v (D.transition (s, D.select s)))).1 + calc ((D.loBound v : ℝ) : EReal) + = ((D.rewardMin : ℝ) : EReal) + ((-(D.discount * ‖v‖) : ℝ) : EReal) := by + rw [← EReal.coe_add] + congr 1 + _ ≤ _ := add_le_add h1 h2 + +theorem maxE_ne_top (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≠ ⊤ := by + intro h + have hle := D.maxE_le v s + rw [h, top_le_iff] at hle + exact EReal.coe_ne_top _ hle + +theorem maxE_ne_bot (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≠ ⊥ := by + intro h + have hle := D.le_maxE v s + rw [h, le_bot_iff] at hle + exact EReal.coe_ne_bot _ hle + +/-- The Bellman operator as a bare real-valued function. -/ +noncomputable def bellmanFn (v : S →ᵇ ℝ) (s : S) : ℝ := (D.maxE v s).toReal + +@[simp] +theorem coe_bellmanFn (v : S →ᵇ ℝ) (s : S) : ((D.bellmanFn v s : ℝ) : EReal) = D.maxE v s := + EReal.coe_toReal (D.maxE_ne_top v s) (D.maxE_ne_bot v s) + +theorem le_bellmanFn (v : S →ᵇ ℝ) {s : S} {a : A} (ha : a ∈ D.feasible s) : + D.objectiveE v s a ≤ ((D.bellmanFn v s : ℝ) : EReal) := by + rw [D.coe_bellmanFn] + exact le_maxValueE ha + +theorem bellmanFn_le (v : S →ᵇ ℝ) {s : S} {c : ℝ} + (h : ∀ a ∈ D.feasible s, D.objectiveE v s a ≤ (c : EReal)) : D.bellmanFn v s ≤ c := by + have hle : D.maxE v s ≤ (c : EReal) := maxValueE_le h + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at hle + exact hle + +theorem bellmanFn_bounds (v : S →ᵇ ℝ) (s : S) : + D.loBound v ≤ D.bellmanFn v s ∧ D.bellmanFn v s ≤ D.hiBound v := by + constructor + · have := D.le_maxE v s + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at this + exact this + · have := D.maxE_le v s + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at this + exact this + +theorem abs_bellmanFn_le (v : S →ᵇ ℝ) (s : S) : + |D.bellmanFn v s| ≤ max |D.loBound v| |D.hiBound v| := by + obtain ⟨h1, h2⟩ := D.bellmanFn_bounds v s + rw [abs_le] + constructor + · have : -|D.loBound v| ≤ D.loBound v := neg_abs_le _ + have h3 : -(max |D.loBound v| |D.hiBound v|) ≤ -|D.loBound v| := neg_le_neg (le_max_left _ _) + linarith + · exact h2.trans ((le_abs_self _).trans (le_max_right _ _)) + +theorem continuous_bellmanFn (v : S →ᵇ ℝ) : Continuous (D.bellmanFn v) := by + have hcont : Continuous (D.maxE v) := + continuous_maxValueE (D.continuous_uncurry_objectiveE v) D.feasible_nonempty + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible + refine EReal.continuousOn_toReal.comp_continuous hcont fun s => ?_ + simp only [mem_compl_iff, mem_insert_iff, mem_singleton_iff, not_or] + exact ⟨D.maxE_ne_bot v s, D.maxE_ne_top v s⟩ + +/-- The **Bellman operator** on bounded continuous functions, with an extended-real +reward. -/ +noncomputable def bellman (v : S →ᵇ ℝ) : S →ᵇ ℝ := + BoundedContinuousFunction.ofNormedAddCommGroup (D.bellmanFn v) (D.continuous_bellmanFn v) + (max |D.loBound v| |D.hiBound v|) + fun s => by simpa [Real.norm_eq_abs] using D.abs_bellmanFn_le v s + +@[simp] +theorem bellman_apply (v : S →ᵇ ℝ) (s : S) : D.bellman v s = D.bellmanFn v s := rfl + +/-- **Blackwell's conditions hold.** The extended-real reward is a common summand on both +sides of each inequality, so it plays no part: monotonicity and discounting are facts about +the continuation term alone. -/ +theorem blackwell : Blackwell D.discount D.bellman where + monotone := by + intro v w hvw s + refine D.bellmanFn_le v fun a ha => ?_ + refine le_trans ?_ (D.le_bellmanFn w ha) + simp only [objectiveE] + refine add_le_add (le_refl _) ?_ + rw [EReal.coe_le_coe_iff] + exact mul_le_mul_of_nonneg_left (by simpa using hvw (D.transition (s, a))) + D.discount.coe_nonneg + discounting := by + intro v c _ s + have hgoal : ((D.bellman v + const S ((D.discount : ℝ) * c)) s : ℝ) + = D.bellmanFn v s + (D.discount : ℝ) * c := by simp + refine le_trans (D.bellmanFn_le (v + const S c) + (c := D.bellmanFn v s + (D.discount : ℝ) * c) fun a ha => ?_) (le_of_eq hgoal.symm) + have hsplit : D.objectiveE (v + const S c) s a + = D.objectiveE v s a + ((D.discount * c : ℝ) : EReal) := by + simp only [objectiveE, coe_add, const_apply, Pi.add_apply] + rw [show (D.discount : ℝ) * (v (D.transition (s, a)) + c) + = D.discount * v (D.transition (s, a)) + D.discount * c by ring, + EReal.coe_add, add_assoc] + rw [hsplit] + calc D.objectiveE v s a + ((D.discount * c : ℝ) : EReal) + ≤ ((D.bellmanFn v s : ℝ) : EReal) + ((D.discount * c : ℝ) : EReal) := + add_le_add (D.le_bellmanFn v ha) (le_refl _) + _ = ((D.bellmanFn v s + D.discount * c : ℝ) : EReal) := (EReal.coe_add _ _).symm + +/-- The supremum is attained: an optimal action exists at every state. -/ +theorem exists_optimal_action (v : S →ᵇ ℝ) (s : S) : + ∃ a ∈ D.feasible s, D.objectiveE v s a = ((D.bellmanFn v s : ℝ) : EReal) := by + obtain ⟨a, ha, heq⟩ := maxValueE_eq (D.continuous_uncurry_objectiveE v) + (D.feasible_nonempty s) (D.isCompact_feasible s) + exact ⟨a, ha, by rw [D.coe_bellmanFn]; exact heq.symm⟩ + +/-- The maximiser correspondence is upper hemicontinuous, by the argmax half of Berge for +extended-real objectives. -/ +theorem upperHemicontinuous_argmax (v : S →ᵇ ℝ) : + UpperHemicontinuous (argmax (D.objectiveE v) D.feasible) := + upperHemicontinuous_argmaxE (D.continuous_uncurry_objectiveE v) + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible + +section Monotone + +variable [Preorder S] + +/-- The operator preserves monotonicity under the same conditions as in the real case. -/ +theorem monotone_bellman + (hfeas : ∀ s s', s ≤ s' → D.feasible s ⊆ D.feasible s') + (hreward : ∀ a, Monotone fun s => D.reward (s, a)) + (htrans : ∀ a, Monotone fun s => D.transition (s, a)) + (v : S →ᵇ ℝ) (hv : Monotone ⇑v) : Monotone ⇑(D.bellman v) := by + intro s s' hss + refine D.bellmanFn_le v fun a ha => ?_ + refine le_trans ?_ (D.le_bellmanFn v (hfeas s s' hss ha)) + refine add_le_add (hreward a hss) ?_ + rw [EReal.coe_le_coe_iff] + exact mul_le_mul_of_nonneg_left (hv (htrans a hss)) D.discount.coe_nonneg + +end Monotone + +section ValueFunction + +/-- The **value function** of the program. -/ +noncomputable def valueFunction : S →ᵇ ℝ := D.blackwell.valueFunction D.discount_lt_one + +theorem bellman_valueFunction : D.bellman D.valueFunction = D.valueFunction := + D.blackwell.isFixedPt_valueFunction D.discount_lt_one + +theorem eq_valueFunction {v : S →ᵇ ℝ} (hv : D.bellman v = v) : v = D.valueFunction := + D.blackwell.eq_valueFunction D.discount_lt_one hv + +theorem tendsto_iterate_valueFunction (v : S →ᵇ ℝ) : + Tendsto (fun n => D.bellman^[n] v) atTop (𝓝 D.valueFunction) := + D.blackwell.tendsto_iterate_valueFunction D.discount_lt_one v + +/-- **The Bellman equation**, with the reward an extended real and no floor anywhere. -/ +theorem exists_optimal_policy (s : S) : + ∃ a ∈ D.feasible s, ((D.valueFunction s : ℝ) : EReal) + = D.reward (s, a) + ((D.discount * D.valueFunction (D.transition (s, a)) : ℝ) : EReal) := by + obtain ⟨a, ha, heq⟩ := D.exists_optimal_action D.valueFunction s + refine ⟨a, ha, ?_⟩ + have hfix : D.bellmanFn D.valueFunction s = D.valueFunction s := by + conv_rhs => rw [← D.bellman_valueFunction] + rfl + rw [← hfix, ← heq] + rfl + +/-- **The value function is monotone** under the same conditions as in the real case. -/ +theorem monotone_valueFunction [Preorder S] + (hfeas : ∀ s s', s ≤ s' → D.feasible s ⊆ D.feasible s') + (hreward : ∀ a, Monotone fun s => D.reward (s, a)) + (htrans : ∀ a, Monotone fun s => D.transition (s, a)) : + Monotone ⇑D.valueFunction := + D.blackwell.monotone_valueFunction D.discount_lt_one + fun v hv => D.monotone_bellman hfeas hreward htrans v hv + +end ValueFunction + +end ExtendedProgram + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/DynamicProgramming/ExtendedStochastic.lean b/LeanPool/AiyagariEquilibrium/DynamicProgramming/ExtendedStochastic.lean new file mode 100644 index 000000000..e7754e787 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/DynamicProgramming/ExtendedStochastic.lean @@ -0,0 +1,454 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Extended +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Stochastic + +/-! +# Stochastic dynamic programs with extended-real rewards + +`LeanEconomics.DynamicProgramming.Extended` allows a reward of `-∞` but has a deterministic +law of motion; `LeanEconomics.DynamicProgramming.Stochastic` has finitely many shocks but a +real-valued reward. This file is the combination, and it is what a stochastic model with +log or CRRA utility needs if it is not to carry a floor. + +## Why the combination is routine + +The expectation `∑ z, p z * v (g z)` is a sum of *real* numbers, since value functions stay +real-valued. So the objective is again `reward + ↑(real)`: an extended real plus a finite +coercion, exactly the shape `Extended` already handles. Neither half has to be redone -- +the reward's `-∞` never meets the expectation's arithmetic, and `⊥ + ⊤` never arises. + +Accordingly the two facts about the expectation are the same two as in the real stochastic +file: `prob_nonneg` gives monotonicity, `prob_sum` gives discounting. + +## Main results + +* `LeanEconomics.ExtendedStochasticProgram.blackwell` : the operator is a contraction. +* `LeanEconomics.ExtendedStochasticProgram.exists_optimal_policy` : the stochastic Bellman + equation, with the reward an extended real and no floor. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +variable {S A Z : Type*} [TopologicalSpace S] [TopologicalSpace A] [Fintype Z] + +/-- A stochastic dynamic program whose reward may be `-∞`. -/ +structure ExtendedStochasticProgram (S A Z : Type*) [TopologicalSpace S] [TopologicalSpace A] + [Fintype Z] where + /-- The actions available at each state. -/ + feasible : S → Set A + isCompact_feasible : ∀ s, IsCompact (feasible s) + feasible_nonempty : ∀ s, (feasible s).Nonempty + upperHemicontinuous_feasible : UpperHemicontinuous feasible + lowerHemicontinuous_feasible : LowerHemicontinuous feasible + /-- The reward, possibly `-∞`. -/ + reward : C(S × A, EReal) + /-- An upper bound on the reward, needed only on the feasible graph. -/ + rewardMax : ℝ + reward_le : ∀ s, ∀ a ∈ feasible s, reward (s, a) ≤ (rewardMax : EReal) + /-- A continuous feasible policy whose reward is bounded below. -/ + select : C(S, A) + select_mem : ∀ s, select s ∈ feasible s + /-- A uniform one-period payoff available through the feasible policy `select`. -/ + rewardMin : ℝ + le_reward_select : ∀ s, (rewardMin : EReal) ≤ reward (s, select s) + /-- The state reached when shock `z` occurs. -/ + transition : Z → C(S × A, S) + /-- The probability that shock `z` occurs. -/ + prob : Z → C(S × A, ℝ) + prob_nonneg : ∀ z p, 0 ≤ prob z p + prob_sum : ∀ p, ∑ z, prob z p = 1 + /-- The factor applied to next-period value. -/ + discount : ℝ≥0 + discount_lt_one : discount < 1 + +namespace ExtendedStochasticProgram + +variable (D : ExtendedStochasticProgram S A Z) + +/-- The expected continuation value: a sum of reals, so it never meets the reward's `-∞`. -/ +noncomputable def expect (v : S →ᵇ ℝ) (p : S × A) : ℝ := + ∑ z, D.prob z p * v (D.transition z p) + +/-- The objective, in the extended reals. -/ +noncomputable def objectiveE (v : S →ᵇ ℝ) (s : S) (a : A) : EReal := + D.reward (s, a) + ((D.discount * D.expect v (s, a) : ℝ) : EReal) + +/-- The value of the one-period problem, in the extended reals. -/ +noncomputable def maxE (v : S →ᵇ ℝ) (s : S) : EReal := maxValueE (D.objectiveE v) D.feasible s + +/-- The upper bound on the one-period value. -/ +noncomputable def hiBound (v : S →ᵇ ℝ) : ℝ := D.rewardMax + D.discount * ‖v‖ + +/-- The lower bound on the one-period value. -/ +noncomputable def loBound (v : S →ᵇ ℝ) : ℝ := D.rewardMin - D.discount * ‖v‖ + +theorem continuous_expect (v : S →ᵇ ℝ) : Continuous (D.expect v) := + continuous_finsetSum _ fun z _ => + (D.prob z).continuous.mul (v.continuous.comp (D.transition z).continuous) + +theorem continuous_uncurry_objectiveE (v : S →ᵇ ℝ) : Continuous ↿(D.objectiveE v) := by + have h1 : Continuous fun p : S × A => D.reward p := D.reward.continuous + have h2 : Continuous fun p : S × A => ((D.discount * D.expect v p : ℝ) : EReal) := + continuous_coe_real_ereal.comp (continuous_const.mul (D.continuous_expect v)) + rw [continuous_iff_continuousAt] + intro p + exact (EReal.continuousAt_add (Or.inr (EReal.coe_ne_bot _)) (Or.inr (EReal.coe_ne_top _))).comp + (h1.continuousAt.prodMk h2.continuousAt) + +/-- An average of values of `v` is no larger than the size of `v`. -/ +theorem abs_expect_le (v : S →ᵇ ℝ) (p : S × A) : |D.expect v p| ≤ ‖v‖ := by + calc |D.expect v p| ≤ ∑ z, |D.prob z p * v (D.transition z p)| := + Finset.abs_sum_le_sum_abs _ _ + _ = ∑ z, D.prob z p * |v (D.transition z p)| := by + refine Finset.sum_congr rfl fun z _ => ?_ + rw [abs_mul, abs_of_nonneg (D.prob_nonneg z p)] + _ ≤ ∑ z, D.prob z p * ‖v‖ := by + refine Finset.sum_le_sum fun z _ => mul_le_mul_of_nonneg_left ?_ (D.prob_nonneg z p) + simpa [Real.norm_eq_abs] using v.norm_coe_le_norm (D.transition z p) + _ = ‖v‖ := by rw [← Finset.sum_mul, D.prob_sum, one_mul] + +theorem maxE_le (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≤ ((D.hiBound v : ℝ) : EReal) := by + refine maxValueE_le fun a ha => ?_ + have h1 : D.reward (s, a) ≤ ((D.rewardMax : ℝ) : EReal) := D.reward_le s a ha + have h2 : ((D.discount * D.expect v (s, a) : ℝ) : EReal) ≤ ((D.discount * ‖v‖ : ℝ) : EReal) := by + rw [EReal.coe_le_coe_iff] + exact mul_le_mul_of_nonneg_left (abs_le.mp (D.abs_expect_le v (s, a))).2 D.discount.coe_nonneg + calc D.objectiveE v s a ≤ ((D.rewardMax : ℝ) : EReal) + ((D.discount * ‖v‖ : ℝ) : EReal) := + add_le_add h1 h2 + _ = ((D.hiBound v : ℝ) : EReal) := by rw [hiBound, EReal.coe_add] + +theorem le_maxE (v : S →ᵇ ℝ) (s : S) : ((D.loBound v : ℝ) : EReal) ≤ D.maxE v s := by + refine le_trans ?_ (le_maxValueE (D.select_mem s)) + have h1 : ((D.rewardMin : ℝ) : EReal) ≤ D.reward (s, D.select s) := D.le_reward_select s + have h2 : ((-(D.discount * ‖v‖) : ℝ) : EReal) + ≤ ((D.discount * D.expect v (s, D.select s) : ℝ) : EReal) := by + rw [EReal.coe_le_coe_iff] + have := (abs_le.mp (D.abs_expect_le v (s, D.select s))).1 + have h := mul_le_mul_of_nonneg_left this D.discount.coe_nonneg + linarith + calc ((D.loBound v : ℝ) : EReal) + = ((D.rewardMin : ℝ) : EReal) + ((-(D.discount * ‖v‖) : ℝ) : EReal) := by + rw [← EReal.coe_add] + congr 1 + _ ≤ _ := add_le_add h1 h2 + +theorem maxE_ne_top (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≠ ⊤ := by + intro h + have hle := D.maxE_le v s + rw [h, top_le_iff] at hle + exact EReal.coe_ne_top _ hle + +theorem maxE_ne_bot (v : S →ᵇ ℝ) (s : S) : D.maxE v s ≠ ⊥ := by + intro h + have hle := D.le_maxE v s + rw [h, le_bot_iff] at hle + exact EReal.coe_ne_bot _ hle + +/-- The Bellman operator as a bare real-valued function. -/ +noncomputable def bellmanFn (v : S →ᵇ ℝ) (s : S) : ℝ := (D.maxE v s).toReal + +@[simp] +theorem coe_bellmanFn (v : S →ᵇ ℝ) (s : S) : ((D.bellmanFn v s : ℝ) : EReal) = D.maxE v s := + EReal.coe_toReal (D.maxE_ne_top v s) (D.maxE_ne_bot v s) + +theorem le_bellmanFn (v : S →ᵇ ℝ) {s : S} {a : A} (ha : a ∈ D.feasible s) : + D.objectiveE v s a ≤ ((D.bellmanFn v s : ℝ) : EReal) := by + rw [D.coe_bellmanFn] + exact le_maxValueE ha + +theorem bellmanFn_le (v : S →ᵇ ℝ) {s : S} {c : ℝ} + (h : ∀ a ∈ D.feasible s, D.objectiveE v s a ≤ (c : EReal)) : D.bellmanFn v s ≤ c := by + have hle : D.maxE v s ≤ (c : EReal) := maxValueE_le h + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at hle + exact hle + +theorem bellmanFn_bounds (v : S →ᵇ ℝ) (s : S) : + D.loBound v ≤ D.bellmanFn v s ∧ D.bellmanFn v s ≤ D.hiBound v := by + constructor + · have := D.le_maxE v s + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at this + exact this + · have := D.maxE_le v s + rw [← D.coe_bellmanFn, EReal.coe_le_coe_iff] at this + exact this + +theorem abs_bellmanFn_le (v : S →ᵇ ℝ) (s : S) : + |D.bellmanFn v s| ≤ max |D.loBound v| |D.hiBound v| := by + obtain ⟨h1, h2⟩ := D.bellmanFn_bounds v s + rw [abs_le] + constructor + · have h3 : -|D.loBound v| ≤ D.loBound v := neg_abs_le _ + have h4 : -(max |D.loBound v| |D.hiBound v|) ≤ -|D.loBound v| := neg_le_neg (le_max_left _ _) + linarith + · exact h2.trans ((le_abs_self _).trans (le_max_right _ _)) + +theorem continuous_bellmanFn (v : S →ᵇ ℝ) : Continuous (D.bellmanFn v) := by + have hcont : Continuous (D.maxE v) := + continuous_maxValueE (D.continuous_uncurry_objectiveE v) D.feasible_nonempty + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible + refine EReal.continuousOn_toReal.comp_continuous hcont fun s => ?_ + simp only [mem_compl_iff, mem_insert_iff, mem_singleton_iff, not_or] + exact ⟨D.maxE_ne_bot v s, D.maxE_ne_top v s⟩ + +/-- The **stochastic Bellman operator** with an extended-real reward. -/ +noncomputable def bellman (v : S →ᵇ ℝ) : S →ᵇ ℝ := + BoundedContinuousFunction.ofNormedAddCommGroup (D.bellmanFn v) (D.continuous_bellmanFn v) + (max |D.loBound v| |D.hiBound v|) + fun s => by simpa [Real.norm_eq_abs] using D.abs_bellmanFn_le v s + +@[simp] +theorem bellman_apply (v : S →ᵇ ℝ) (s : S) : D.bellman v s = D.bellmanFn v s := rfl + +/-- Adding a constant to the continuation value adds exactly that constant to the +expectation: this is `prob_sum`. -/ +theorem expect_add_const (v : S →ᵇ ℝ) (c : ℝ) (p : S × A) : + D.expect (v + const S c) p = D.expect v p + c := by + have hterm : ∀ z : Z, D.prob z p * (v + const S c) (D.transition z p) + = D.prob z p * v (D.transition z p) + D.prob z p * c := fun z => by + simp only [coe_add, const_apply, Pi.add_apply]; ring + simp only [expect] + rw [Finset.sum_congr rfl fun z _ => hterm z, Finset.sum_add_distrib, ← Finset.sum_mul, + D.prob_sum, one_mul] + +/-- **Blackwell's conditions hold.** -/ +theorem blackwell : Blackwell D.discount D.bellman where + monotone := by + intro v w hvw s + refine D.bellmanFn_le v fun a ha => ?_ + refine le_trans ?_ (D.le_bellmanFn w ha) + simp only [objectiveE] + refine add_le_add (le_refl _) ?_ + rw [EReal.coe_le_coe_iff] + refine mul_le_mul_of_nonneg_left ?_ D.discount.coe_nonneg + refine Finset.sum_le_sum fun z _ => mul_le_mul_of_nonneg_left ?_ (D.prob_nonneg z _) + simpa using hvw (D.transition z (s, a)) + discounting := by + intro v c _ s + have hgoal : ((D.bellman v + const S ((D.discount : ℝ) * c)) s : ℝ) + = D.bellmanFn v s + (D.discount : ℝ) * c := by simp + refine le_trans (D.bellmanFn_le (v + const S c) + (c := D.bellmanFn v s + (D.discount : ℝ) * c) fun a ha => ?_) (le_of_eq hgoal.symm) + have hsplit : D.objectiveE (v + const S c) s a + = D.objectiveE v s a + ((D.discount * c : ℝ) : EReal) := by + simp only [objectiveE, D.expect_add_const] + rw [show (D.discount : ℝ) * (D.expect v (s, a) + c) + = D.discount * D.expect v (s, a) + D.discount * c by ring, + EReal.coe_add, add_assoc] + rw [hsplit] + calc D.objectiveE v s a + ((D.discount * c : ℝ) : EReal) + ≤ ((D.bellmanFn v s : ℝ) : EReal) + ((D.discount * c : ℝ) : EReal) := + add_le_add (D.le_bellmanFn v ha) (le_refl _) + _ = ((D.bellmanFn v s + D.discount * c : ℝ) : EReal) := (EReal.coe_add _ _).symm + +/-- The supremum is attained: an optimal action exists at every state. -/ +theorem exists_optimal_action (v : S →ᵇ ℝ) (s : S) : + ∃ a ∈ D.feasible s, D.objectiveE v s a = ((D.bellmanFn v s : ℝ) : EReal) := by + obtain ⟨a, ha, heq⟩ := maxValueE_eq (D.continuous_uncurry_objectiveE v) + (D.feasible_nonempty s) (D.isCompact_feasible s) + exact ⟨a, ha, by rw [D.coe_bellmanFn]; exact heq.symm⟩ + +/-- The maximiser correspondence is upper hemicontinuous. -/ +theorem upperHemicontinuous_argmax (v : S →ᵇ ℝ) : + UpperHemicontinuous (argmax (D.objectiveE v) D.feasible) := + upperHemicontinuous_argmaxE (D.continuous_uncurry_objectiveE v) + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible + +section ValueFunction + +/-- The **value function** of the program. -/ +noncomputable def valueFunction : S →ᵇ ℝ := D.blackwell.valueFunction D.discount_lt_one + +theorem bellman_valueFunction : D.bellman D.valueFunction = D.valueFunction := + D.blackwell.isFixedPt_valueFunction D.discount_lt_one + +/-- **An explicit bound on the value function**, in terms of the reward bounds alone. + +The right-hand side mentions no state and no fixed point, which is exactly what is needed to +make bounds derived from `‖valueFunction‖` uniform across a FAMILY of programs: the reward +bounds of a family are typically easy to control, whereas its value functions are not. -/ +theorem norm_valueFunction_le : + ‖D.valueFunction‖ ≤ max |D.rewardMin| |D.rewardMax| / (1 - D.discount) := by + have hβ1 : (D.discount : ℝ) < 1 := by exact_mod_cast D.discount_lt_one + have hβ0 : (0 : ℝ) ≤ (D.discount : ℝ) := D.discount.coe_nonneg + set M : ℝ := max |D.rewardMin| |D.rewardMax| with hM + have hM0 : 0 ≤ M := le_trans (abs_nonneg _) (le_max_left _ _) + have hV0 : 0 ≤ ‖D.valueFunction‖ := norm_nonneg _ + have hkey : ‖D.valueFunction‖ ≤ M + D.discount * ‖D.valueFunction‖ := by + refine (BoundedContinuousFunction.norm_le (by positivity)).mpr fun s => ?_ + have hfix : D.valueFunction s = D.bellmanFn D.valueFunction s := by + rw [← D.bellman_apply, D.bellman_valueFunction] + rw [Real.norm_eq_abs, hfix] + refine le_trans (D.abs_bellmanFn_le _ s) (max_le ?_ ?_) + · have h1 : |D.rewardMin| ≤ M := le_max_left _ _ + rw [abs_le] at h1 ⊢ + constructor <;> · simp only [loBound]; nlinarith [h1.1, h1.2] + · have h1 : |D.rewardMax| ≤ M := le_max_right _ _ + rw [abs_le] at h1 ⊢ + constructor <;> · simp only [hiBound]; nlinarith [h1.1, h1.2] + rw [le_div_iff₀ (by linarith)] + nlinarith [hkey] + +theorem eq_valueFunction {v : S →ᵇ ℝ} (hv : D.bellman v = v) : v = D.valueFunction := + D.blackwell.eq_valueFunction D.discount_lt_one hv + +theorem tendsto_iterate_valueFunction (v : S →ᵇ ℝ) : + Tendsto (fun n => D.bellman^[n] v) atTop (𝓝 D.valueFunction) := + D.blackwell.tendsto_iterate_valueFunction D.discount_lt_one v + +/-- The constant plan is not improved on by the operator, so value function iteration +started there stays below it. -/ +theorem bellman_const_le {c : ℝ} (hc : D.rewardMax + D.discount * c ≤ c) : + D.bellman (const S c) ≤ const S c := by + intro s + refine D.bellmanFn_le (const S c) fun a ha => ?_ + have hexp : D.expect (const S c) (s, a) = c := by + have := D.expect_add_const (0 : S →ᵇ ℝ) c (s, a) + simpa [expect] using this + simp only [objectiveE, hexp] + calc D.reward (s, a) + ((D.discount * c : ℝ) : EReal) + ≤ ((D.rewardMax : ℝ) : EReal) + ((D.discount * c : ℝ) : EReal) := + add_le_add (D.reward_le s a ha) (le_refl _) + _ = ((D.rewardMax + D.discount * c : ℝ) : EReal) := (EReal.coe_add _ _).symm + _ ≤ ((c : ℝ) : EReal) := EReal.coe_le_coe_iff.mpr hc + +theorem le_bellman_const {c : ℝ} (hc : c ≤ D.rewardMin + D.discount * c) : + const S c ≤ D.bellman (const S c) := by + intro s + have hexp : D.expect (const S c) (s, D.select s) = c := by + have := D.expect_add_const (0 : S →ᵇ ℝ) c (s, D.select s) + simpa [expect] using this + have hle : ((c : ℝ) : EReal) ≤ D.objectiveE (const S c) s (D.select s) := by + simp only [objectiveE, hexp] + calc ((c : ℝ) : EReal) ≤ ((D.rewardMin + D.discount * c : ℝ) : EReal) := + EReal.coe_le_coe_iff.mpr hc + _ = ((D.rewardMin : ℝ) : EReal) + ((D.discount * c : ℝ) : EReal) := EReal.coe_add _ _ + _ ≤ D.reward (s, D.select s) + ((D.discount * c : ℝ) : EReal) := + add_le_add (D.le_reward_select s) (le_refl _) + have := le_trans hle (D.le_bellmanFn (const S c) (D.select_mem s)) + simpa using EReal.coe_le_coe_iff.mp this + +/-- **The value function never exceeds the best constant plan.** Unlike `norm_valueFunction_le` +this is one-sided, and the pair of bounds caps the OSCILLATION of the value function by +`(rewardMax - rewardMin) / (1 - β)` -- a quantity that does not move when a constant is added to +the reward, which the sup norm does. That matters for CES: `c ^ (1-γ) / (1-γ)` differs from `log` +by the constant `1 / (1-γ)`, so any bound built from `‖V‖` blows up as `γ → 1` while the model it +describes does not. -/ +theorem valueFunction_le_const {c : ℝ} (hc : D.rewardMax + D.discount * c ≤ c) (s : S) : + D.valueFunction s ≤ c := by + have hiter : ∀ n : ℕ, D.bellman^[n] (const S c) ≤ const S c := by + intro n + induction n with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply'] + exact le_trans (D.blackwell.monotone ih) (D.bellman_const_le hc) + have hlim := D.tendsto_iterate_valueFunction (const S c) + have hpt : Filter.Tendsto (fun n => D.bellman^[n] (const S c) s) Filter.atTop + (𝓝 (D.valueFunction s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp hlim + exact le_of_tendsto' hpt fun n => hiter n s + +theorem const_le_valueFunction {c : ℝ} (hc : c ≤ D.rewardMin + D.discount * c) (s : S) : + c ≤ D.valueFunction s := by + have hiter : ∀ n : ℕ, (const S c : S →ᵇ ℝ) ≤ D.bellman^[n] (const S c) := by + intro n + induction n with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply'] + exact le_trans (D.le_bellman_const hc) (D.blackwell.monotone ih) + have hlim := D.tendsto_iterate_valueFunction (const S c) + have hpt : Filter.Tendsto (fun n => D.bellman^[n] (const S c) s) Filter.atTop + (𝓝 (D.valueFunction s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp hlim + exact ge_of_tendsto' hpt fun n => hiter n s + +/-- **A dominated operator has a dominated value function, on an invariant class.** The +domination need only hold on a class of continuation values that contains `0` and that the +dominated operator preserves, since value function iteration from `0` never leaves it. That is +what lets the discount factor be compared: a more patient agent's operator dominates only on +NONNEGATIVE continuation values, which is exactly the class the iteration stays in when the +reward is nonnegative. -/ +theorem valueFunction_le_of_bellman_le_on (D₁ D₂ : ExtendedStochasticProgram S A Z) + (C : (S →ᵇ ℝ) → Prop) (hC0 : C 0) (hC : ∀ v, C v → C (D₁.bellman v)) + (hle : ∀ v : S →ᵇ ℝ, C v → ⇑(D₁.bellman v) ≤ ⇑(D₂.bellman v)) (s : S) : + D₁.valueFunction s ≤ D₂.valueFunction s := by + have hmemC : ∀ n : ℕ, C (D₁.bellman^[n] (0 : S →ᵇ ℝ)) := by + intro n + induction n with + | zero => simpa using hC0 + | succ k ih => rw [Function.iterate_succ_apply']; exact hC _ ih + have hiter : ∀ n : ℕ, + ⇑(D₁.bellman^[n] (0 : S →ᵇ ℝ)) ≤ ⇑(D₂.bellman^[n] (0 : S →ᵇ ℝ)) := by + intro n + induction n with + | zero => simp + | succ k ih => + rw [Function.iterate_succ_apply', Function.iterate_succ_apply'] + exact le_trans (hle _ (hmemC k)) (D₂.blackwell.monotone ih) + have hp₁ : Filter.Tendsto (fun n => D₁.bellman^[n] (0 : S →ᵇ ℝ) s) Filter.atTop + (𝓝 (D₁.valueFunction s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp + (D₁.tendsto_iterate_valueFunction 0) + have hp₂ : Filter.Tendsto (fun n => D₂.bellman^[n] (0 : S →ᵇ ℝ) s) Filter.atTop + (𝓝 (D₂.valueFunction s)) := + ((BoundedContinuousFunction.evalCLM ℝ s).continuous.tendsto _).comp + (D₂.tendsto_iterate_valueFunction 0) + exact le_of_tendsto_of_tendsto hp₁ hp₂ (Filter.Eventually.of_forall fun n => hiter n s) + +/-- **Nonnegative rewards give a nonnegative value.** -/ +theorem zero_le_bellman (D : ExtendedStochasticProgram S A Z) (hmin : 0 ≤ D.rewardMin) + {v : S →ᵇ ℝ} (hv : 0 ≤ ⇑v) : 0 ≤ ⇑(D.bellman v) := by + intro s + have hexp : 0 ≤ D.expect v (s, D.select s) := by + refine Finset.sum_nonneg fun z _ => ?_ + exact mul_nonneg (D.prob_nonneg z _) (hv _) + have hobj : ((D.rewardMin + D.discount * D.expect v (s, D.select s) : ℝ) : EReal) + ≤ D.objectiveE v s (D.select s) := by + rw [EReal.coe_add] + exact add_le_add (D.le_reward_select s) (le_refl _) + have := le_trans hobj (D.le_bellmanFn v (D.select_mem s)) + rw [EReal.coe_le_coe_iff] at this + have hβ : (0 : ℝ) ≤ (D.discount : ℝ) := D.discount.coe_nonneg + have : 0 ≤ D.bellmanFn v s := by nlinarith [hmin, hexp, hβ] + exact this + +/-- **A dominated operator has a dominated value function.** The comparative-statics workhorse, +at the level the models use: comparing two economies reduces to comparing their Bellman operators +at a common continuation value. -/ +theorem valueFunction_le_of_bellman_le (D₁ D₂ : ExtendedStochasticProgram S A Z) + (hle : ∀ v : S →ᵇ ℝ, ⇑(D₁.bellman v) ≤ ⇑(D₂.bellman v)) (s : S) : + D₁.valueFunction s ≤ D₂.valueFunction s := + Blackwell.valueFunction_le D₁.blackwell D₂.blackwell D₁.discount_lt_one D₂.discount_lt_one + hle s + +/-- **The stochastic Bellman equation**, with the reward an extended real and no floor. -/ +theorem exists_optimal_policy (s : S) : + ∃ a ∈ D.feasible s, ((D.valueFunction s : ℝ) : EReal) + = D.reward (s, a) + + ((D.discount * ∑ z, D.prob z (s, a) * D.valueFunction (D.transition z (s, a)) : + ℝ) : EReal) := by + obtain ⟨a, ha, heq⟩ := D.exists_optimal_action D.valueFunction s + refine ⟨a, ha, ?_⟩ + have hfix : D.bellmanFn D.valueFunction s = D.valueFunction s := by + conv_rhs => rw [← D.bellman_valueFunction] + rfl + rw [← hfix, ← heq] + rfl + +end ValueFunction + +end ExtendedStochasticProgram + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/DynamicProgramming/Stochastic.lean b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Stochastic.lean new file mode 100644 index 000000000..7ae5ec5cc --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/DynamicProgramming/Stochastic.lean @@ -0,0 +1,231 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Bellman + +/-! +# Stochastic dynamic programming with finitely many shocks + +The Bellman operator + + `T v s = sup { r (s, a) + β * ∑ z, p z (s, a) * v (g z (s, a)) | a ∈ Γ s }` + +where a shock `z` is drawn from a finite set with probabilities `p z (s, a)` and leads to +the state `g z (s, a)`. This is the deterministic operator of +`LeanEconomics.DynamicProgramming.Bellman` with the successor state replaced by an +expectation over successors. + +## Why this is a new structure rather than a reuse + +The deterministic program's continuation is `v` evaluated at one point. Here it is a convex +combination of `v` at several points, which is not of that form, so the operator has to be +rebuilt. Everything above the operator is reused unchanged: the same Blackwell conditions +give the contraction, and the same Berge theorem gives continuity. Only the two facts about +the expectation are new, and they are exactly the two properties of a probability +distribution: + +* `p ≥ 0` gives **monotonicity** -- a larger continuation value cannot lower the average; +* `∑ p = 1` gives **discounting** -- adding a constant to the continuation value adds + exactly that constant to the average, not more. + +Shock probabilities may depend on the state and the action, which covers a Markov chain on +a component of the state as the special case where they depend on that component alone. + +## Main results + +* `LeanEconomics.StochasticDynamicProgram.blackwell` : the operator is a contraction. +* `LeanEconomics.StochasticDynamicProgram.exists_optimal_policy` : the value function + satisfies the stochastic Bellman equation, and the maximum is attained. +-/ + +@[expose] public section + +open scoped NNReal +open Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +variable {S A Z : Type*} [TopologicalSpace S] [TopologicalSpace A] [Fintype Z] + +/-- A **stochastic dynamic program** with finitely many shocks. -/ +structure StochasticDynamicProgram (S A Z : Type*) [TopologicalSpace S] [TopologicalSpace A] + [Fintype Z] where + /-- The actions available at each state. -/ + feasible : S → Set A + isCompact_feasible : ∀ s, IsCompact (feasible s) + feasible_nonempty : ∀ s, (feasible s).Nonempty + upperHemicontinuous_feasible : UpperHemicontinuous feasible + lowerHemicontinuous_feasible : LowerHemicontinuous feasible + /-- The one-period reward. -/ + reward : (S × A) →ᵇ ℝ + /-- The state reached when shock `z` occurs. -/ + transition : Z → C(S × A, S) + /-- The probability that shock `z` occurs. -/ + prob : Z → C(S × A, ℝ) + prob_nonneg : ∀ z p, 0 ≤ prob z p + prob_sum : ∀ p, ∑ z, prob z p = 1 + /-- The factor applied to next-period value. -/ + discount : ℝ≥0 + discount_lt_one : discount < 1 + +namespace StochasticDynamicProgram + +variable (D : StochasticDynamicProgram S A Z) + +/-- The expected continuation value of `v` after choosing `a` at `s`. -/ +noncomputable def expectation (v : S →ᵇ ℝ) (p : S × A) : ℝ := + ∑ z, D.prob z p * v (D.transition z p) + +/-- Current reward plus the discounted expected continuation value. -/ +noncomputable def objective (v : S →ᵇ ℝ) (s : S) (a : A) : ℝ := + D.reward (s, a) + D.discount * D.expectation v (s, a) + +theorem continuous_expectation (v : S →ᵇ ℝ) : Continuous (D.expectation v) := + continuous_finsetSum _ fun z _ => + (D.prob z).continuous.mul (v.continuous.comp (D.transition z).continuous) + +theorem continuous_uncurry_objective (v : S →ᵇ ℝ) : Continuous ↿(D.objective v) := + D.reward.continuous.add (continuous_const.mul (D.continuous_expectation v)) + +/-- An average of values of `v` is no larger than the size of `v`. This is where `p ≥ 0` +and `∑ p = 1` first do their work. -/ +theorem abs_expectation_le (v : S →ᵇ ℝ) (p : S × A) : |D.expectation v p| ≤ ‖v‖ := by + calc |D.expectation v p| ≤ ∑ z, |D.prob z p * v (D.transition z p)| := + Finset.abs_sum_le_sum_abs _ _ + _ = ∑ z, D.prob z p * |v (D.transition z p)| := by + refine Finset.sum_congr rfl fun z _ => ?_ + rw [abs_mul, abs_of_nonneg (D.prob_nonneg z p)] + _ ≤ ∑ z, D.prob z p * ‖v‖ := by + refine Finset.sum_le_sum fun z _ => mul_le_mul_of_nonneg_left ?_ (D.prob_nonneg z p) + simpa [Real.norm_eq_abs] using v.norm_coe_le_norm (D.transition z p) + _ = ‖v‖ := by rw [← Finset.sum_mul, D.prob_sum, one_mul] + +theorem abs_objective_le (v : S →ᵇ ℝ) (s : S) (a : A) : + |D.objective v s a| ≤ ‖D.reward‖ + D.discount * ‖v‖ := by + have h₁ : |D.reward (s, a)| ≤ ‖D.reward‖ := by + simpa [Real.norm_eq_abs] using D.reward.norm_coe_le_norm (s, a) + have h₂ := D.abs_expectation_le v (s, a) + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + rw [abs_le] at h₁ h₂ + simp only [objective, abs_le] + constructor <;> + nlinarith [h₁.1, h₁.2, h₂.1, h₂.2, mul_le_mul_of_nonneg_left h₂.1 hβ, + mul_le_mul_of_nonneg_left h₂.2 hβ] + +/-- The Bellman operator as a bare function. -/ +noncomputable def bellmanFn (v : S →ᵇ ℝ) (s : S) : ℝ := + maxValue (D.objective v) D.feasible s + +theorem le_bellmanFn (v : S →ᵇ ℝ) {s : S} {a : A} (ha : a ∈ D.feasible s) : + D.objective v s a ≤ D.bellmanFn v s := + le_maxValue (D.continuous_uncurry_objective v) (D.isCompact_feasible s) ha + +theorem bellmanFn_le (v : S →ᵇ ℝ) {s : S} {c : ℝ} + (h : ∀ a ∈ D.feasible s, D.objective v s a ≤ c) : D.bellmanFn v s ≤ c := + maxValue_le (D.feasible_nonempty s) h + +theorem abs_bellmanFn_le (v : S →ᵇ ℝ) (s : S) : + |D.bellmanFn v s| ≤ ‖D.reward‖ + D.discount * ‖v‖ := by + obtain ⟨a, ha⟩ := D.feasible_nonempty s + rw [abs_le] + refine ⟨?_, D.bellmanFn_le v fun b _ => (le_abs_self _).trans (D.abs_objective_le v s b)⟩ + exact le_trans (neg_le_of_abs_le (D.abs_objective_le v s a)) (D.le_bellmanFn v ha) + +/-- The **stochastic Bellman operator** on bounded continuous functions. -/ +noncomputable def bellman (v : S →ᵇ ℝ) : S →ᵇ ℝ := + BoundedContinuousFunction.ofNormedAddCommGroup (D.bellmanFn v) + (continuous_maxValue (D.continuous_uncurry_objective v) D.feasible_nonempty + D.isCompact_feasible D.upperHemicontinuous_feasible D.lowerHemicontinuous_feasible) + (‖D.reward‖ + D.discount * ‖v‖) + fun s => by simpa [Real.norm_eq_abs] using D.abs_bellmanFn_le v s + +@[simp] +theorem bellman_apply (v : S →ᵇ ℝ) (s : S) : + D.bellman v s = maxValue (D.objective v) D.feasible s := rfl + +/-- **Blackwell's conditions hold.** Monotonicity is `prob_nonneg`: averaging with +nonnegative weights preserves order. Discounting is `prob_sum`: the weights sum to one, so +raising the continuation value by `c` raises the average by exactly `c`, and the objective +by `β * c`. -/ +theorem blackwell : Blackwell D.discount D.bellman where + monotone := by + intro v w hvw s + have hβ : (0 : ℝ) ≤ D.discount := D.discount.coe_nonneg + have key : ∀ a ∈ D.feasible s, D.objective v s a ≤ D.bellmanFn w s := by + intro a ha + refine le_trans ?_ (D.le_bellmanFn w ha) + have hsum : D.expectation v (s, a) ≤ D.expectation w (s, a) := by + refine Finset.sum_le_sum fun z _ => mul_le_mul_of_nonneg_left ?_ (D.prob_nonneg z _) + simpa using hvw (D.transition z (s, a)) + simp only [objective] + nlinarith + exact D.bellmanFn_le v key + discounting := by + intro v c _ s + have key : ∀ a ∈ D.feasible s, D.objective (v + const S c) s a + ≤ D.bellmanFn v s + D.discount * c := by + intro a ha + have hterm : ∀ z : Z, D.prob z (s, a) * (v + const S c) (D.transition z (s, a)) + = D.prob z (s, a) * v (D.transition z (s, a)) + D.prob z (s, a) * c := fun z => by + simp only [coe_add, const_apply, Pi.add_apply]; ring + have hsum : D.expectation (v + const S c) (s, a) = D.expectation v (s, a) + c := by + simp only [expectation] + rw [Finset.sum_congr rfl fun z _ => hterm z, Finset.sum_add_distrib, + ← Finset.sum_mul, D.prob_sum, one_mul] + have h : D.objective (v + const S c) s a = D.objective v s a + D.discount * c := by + simp only [objective, hsum]; ring + rw [h] + have := D.le_bellmanFn v (s := s) ha + linarith + exact D.bellmanFn_le (v + const S c) key + +/-- The supremum is attained: an optimal action exists at every state. -/ +theorem exists_optimal_action (v : S →ᵇ ℝ) (s : S) : + ∃ a ∈ D.feasible s, D.bellman v s = D.objective v s a ∧ + ∀ b ∈ D.feasible s, D.objective v s b ≤ D.objective v s a := by + obtain ⟨a, ha, hmax⟩ := + argmax_nonempty (D.continuous_uncurry_objective v) (D.feasible_nonempty s) + (D.isCompact_feasible s) + exact ⟨a, ha, + maxValue_eq (D.continuous_uncurry_objective v) (D.isCompact_feasible s) ⟨ha, hmax⟩, + fun b hb => isMaxOn_iff.mp hmax b hb⟩ + +section ValueFunction + +/-- The **value function** of the stochastic program. -/ +noncomputable def valueFunction : S →ᵇ ℝ := D.blackwell.valueFunction D.discount_lt_one + +theorem bellman_valueFunction : D.bellman D.valueFunction = D.valueFunction := + D.blackwell.isFixedPt_valueFunction D.discount_lt_one + +theorem eq_valueFunction {v : S →ᵇ ℝ} (hv : D.bellman v = v) : v = D.valueFunction := + D.blackwell.eq_valueFunction D.discount_lt_one hv + +theorem tendsto_iterate_valueFunction (v : S →ᵇ ℝ) : + Tendsto (fun n => D.bellman^[n] v) atTop (𝓝 D.valueFunction) := + D.blackwell.tendsto_iterate_valueFunction D.discount_lt_one v + +theorem norm_iterate_sub_valueFunction_le (v : S →ᵇ ℝ) (n : ℕ) : + ‖D.bellman^[n] v - D.valueFunction‖ + ≤ (D.discount : ℝ) ^ n / (1 - D.discount) * ‖D.bellman v - v‖ := + D.blackwell.norm_iterate_sub_valueFunction_le D.discount_lt_one v n + +/-- **The stochastic Bellman equation.** At every state there is an optimal action, and the +value is the reward it earns plus the discounted expected value of the successor state. -/ +theorem exists_optimal_policy (s : S) : + ∃ a ∈ D.feasible s, D.valueFunction s + = D.reward (s, a) + + D.discount * ∑ z, D.prob z (s, a) * D.valueFunction (D.transition z (s, a)) := by + obtain ⟨a, ha, heq, -⟩ := D.exists_optimal_action D.valueFunction s + rw [D.bellman_valueFunction] at heq + exact ⟨a, ha, heq⟩ + +end ValueFunction + +end StochasticDynamicProgram + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/Aiyagari.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/Aiyagari.lean new file mode 100644 index 000000000..4da3774ef --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/Aiyagari.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Distribution.Uniqueness + +/-! +# Aiyagari stationary equilibrium + +An interest rate at which the capital households choose to hold equals the capital firms +want to employ. + +Aggregation is the easy half and is done here in full: mean assets under the agent +distribution is the integral of the asset coordinate, which is a BOUNDED CONTINUOUS function +on the compact state space, so aggregate capital is weakly continuous in the distribution. +That is the one place the compactness of the state space pays off directly, and it means no +separate uniform-integrability argument is needed. + +The firm side is left abstract: any continuous capital demand schedule `D` will do, so a +Cobb-Douglas firm with depreciation is one instance among many, and nothing here depends on +which. + +This module takes continuity of capital supply as an explicit hypothesis. The later +`AiyagariContinuity` module proves it for a fixed-cap, zero-borrowing-floor rate family from a +stationary selection that is unique at each rate; its equilibrium theorem also requires a +continuous demand schedule and opposite signs of excess demand at the interval endpoints. +-/ + +@[expose] public section + +open scoped NNReal ENNReal +open Set Filter Topology BoundedContinuousFunction MeasureTheory + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### Aggregation -/ + +theorem continuous_assetCoord : Continuous fun s : P.State => (s.1 : ℝ) := + continuous_subtype_val.comp continuous_fst + +theorem norm_assetCoord_le (s : P.State) : ‖(s.1 : ℝ)‖ ≤ |assetFloor| + |assetCap| := + abs_le.mpr ⟨by linarith [s.1.2.1, neg_abs_le assetFloor, abs_nonneg assetCap], + by linarith [s.1.2.2, le_abs_self assetCap, abs_nonneg assetFloor]⟩ + +/-- The asset coordinate, as a bounded continuous function. Boundedness is exactly the asset +cap, and continuity is immediate; together they are what make aggregate capital behave well +under weak convergence. -/ +noncomputable def assetCoord : P.State →ᵇ ℝ := + ofNormedAddCommGroup (fun s => (s.1 : ℝ)) P.continuous_assetCoord (|assetFloor| + |assetCap|) + P.norm_assetCoord_le + +@[simp] +theorem assetCoord_apply (s : P.State) : P.assetCoord s = (s.1 : ℝ) := rfl + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- **Aggregate capital**: mean assets under an agent distribution. -/ +noncomputable def aggregateCapital (μ : ProbabilityMeasure P.State) : ℝ := + ∫ s, P.assetCoord s ∂(μ : Measure P.State) + +/-- **Aggregation is weakly continuous.** The asset coordinate is bounded and continuous, so +this is exactly what the weak topology is designed to give. -/ +theorem continuous_aggregateCapital : Continuous P.aggregateCapital := + P.continuous_integral_bcf P.assetCoord + +end IncomeFluctuation + +/-! ### Equilibrium -/ + +section Equilibrium + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] {assetFloor assetCap : ℝ} + +/-- **An Aiyagari stationary equilibrium** at interest rate `r`: the household problem +`Pf r` has a stationary agent distribution whose aggregate capital equals the capital +demanded at that rate. -/ +def IsAiyagariEquilibrium (Pf : ℝ → IncomeFluctuation Z assetFloor assetCap) (D : ℝ → ℝ) + (r : ℝ) : Prop := + ∃ μ : ProbabilityMeasure (Pf r).State, + (Pf r).IsStationary μ ∧ (Pf r).aggregateCapital μ = D r + +/-- **Existence of an Aiyagari stationary equilibrium.** + +Given a capital supply schedule `S` that is realised by a stationary distribution at every +interest rate and is continuous, and any continuous capital demand schedule `D`, a rate at +which the two agree exists as soon as excess demand changes sign across the interval. + +Uniqueness of the stationary distribution is what makes `S` a FUNCTION of `r` rather than a +correspondence, and that is what allows the intermediate value theorem to close the argument +in place of a fixed point theorem for correspondences -- Mathlib has no Kakutani. -/ +theorem exists_aiyagari_equilibrium + {Pf : ℝ → IncomeFluctuation Z assetFloor assetCap} {D S : ℝ → ℝ} {rlo rhi : ℝ} (hle : rlo ≤ rhi) + (hS : ∀ r, ∃ μ : ProbabilityMeasure (Pf r).State, + (Pf r).IsStationary μ ∧ (Pf r).aggregateCapital μ = S r) + (hScont : ContinuousOn S (Icc rlo rhi)) (hDcont : ContinuousOn D (Icc rlo rhi)) + (hlo : S rlo ≤ D rlo) (hhi : D rhi ≤ S rhi) : + ∃ r ∈ Icc rlo rhi, IsAiyagariEquilibrium Pf D r := by + have hcont : ContinuousOn (fun r => S r - D r) (Icc rlo rhi) := hScont.sub hDcont + have hmem : (0 : ℝ) ∈ Icc (S rlo - D rlo) (S rhi - D rhi) := + ⟨by linarith, by linarith⟩ + obtain ⟨r, hr, hr0⟩ := intermediate_value_Icc hle hcont hmem + refine ⟨r, hr, ?_⟩ + obtain ⟨μ, hstat, hagg⟩ := hS r + exact ⟨μ, hstat, by rw [hagg]; linarith [sub_eq_zero.mp hr0]⟩ + +end Equilibrium + +end LeanEconomics + +/-! +## Rate continuity and the remaining hypotheses + +`IncomeFluctuation Z assetFloor assetCap` takes the asset bounds as parameters. Fixing them +across a rate family gives a common state space for its stationary distributions. + +For a zero borrowing floor, `IncomeFluctuation.continuousOn_capitalSupply` in +`Equilibrium/AiyagariContinuity.lean` proves rate continuity from a selection of stationary +distributions and uniqueness at every rate of a closed interval. Its +`IncomeFluctuation.exists_equilibrium_of_selection` then applies the intermediate value +theorem, assuming a continuous capital-demand schedule and endpoint sign inequalities. +The stationary selection, its uniqueness and the endpoint inequalities remain explicit +hypotheses of that result. The more abstract theorem in this file also allows other fixed +borrowing floors, while taking continuity of the realised supply schedule as a hypothesis. +-/ diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariContinuity.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariContinuity.lean new file mode 100644 index 000000000..6d28f822a --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariContinuity.lean @@ -0,0 +1,105 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationAugmented +public import LeanPool.AiyagariEquilibrium.Equilibrium.Aiyagari + +/-! +# Capital supply is continuous, and the equilibrium theorem applies + +`exists_aiyagari_equilibrium` has carried one hypothesis since it was proved: that capital +supply moves continuously with the interest rate. This file discharges it. + +Aggregation turns out not to depend on the rate at all -- the asset coordinate is the asset +coordinate whatever the rate, and `aggregateCapital` is equal across rates by `rfl` -- so all +the rate dependence sits in the stationary distribution, and `tendsto_stationary` supplies it. +Capital supply is then a continuous function composed with a convergent selection. + +The family is indexed by the CLAMPED rate, so that a household problem is defined at every +real number while agreeing with the intended one on `[rlo, rhi]`. Without that the family +would need a proof of `0 < 1 + r` at every real `r`, which is false. +-/ + +@[expose] public section + +open Set Filter Topology MeasureTheory + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +-- Continuity in the rate is read off the augmented state space, which is built at a zero +-- borrowing limit; see `IncomeFluctuationAugmented`. +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) {rlo rhi : ℝ} + +/-- The household problem at the clamped rate, defined at every real number. -/ +noncomputable def rateFamily (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (r : ℝ) : + IncomeFluctuation Z (0 : ℝ) assetCap := + P.withRate (clampRate rlo rhi r) (rateOK_of_floor_zero (one_add_clampRate_pos hrlo hle r)) + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem rateFamily_eq (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) {r : ℝ} (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) : P.rateFamily hrlo hle r = P.withRate r hrr := by + unfold rateFamily + congr 1 + exact clampRate_eq hr + +/-- **Capital supply is continuous in the interest rate.** -/ +theorem continuousOn_capitalSupply (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, ∀ μ : ProbabilityMeasure P.State, + (P.withRate r hrr).IsStationary μ → μ = ν r) : + ContinuousOn (fun r => P.aggregateCapital (ν r)) (Icc rlo rhi) := by + intro r₀ hr₀ + have hrr₀ : P.RateOK r₀ := rateOK_of_floor_zero (by have := hr₀.1; linarith) + exact (P.continuous_aggregateCapital.tendsto (ν r₀)).comp + (P.tendsto_stationary hrlo hle hr₀ hrr₀ ν hν (huniq r₀ hr₀ hrr₀)) + +/-- **An Aiyagari stationary equilibrium exists**, with nothing assumed about the household +side beyond a selection of stationary distributions that is unique at each rate. + +Compared with `exists_aiyagari_equilibrium`, the continuity of capital supply is no longer a +hypothesis: it is proved, from continuity of the policy in the rate and weak continuity of +aggregation. What remains assumed is the firm side -- any continuous demand schedule -- and +that excess demand changes sign, which is where the economics of the calibration enters. -/ +theorem exists_equilibrium_of_selection (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, ∀ μ : ProbabilityMeasure P.State, + (P.withRate r hrr).IsStationary μ → μ = ν r) + (D : ℝ → ℝ) (hD : ContinuousOn D (Icc rlo rhi)) + (hlo : P.aggregateCapital (ν rlo) ≤ D rlo) + (hhi : D rhi ≤ P.aggregateCapital (ν rhi)) : + ∃ r ∈ Icc rlo rhi, + IsAiyagariEquilibrium (P.rateFamily hrlo hle) D r := by + classical + set S : ℝ → ℝ := fun r => P.aggregateCapital (ν (clampRate rlo rhi r)) with hS + have hlo' : rlo ∈ Icc rlo rhi := ⟨le_rfl, hle⟩ + have hhi' : rhi ∈ Icc rlo rhi := ⟨hle, le_rfl⟩ + -- on the interval the clamp is inactive, so `S` is the supply schedule + have hSeq : ∀ r ∈ Icc rlo rhi, S r = P.aggregateCapital (ν r) := fun r hr => by + rw [hS]; simp only [clampRate_eq hr] + refine exists_aiyagari_equilibrium (Pf := P.rateFamily hrlo hle) (S := S) hle ?_ ?_ hD + (by rw [hSeq rlo hlo']; exact hlo) (by rw [hSeq rhi hhi']; exact hhi) + · -- a stationary distribution realising `S` at every rate + intro r + refine ⟨ν (clampRate rlo rhi r), ?_, rfl⟩ + have hmem := clampRate_mem hle r + have hrr : P.RateOK (clampRate rlo rhi r) := + rateOK_of_floor_zero (one_add_clampRate_pos hrlo hle r) + have := hν _ hmem hrr + rwa [show P.rateFamily hrlo hle r = P.withRate (clampRate rlo rhi r) hrr from rfl] + · -- and `S` is continuous there + refine (P.continuousOn_capitalSupply hrlo hle ν hν huniq).congr ?_ + exact fun r hr => hSeq r hr + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariUniqueness.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariUniqueness.lean new file mode 100644 index 000000000..87cad9754 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/AiyagariUniqueness.lean @@ -0,0 +1,505 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.LogAiyagari +public import LeanPool.AiyagariEquilibrium.Equilibrium.CapitalSupplyMonotone +public import LeanPool.AiyagariEquilibrium.Equilibrium.Uniqueness +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariContinuity +public import LeanPool.AiyagariEquilibrium.Equilibrium.SignChange + +/-! +# Rate uniqueness for normalized capped-log clearing + +This file combines rate monotonicity of household saving, monotonicity of stationary mean +assets and a strictly decreasing prescribed demand schedule. The log-household constants +support discount `24/25`. + +## The prescribed normalized schedule + +`normalisedDemand α δ r = α / ((1 - α) * (r + δ))` is the capital-to-wage ratio for a +Cobb–Douglas firm per unit of effective labor. The clearing equation used here equates this +schedule to mean household assets. No mean-one endowment condition or wage/asset-cap scaling +bridge to full competitive equilibrium is supplied. The result is uniqueness for this +explicit unit-wage normalized clearing problem. + +## Household conditions + +Transitions are monotone in income, and a lowest-income state is reached with positive +probability in one step from every state. The Tauchen construction verifies these properties +for its arbitrary positive earnings scale. The cap inequality +`2β * maxIncome < (1 - β * (1 + rhi)) * cap` guarantees the required saving bound across +the rate interval; the asset bounds are fixed across that family. +-/ + +@[expose] public section + +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +/-! ### The prescribed normalized demand -/ + +/-- The prescribed unit-wage normalized demand schedule, per unit of effective labor. -/ +noncomputable def normalisedDemand (α δ r : ℝ) : ℝ := α / ((1 - α) * (r + δ)) + +theorem normalisedDemand_strictAntiOn {α δ rlo rhi : ℝ} (hα0 : 0 < α) (hα1 : α < 1) + (hδ : 0 < rlo + δ) : StrictAntiOn (normalisedDemand α δ) (Icc rlo rhi) := by + intro x hx y hy hxy + simp only [normalisedDemand] + have hx0 : 0 < (1 - α) * (x + δ) := mul_pos (by linarith) (by linarith [hx.1]) + have hlt : (1 - α) * (x + δ) < (1 - α) * (y + δ) := + mul_lt_mul_of_pos_left (by linarith) (by linarith) + exact div_lt_div_of_pos_left hα0 hx0 hlt + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-! ### Theorem 1 on the interval, by chaining -/ + +/-- **Saving rises with the rate on `[rlo, rhi]`**, for log utility. Local comparisons from +`crra_policy_mono_withRate_of_minMPC` chained by `monotoneOn_of_local`. For log the minimal MPC is +`1 - β` at every rate, so every cap condition collapses to the one on `hcap`. -/ +theorem log_policy_mono_rateFamily {rlo rhi ε : ℝ} (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) + (hε : 0 < ε) + (hcap : (P.discount : ℝ) * P.maxIncome + + ((P.discount : ℝ) * (1 + rhi) + ε) * assetCap < assetCap) : + ∀ r ∈ Icc rlo rhi, ∀ r' ∈ Icc rlo rhi, r ≤ r' → + ∀ s : ℝ × Z, s.1 ∈ Icc (0 : ℝ) assetCap → + (P.rateFamily hrlo hlohi r).policy s + ≤ (P.rateFamily hrlo hlohi r').policy s := by + have hβ1 : (P.discount : ℝ) < 1 := P.discount_lt_one + intro r hr r' hr' hle s hs + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hmono : MonotoneOn (fun x => (P.rateFamily hrlo hlohi x).policy s) + (Icc rlo rhi) := by + refine monotoneOn_of_local hε ?_ + intro x hx y hy hxy hgapxy + have hxok : P.RateOK x := rateOK_of_floor_zero (by linarith [hx.1]) + have hyok : P.RateOK y := rateOK_of_floor_zero (by linarith [hy.1]) + change (P.rateFamily hrlo hlohi x).policy s + ≤ (P.rateFamily hrlo hlohi y).policy s + rw [P.rateFamily_eq _ _ hx hxok, P.rateFamily_eq _ _ hy hyok] + obtain ⟨a, z⟩ := s + have hκx : 1 - (P.withRate x hxok).minMPC 1 = (P.discount : ℝ) := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; ring + have hκy : 1 - (P.withRate y hyok).minMPC 1 = (P.discount : ℝ) := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; ring + have hκxpos : 0 < (P.withRate x hxok).minMPC 1 := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; linarith + have hκypos : 0 < (P.withRate y hyok).minMPC 1 := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; linarith + have hx_le : (P.discount : ℝ) * (1 + x) ≤ (P.discount : ℝ) * (1 + rhi) := + mul_le_mul_of_nonneg_left (by linarith [hx.2]) hβ.le + have hy_le : (P.discount : ℝ) * (1 + y) ≤ (P.discount : ℝ) * (1 + rhi) := + mul_le_mul_of_nonneg_left (by linarith [hy.2]) hβ.le + have hεcap : (0 : ℝ) ≤ ε * assetCap := mul_nonneg hε.le hcap0 + refine P.crra_policy_mono_withRate_of_minMPC (γ := 1) one_pos le_rfl hu hxok hyok hxy hκxpos + hκypos hβ + ((P.withRate x hxok).positiveConsumptionAll_of_unbounded hunb) + ((P.withRate y hyok).positiveConsumptionAll_of_unbounded hunb) ?_ ?_ ?_ hs z + · rw [hκx] + have := mul_le_mul_of_nonneg_left hx_le hcap0 + nlinarith + · rw [hκy] + have := mul_le_mul_of_nonneg_left hy_le hcap0 + nlinarith + · rw [hκx] + have h1 : ((P.discount : ℝ) * (1 + x) + (y - x)) * assetCap + ≤ ((P.discount : ℝ) * (1 + rhi) + ε) * assetCap := + mul_le_mul_of_nonneg_right (by linarith) hcap0 + linarith + exact hmono hr hr' hle + +/-! ### The Doeblin chain on the interval -/ + +section Measure + +variable [MeasurableSpace Z] [BorelSpace Z] + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- The per-rate corner test, from its worst case at the top of the interval. For log the test +at rate `r` is `β(1+r)(income z₀ + (1+r) a₀) < minIncome`, increasing in `r`. -/ +theorem log_corner_of_top {rlo rhi a₀ : ℝ} {z₀ : Z} (hβ : 0 < (P.discount : ℝ)) (ha₀ : 0 < a₀) + (hcorn : (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) < P.minIncome) + {r : ℝ} (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) : + ((P.withRate r hrr).discount : ℝ) * (1 + (P.withRate r hrr).interest) + * (P.withRate r hrr).minIncome ^ (-(1 : ℝ)) + < ((P.withRate r hrr).income z₀ + (1 + (P.withRate r hrr).interest) * a₀) ^ (-(1 : ℝ)) := by + have hy : 0 < P.income z₀ := lt_of_lt_of_le P.minIncome_pos (P.minIncome_le z₀) + have hmin : 0 < P.minIncome := P.minIncome_pos + have hq : 0 < P.income z₀ + (1 + r) * a₀ := by nlinarith [hrr.1] + have hstep : (P.discount : ℝ) * (1 + r) * (P.income z₀ + (1 + r) * a₀) + ≤ (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) := by + have h1 : (P.discount : ℝ) * (1 + r) ≤ (P.discount : ℝ) * (1 + rhi) := + mul_le_mul_of_nonneg_left (by linarith [hr.2]) hβ.le + have h2 : P.income z₀ + (1 + r) * a₀ ≤ P.income z₀ + (1 + rhi) * a₀ := by + nlinarith [hr.2] + exact mul_le_mul h1 h2 hq.le (mul_nonneg hβ.le (by linarith [hrr.1, hr.2])) + simp only [IncomeFluctuation.withRate_discount, IncomeFluctuation.withRate_interest, + IncomeFluctuation.withRate_minIncome, IncomeFluctuation.withRate_income, + Real.rpow_neg_one] + rw [mul_inv_lt_iff₀ hmin, inv_mul_eq_div, lt_div_iff₀ hq] + linarith + +/-- **A unique stationary distribution at every rate in `[0, rhi]`**, for log utility. -/ +theorem log_existsUnique_isStationary {rlo rhi a₀ : ℝ} (hu : P.u = crraUtility 1) + (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hcap : (P.discount : ℝ) * (P.maxIncome + (1 + rhi) * assetCap) < assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorn : (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) < P.minIncome) + {r : ℝ} (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) : + ∃! μ : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary μ := by + have hβ1 : (P.discount : ℝ) < 1 := P.discount_lt_one + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hκ : 1 - (P.withRate r hrr).minMPC 1 = (P.discount : ℝ) := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; ring + have hκpos : 0 < (P.withRate r hrr).minMPC 1 := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; linarith + refine (P.withRate r hrr).crra_existsUnique_isStationary_of_euler_corner_monotone (γ := 1) + one_pos hu rfl hκpos hβ (by simpa using + (show (P.discount : ℝ) * (1 + r) < 1 by nlinarith [hr.2, hβ])) + (P.monotoneTransitions_withRate hmono hrr) + ((P.withRate r hrr).positiveConsumptionAll_of_unbounded hunb) ?_ hz₀ hreach ha₀ hle + (P.log_corner_of_top hβ ha₀ hcorn hr hrr) + rw [hκ] + simp only [IncomeFluctuation.withRate_maxIncome, IncomeFluctuation.withRate_interest] + have := mul_le_mul_of_nonneg_left (show (1 + r) * assetCap ≤ (1 + rhi) * assetCap from + mul_le_mul_of_nonneg_right (by linarith [hr.2]) hcap0) hβ.le + nlinarith + +/-- **Convergence to it from any start**, at every rate in `[0, rhi]`. -/ +theorem log_tendsto_pushProb {rlo rhi a₀ : ℝ} (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hcap : (P.discount : ℝ) * (P.maxIncome + (1 + rhi) * assetCap) < assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorn : (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) < P.minIncome) + {r : ℝ} (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) + (μ₀ : ProbabilityMeasure P.State) {μ : ProbabilityMeasure P.State} + (hμ : (P.withRate r hrr).IsStationary μ) : + Tendsto (fun m => (P.withRate r hrr).pushProb^[m] μ₀) atTop (𝓝 μ) := by + have hβ1 : (P.discount : ℝ) < 1 := P.discount_lt_one + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hκ : 1 - (P.withRate r hrr).minMPC 1 = (P.discount : ℝ) := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; ring + have hκpos : 0 < (P.withRate r hrr).minMPC 1 := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; linarith + refine (P.withRate r hrr).crra_tendsto_pushProb_of_euler_corner_monotone (γ := 1) + one_pos hu rfl hκpos hβ (by simpa using + (show (P.discount : ℝ) * (1 + r) < 1 by nlinarith [hr.2, hβ])) + (P.monotoneTransitions_withRate hmono hrr) + ((P.withRate r hrr).positiveConsumptionAll_of_unbounded hunb) ?_ hz₀ hreach ha₀ hle + (P.log_corner_of_top hβ ha₀ hcorn hr hrr) μ₀ hμ + rw [hκ] + simp only [IncomeFluctuation.withRate_maxIncome, IncomeFluctuation.withRate_interest] + have := mul_le_mul_of_nonneg_left (show (1 + r) * assetCap ≤ (1 + rhi) * assetCap from + mul_le_mul_of_nonneg_right (by linarith [hr.2]) hcap0) hβ.le + nlinarith + +/-! ### Theorem 2: capital supply is monotone -/ + +/-- **Capital supply rises with the rate on `[0, rhi]`**, for log utility, along any selection of +stationary distributions. Light's Theorems 1 and 2. -/ +theorem log_monotoneOn_capitalSupply {rlo rhi ε a₀ : ℝ} (hu : P.u = crraUtility 1) + (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hε : 0 < ε) + (hcap : (P.discount : ℝ) * P.maxIncome + + ((P.discount : ℝ) * (1 + rhi) + ε) * assetCap < assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorn : (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) < P.minIncome) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) : + MonotoneOn (fun r => P.aggregateCapital (ν r)) (Icc rlo rhi) := by + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hcap' : (P.discount : ℝ) * (P.maxIncome + (1 + rhi) * assetCap) < assetCap := by + have := mul_nonneg hε.le hcap0 + nlinarith + refine P.monotoneOn_capitalSupply_of_policy_mono (P.rateFamily hrlo hlohi) + (fun r r' z z' => rfl) + (P.log_policy_mono_rateFamily hu hunb hβ hrlo hlohi hε hcap) + ⟨Measure.dirac (Classical.ofNonempty : P.State), inferInstance⟩ ν ?_ + intro r hr + have hrr : P.RateOK r := rateOK_of_floor_zero (by linarith [hr.1]) + rw [P.rateFamily_eq _ _ hr hrr] + exact P.log_tendsto_pushProb hu hunb hβ hβR hcap' hmono hz₀ hreach ha₀ hle hcorn hr hrr _ + (hν r hr hrr) + +/-! ### Theorem 3: single crossing -/ + +/-- **The normalized clearing rate is unique on `[rlo, rhi]`.** Two rates with stationary +household distributions whose mean assets equal the prescribed `normalisedDemand α δ r` +have the same rate, under the stated cap, transition and impatience hypotheses. -/ +theorem log_equilibriumRate_unique {rlo rhi ε a₀ α δ : ℝ} (hα0 : 0 < α) (hα1 : α < 1) + (hδ : 0 < rlo + δ) + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hε : 0 < ε) + (hcap : (P.discount : ℝ) * P.maxIncome + + ((P.discount : ℝ) * (1 + rhi) + ε) * assetCap < assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hreach : ∀ z, 0 < P.transitionMatrix z z₀) (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorn : (P.discount : ℝ) * (1 + rhi) * (P.income z₀ + (1 + rhi) * a₀) < P.minIncome) + {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Icc rlo rhi) (h₂ : r₂ ∈ Icc rlo rhi) + (hrr₁ : P.RateOK r₁) (hrr₂ : P.RateOK r₂) + {μ₁ μ₂ : ProbabilityMeasure P.State} + (hμ₁ : (P.withRate r₁ hrr₁).IsStationary μ₁) (hμ₂ : (P.withRate r₂ hrr₂).IsStationary μ₂) + (he₁ : P.aggregateCapital μ₁ = normalisedDemand α δ r₁) + (he₂ : P.aggregateCapital μ₂ = normalisedDemand α δ r₂) : r₁ = r₂ := by + classical + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hcap' : (P.discount : ℝ) * (P.maxIncome + (1 + rhi) * assetCap) < assetCap := by + have := mul_nonneg hε.le hcap0 + nlinarith + -- the selection of stationary distributions, defined through the clamped rate + have hE : ∀ r : ℝ, ∃! μ : ProbabilityMeasure P.State, + (P.withRate (clampRate rlo rhi r) + (rateOK_of_floor_zero (one_add_clampRate_pos hrlo hlohi r))).IsStationary μ := + fun r => P.log_existsUnique_isStationary hu hunb hβ hβR hcap' hmono hz₀ hreach ha₀ hle + hcorn (clampRate_mem hlohi r) _ + set ν : ℝ → ProbabilityMeasure P.State := fun r => (hE r).choose with hνdef + have hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r) := by + intro r hr hrr + have hc : clampRate rlo rhi r = r := clampRate_eq hr + rw [← P.withRate_congr hc _ hrr] + exact (hE r).choose_spec.1 + have hid : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, ∀ μ : ProbabilityMeasure P.State, + (P.withRate r hrr).IsStationary μ → μ = ν r := by + intro r hr hrr μ hμ + have hc : clampRate rlo rhi r = r := clampRate_eq hr + refine (hE r).choose_spec.2 μ ?_ + rw [P.withRate_congr hc _ hrr] + exact hμ + rw [hid r₁ h₁ hrr₁ μ₁ hμ₁] at he₁ + rw [hid r₂ h₂ hrr₂ μ₂ hμ₂] at he₂ + exact eq_of_monotoneOn_of_strictAntiOn + (P.log_monotoneOn_capitalSupply hu hunb hβ hrlo hlohi hβR hε hcap hmono hz₀ hreach ha₀ hle + hcorn ν hν) + (normalisedDemand_strictAntiOn hα0 hα1 hδ) h₁ h₂ he₁ he₂ + +/-! ### With the constants constructed + +The interval, the step and the corner level are not primitives of the economy. Given only that +the cap clears `2β · maxIncome / (1 - β(1+rhi))`, choose the step as half the room under `1` and +the corner level as small as the corner test needs. -/ + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- A slack cap supplies a positive comparison step and a corner level meeting both bounds. -/ +theorem exists_log_cap_corner {R : ℝ} (hβ : 0 < (P.discount : ℝ)) (hR : 0 < R) + (hcap : 2 * (P.discount : ℝ) * P.maxIncome < (1 - (P.discount : ℝ) * R) * assetCap) + {z₀ : Z} (hmin : P.income z₀ = P.minIncome) : + ∃ ε > 0, ∃ a₀ > 0, a₀ ≤ assetCap ∧ + (P.discount : ℝ) * P.maxIncome + + ((P.discount : ℝ) * R + ε) * assetCap < assetCap ∧ + (P.discount : ℝ) * R * (P.income z₀ + R * a₀) < P.minIncome := by + set β : ℝ := (P.discount : ℝ) with hβdef + have hmaxpos : 0 < P.maxIncome := lt_of_lt_of_le P.minIncome_pos P.minIncome_le_maxIncome + have hproduct : 0 < (1 - β * R) * assetCap := lt_trans (by positivity) hcap + have hroom : 0 < 1 - β * R := pos_of_mul_pos_left hproduct P.assetCap_nonneg + have hcap0 : 0 < assetCap := pos_of_mul_pos_right hproduct hroom.le + have hminpos : 0 < P.minIncome := P.minIncome_pos + -- the step: half the room under `1` + set ε : ℝ := (1 - β * R) / 2 with hεdef + have hε : 0 < ε := by rw [hεdef]; linarith + -- the corner level: small enough for the corner test, and below the cap + set A : ℝ := P.minIncome * (1 - β * R) / (2 * β * R ^ 2) with hAdef + have hA : 0 < A := by rw [hAdef]; positivity + set a₀ : ℝ := min assetCap A with ha₀def + have ha₀ : 0 < a₀ := lt_min hcap0 hA + have hle : a₀ ≤ assetCap := min_le_left _ _ + have ha₀A : a₀ ≤ A := min_le_right _ _ + -- the cap condition with the chosen step + have hcapε : β * P.maxIncome + (β * R + ε) * assetCap < assetCap := by + rw [hεdef] + nlinarith [hcap] + -- the corner test at the top of the interval + have hcorn : β * R * (P.income z₀ + R * a₀) < P.minIncome := by + rw [hmin] + have hkey : β * R * (R * A) = P.minIncome * (1 - β * R) / 2 := by + rw [hAdef]; field_simp; try ring + have hmono : β * R * (R * a₀) ≤ β * R * (R * A) := + mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left ha₀A hR.le) (by positivity) + have : β * R * (P.minIncome + R * a₀) = β * R * P.minIncome + β * R * (R * a₀) := by ring + rw [this] + nlinarith [hkey, hmono, hroom, hminpos] + exact ⟨ε, hε, a₀, ha₀, hle, hcapε, hcorn⟩ + +/-- **Uniqueness from one inequality on the cap.** -/ +theorem log_equilibriumRate_unique_of_cap {rlo rhi α δ : ℝ} (hα0 : 0 < α) (hα1 : α < 1) + (hδ : 0 < rlo + δ) + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hcap : 2 * (P.discount : ℝ) * P.maxIncome < (1 - (P.discount : ℝ) * (1 + rhi)) * assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Icc rlo rhi) (h₂ : r₂ ∈ Icc rlo rhi) + (hrr₁ : P.RateOK r₁) (hrr₂ : P.RateOK r₂) + {μ₁ μ₂ : ProbabilityMeasure P.State} + (hμ₁ : (P.withRate r₁ hrr₁).IsStationary μ₁) (hμ₂ : (P.withRate r₂ hrr₂).IsStationary μ₂) + (he₁ : P.aggregateCapital μ₁ = normalisedDemand α δ r₁) + (he₂ : P.aggregateCapital μ₂ = normalisedDemand α δ r₂) : r₁ = r₂ := by + obtain ⟨ε, hε, a₀, ha₀, hle, hcapε, hcorn⟩ := + P.exists_log_cap_corner hβ (R := 1 + rhi) (by linarith only [hrlo, hlohi]) hcap hmin + exact P.log_equilibriumRate_unique hα0 hα1 hδ hu hunb hβ hrlo hlohi hβR hε hcapε hmono hz₀ + hreach ha₀ hle hcorn h₁ h₂ hrr₁ hrr₂ hμ₁ hμ₂ he₁ he₂ + +/-! ### Negative rates + +The uniqueness theorems live on `[0, rhi]`. Below zero the minimal-MPC ceiling on capital +supply, `β · maxIncome / (1 - β(1+r))`, still holds for log utility — `κ = 1 - β` at every +rate — while Cobb--Douglas demand blows up as `r ↓ -δ`. Where the ceiling is below demand there +is no equilibrium. -/ + +/-- **The log capital ceiling at any admissible rate** with `β(1+r) < 1`: no sign restriction on +`r`, because `κ = 1 - β` (`minMPC_one`). -/ +theorem log_aggregateCapital_le {r : ℝ} (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrr : P.RateOK r) + (hβR : (P.discount : ℝ) * (1 + r) < 1) + (hthr : (P.discount : ℝ) * (P.maxIncome + (1 + r) * assetCap) < assetCap) + {μ : ProbabilityMeasure P.State} (hμ : (P.withRate r hrr).IsStationary μ) : + P.aggregateCapital μ + ≤ (P.discount : ℝ) * P.maxIncome / (1 - (P.discount : ℝ) * (1 + r)) := by + have hβ1 : (P.discount : ℝ) < 1 := P.discount_lt_one + have hκ : 1 - (P.withRate r hrr).minMPC 1 = (P.discount : ℝ) := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; ring + have hκpos : 0 < (P.withRate r hrr).minMPC 1 := by + rw [IncomeFluctuation.minMPC_one, IncomeFluctuation.withRate_discount]; linarith + have hβR' : ((P.withRate r hrr).discount : ℝ) * (1 + (P.withRate r hrr).interest) < 1 := hβR + have hthr' : (1 - (P.withRate r hrr).minMPC 1) + * ((P.withRate r hrr).maxIncome + (1 + (P.withRate r hrr).interest) * assetCap) + < assetCap := by + rw [hκ]; exact hthr + have hpc : (P.withRate r hrr).PositiveConsumptionAll := + (P.withRate r hrr).positiveConsumptionAll_of_unbounded hunb + have hposIt : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < (P.withRate r hrr).consumptionFnOf + (((P.withRate r hrr).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc _ ((P.withRate r hrr).concaveSlices_iterate_zero n) z a ha + have hslackIt : ∀ n : ℕ, ∀ x ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r hrr).policyOf + (((P.withRate r hrr).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (x, z) < assetCap := + fun n x hx z => ((P.withRate r hrr).crra_minMPC_of_cap one_pos hu rfl hκpos hβ + hposIt hthr' n).2 x hx z + have hkey := (P.withRate r hrr).crra_aggregateCapital_le_of_minMPC one_pos hu hκpos hβ hβR' + hposIt hslackIt hμ + rw [hκ] at hkey + exact hkey + +/-- **No equilibrium where the ceiling is below demand.** -/ +theorem log_no_equilibrium_of_ceiling {α δ r : ℝ} (hu : P.u = crraUtility 1) + (hunb : P.Unbounded) (hβ : 0 < (P.discount : ℝ)) + (hrr : P.RateOK r) (hβR : (P.discount : ℝ) * (1 + r) < 1) + (hthr : (P.discount : ℝ) * (P.maxIncome + (1 + r) * assetCap) < assetCap) + (hD : (P.discount : ℝ) * P.maxIncome / (1 - (P.discount : ℝ) * (1 + r)) + < normalisedDemand α δ r) + {μ : ProbabilityMeasure P.State} (hμ : (P.withRate r hrr).IsStationary μ) : + P.aggregateCapital μ ≠ normalisedDemand α δ r := fun h => by + have := P.log_aggregateCapital_le hu hunb hβ hrr hβR hthr hμ + linarith + +/-! ### Normalized clearing with the Aiyagari (1994) log calibration + +The constants are `β = 0.96`, `α = 0.36` and `δ = 0.08`, with log utility, no borrowing, +and monotone earnings transitions reaching a lowest-income state with positive probability +in one step. The time-preference rate is `λ = 1/0.96 - 1 = 1/24`. The theorems concern the +prescribed normalized equation on intervals `-δ < rlo ≤ rhi < λ`; they do not identify it +with full competitive market clearing for arbitrary household earnings scales. -/ + +/-- **The calibrated normalized clearing rate is unique on `[rlo, rhi]`.** The interval is +above `-δ` and below the time-preference rate `1/24`. Earnings transitions are monotone and +reach a lowest-income state in one step; the asset cap must exceed +`(48/25) · maxIncome / (1 - (24/25)(1+rhi))`. -/ +theorem aiyagari1994_log_equilibriumRate_unique {rlo rhi : ℝ} + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) (hβ : (P.discount : ℝ) = 24 / 25) + (hrlo : -2 / 25 < rlo) (hlohi : rlo ≤ rhi) (hlam : rhi < 1 / 24) + (hcap : 2 * (24 / 25) * P.maxIncome < (1 - 24 / 25 * (1 + rhi)) * assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Icc rlo rhi) (h₂ : r₂ ∈ Icc rlo rhi) + (hrr₁ : P.RateOK r₁) (hrr₂ : P.RateOK r₂) + {μ₁ μ₂ : ProbabilityMeasure P.State} + (hμ₁ : (P.withRate r₁ hrr₁).IsStationary μ₁) (hμ₂ : (P.withRate r₂ hrr₂).IsStationary μ₂) + (he₁ : P.aggregateCapital μ₁ = normalisedDemand (9 / 25) (2 / 25) r₁) + (he₂ : P.aggregateCapital μ₂ = normalisedDemand (9 / 25) (2 / 25) r₂) : r₁ = r₂ := + P.log_equilibriumRate_unique_of_cap (by norm_num) (by norm_num) (by linarith) hu hunb + (by rw [hβ]; norm_num) (by linarith) hlohi (by rw [hβ]; linarith) + (by rw [hβ]; exact hcap) hmono hz₀ hmin hreach h₁ h₂ hrr₁ hrr₂ hμ₁ hμ₂ he₁ he₂ + +/-- **Two normalized clearing rates in `(-δ, λ)` coincide.** The interval version is applied +to `[min r₁ r₂, max r₁ r₂]`; the cap condition is required at the larger rate. The clearing +hypotheses equate stationary mean household assets to the prescribed normalized demand. -/ +theorem aiyagari1994_log_equilibriumRate_unique_Ioo + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) (hβ : (P.discount : ℝ) = 24 / 25) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Ioo (-2 / 25 : ℝ) (1 / 24)) (h₂ : r₂ ∈ Ioo (-2 / 25 : ℝ) (1 / 24)) + (hcap : 2 * (24 / 25) * P.maxIncome < (1 - 24 / 25 * (1 + max r₁ r₂)) * assetCap) + (hrr₁ : P.RateOK r₁) (hrr₂ : P.RateOK r₂) + {μ₁ μ₂ : ProbabilityMeasure P.State} + (hμ₁ : (P.withRate r₁ hrr₁).IsStationary μ₁) (hμ₂ : (P.withRate r₂ hrr₂).IsStationary μ₂) + (he₁ : P.aggregateCapital μ₁ = normalisedDemand (9 / 25) (2 / 25) r₁) + (he₂ : P.aggregateCapital μ₂ = normalisedDemand (9 / 25) (2 / 25) r₂) : r₁ = r₂ := + P.aiyagari1994_log_equilibriumRate_unique hu hunb hβ (rlo := min r₁ r₂) (rhi := max r₁ r₂) + (lt_min h₁.1 h₂.1) min_le_max (max_lt h₁.2 h₂.2) hcap hmono hz₀ hmin hreach + ⟨min_le_left _ _, le_max_left _ _⟩ ⟨min_le_right _ _, le_max_right _ _⟩ + hrr₁ hrr₂ hμ₁ hμ₂ he₁ he₂ + +/-- **No normalized clearing at a negative rate where the saving ceiling is below demand.** +The ceiling–demand inequality uses the calibrated constants and is polynomial in `r` and +`maxIncome`, so it can be checked for any earnings grid. -/ +theorem aiyagari1994_log_no_equilibrium_of_ceiling {r : ℝ} + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) (hβ : (P.discount : ℝ) = 24 / 25) + (hcap : 24 * P.maxIncome < assetCap) (hlo : -2 / 25 < r) (hr0 : r ≤ 0) (hrr : P.RateOK r) + (hD : 24 / 25 * P.maxIncome * (r + 2 / 25) < 9 / 16 * (1 - 24 / 25 * (1 + r))) + {μ : ProbabilityMeasure P.State} (hμ : (P.withRate r hrr).IsStationary μ) : + P.aggregateCapital μ ≠ normalisedDemand (9 / 25) (2 / 25) r := by + have hcap0 : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hmax : 0 < P.maxIncome := lt_of_lt_of_le P.minIncome_pos P.minIncome_le_maxIncome + refine P.log_no_equilibrium_of_ceiling hu hunb (by rw [hβ]; norm_num) + hrr (by rw [hβ]; linarith) ?_ ?_ hμ + · rw [hβ] + have := mul_nonneg (neg_nonneg.mpr hr0) hcap0 + nlinarith + · rw [hβ] + simp only [normalisedDemand] + have h1 : (0 : ℝ) < 1 - 24 / 25 * (1 + r) := by linarith + have h2 : (0 : ℝ) < (1 - 9 / 25) * (r + 2 / 25) := by nlinarith + rw [div_lt_div_iff₀ h1 h2] + nlinarith [hD] + +/-- **No normalized clearing on `(-δ, -1/20]`** when household income is at most `3/2` and +the stated asset-cap bound holds. The income bound is an explicit hypothesis in household +units; it is not inferred from a mean-one endowment normalization. -/ +theorem aiyagari1994_log_no_equilibrium_below {r : ℝ} + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) (hβ : (P.discount : ℝ) = 24 / 25) + (hcap : 24 * P.maxIncome < assetCap) (hmax : P.maxIncome ≤ 3 / 2) + (hr : r ∈ Ioc (-2 / 25 : ℝ) (-1 / 20)) (hrr : P.RateOK r) + {μ : ProbabilityMeasure P.State} (hμ : (P.withRate r hrr).IsStationary μ) : + P.aggregateCapital μ ≠ normalisedDemand (9 / 25) (2 / 25) r := by + refine P.aiyagari1994_log_no_equilibrium_of_ceiling hu hunb hβ hcap hr.1 (by linarith [hr.2]) + hrr ?_ hμ + have hx : (0 : ℝ) ≤ r + 2 / 25 := by linarith [hr.1] + have := mul_le_mul_of_nonneg_right hmax hx + nlinarith [hr.2] + +end Measure + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/Bisection.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/Bisection.lean new file mode 100644 index 000000000..b9e293668 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/Bisection.lean @@ -0,0 +1,255 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariUniqueness + +/-! +# Bisection for a monotone zero and normalized clearing + +Bisection evaluates excess supply at the midpoint of a bracket and keeps the half containing +the zero. Hajek and Livesay (2019) discuss stability of best-response fixed points in mean field +games. Here, a decreasing inverse-demand map composed with an increasing supply map can reverse +order, so raw iteration `r ↦ r_d(K_s(r))` may oscillate. This file certifies bisection. + +## The abstract statement + +`bisect f a b n` is the bracket after `n` steps. If `f` is STRICTLY MONOTONE on `[a, b]` and +vanishes at `r ∈ [a, b]`, then every bracket contains `r` (`bisect_mem_of_strictMonoOn`), the +width halves each step (`bisect_width`), and the endpoints converge to `r` +(`tendsto_bisect_fst`). No continuity and no sign condition at the ends are used: strict +monotonicity alone decides which half the zero is in. The midpoint after `n` steps is within +`(b - a)/2^(n+1)` of `r` (`abs_bisect_mid_sub_le`). + +## The instance + +For the capped log-household calibration, stationary mean assets are monotone in the rate +under the stated hypotheses, and the prescribed normalized demand is strictly decreasing. +Thus normalized excess supply is strictly increasing. Given a stationary distribution and a +rate solving that explicit normalized clearing equation, `aiyagari1994_log_bisection` proves +that bisection brackets the rate and converges to it. Existence of such a solution is an input. + +## Normalized clearing scope + +The economic endpoints use the prescribed equation +`aggregateCapital μ = normalisedDemand (9/25) (2/25) r`, where the right side is +`(9/25) / ((1 - 9/25) * (r + 2/25))`. This is a unit-wage normalized clearing problem. +The household earnings scale is arbitrary; no mean-one endowment condition or bridge from +these units and the fixed asset cap to full competitive market clearing is established here. +The uniqueness and bisection claims concern this explicit normalized equation. + +-/ + +@[expose] public section + +open Set Filter Topology Function MeasureTheory + +namespace LeanEconomics + +/-! ### Bisection on the line -/ + +/-- One bisection step on a bracket `(a, b)`: if `f ≤ 0` at the midpoint the zero of an +increasing `f` is to the right, so keep `(mid, b)`; otherwise keep `(a, mid)`. -/ +noncomputable def bisectStep (f : ℝ → ℝ) (p : ℝ × ℝ) : ℝ × ℝ := + if f ((p.1 + p.2) / 2) ≤ 0 then ((p.1 + p.2) / 2, p.2) else (p.1, (p.1 + p.2) / 2) + +/-- The bracket after `n` bisection steps from `(a, b)`. -/ +noncomputable def bisect (f : ℝ → ℝ) (a b : ℝ) (n : ℕ) : ℝ × ℝ := (bisectStep f)^[n] (a, b) + +theorem bisect_succ (f : ℝ → ℝ) (a b : ℝ) (n : ℕ) : + bisect f a b (n + 1) = bisectStep f (bisect f a b n) := + iterate_succ_apply' _ _ _ + +theorem bisectStep_width (f : ℝ → ℝ) (p : ℝ × ℝ) : + (bisectStep f p).2 - (bisectStep f p).1 = (p.2 - p.1) / 2 := by + unfold bisectStep + split_ifs <;> simp only <;> ring + +/-- **The bracket halves at every step.** -/ +theorem bisect_width (f : ℝ → ℝ) (a b : ℝ) (n : ℕ) : + (bisect f a b n).2 - (bisect f a b n).1 = (b - a) / 2 ^ n := by + induction n with + | zero => simp [bisect] + | succ k ih => rw [bisect_succ, bisectStep_width, ih, pow_succ, div_div] + +/-- **Every bracket contains the zero of a strictly increasing function**, and stays inside the +initial bracket. -/ +theorem bisect_mem_of_strictMonoOn {f : ℝ → ℝ} {a b r : ℝ} (hf : StrictMonoOn f (Icc a b)) + (hr : r ∈ Icc a b) (hfr : f r = 0) (n : ℕ) : + a ≤ (bisect f a b n).1 ∧ (bisect f a b n).2 ≤ b ∧ + r ∈ Icc (bisect f a b n).1 (bisect f a b n).2 := by + induction n with + | zero => exact ⟨le_rfl, le_rfl, hr⟩ + | succ k ih => + obtain ⟨ha, hb, hr1, hr2⟩ := ih + rw [bisect_succ] + set p := bisect f a b k with hp + have hm1 : p.1 ≤ (p.1 + p.2) / 2 := by linarith + have hm2 : (p.1 + p.2) / 2 ≤ p.2 := by linarith + have hmem : (p.1 + p.2) / 2 ∈ Icc a b := ⟨by linarith, by linarith⟩ + unfold bisectStep + split_ifs with h + · -- `f ≤ 0` at the midpoint: the zero is at or to the right of it + refine ⟨by linarith, hb, ?_, hr2⟩ + by_contra hlt + push Not at hlt + have := hf hr hmem hlt + linarith + · -- `0 < f` at the midpoint: the zero is at or to the left of it + push Not at h + refine ⟨ha, by linarith, hr1, ?_⟩ + by_contra hlt + push Not at hlt + have := hf hmem hr hlt + linarith + +/-- **The midpoint after `n` steps is within `(b - a)/2^(n+1)` of the zero.** -/ +theorem abs_bisect_mid_sub_le {f : ℝ → ℝ} {a b r : ℝ} (hf : StrictMonoOn f (Icc a b)) + (hr : r ∈ Icc a b) (hfr : f r = 0) (n : ℕ) : + |((bisect f a b n).1 + (bisect f a b n).2) / 2 - r| ≤ (b - a) / 2 ^ (n + 1) := by + obtain ⟨-, -, h1, h2⟩ := bisect_mem_of_strictMonoOn hf hr hfr n + have hw := bisect_width f a b n + rw [abs_le, pow_succ, ← div_div] + constructor <;> linarith + +/-- The bracket width tends to zero. -/ +theorem tendsto_bisect_width (a b : ℝ) : + Tendsto (fun n : ℕ => (b - a) / 2 ^ n) atTop (𝓝 0) := by + have h := (tendsto_pow_atTop_nhds_zero_of_lt_one (by norm_num : (0 : ℝ) ≤ 1 / 2) + (by norm_num : (1 / 2 : ℝ) < 1)).const_mul (b - a) + rw [mul_zero] at h + refine h.congr fun n => ?_ + rw [one_div_pow, div_eq_mul_one_div (b - a)] + +/-- **The left endpoints converge to the zero.** -/ +theorem tendsto_bisect_fst {f : ℝ → ℝ} {a b r : ℝ} (hf : StrictMonoOn f (Icc a b)) + (hr : r ∈ Icc a b) (hfr : f r = 0) : + Tendsto (fun n => (bisect f a b n).1) atTop (𝓝 r) := by + rw [tendsto_iff_dist_tendsto_zero] + refine squeeze_zero (fun n => dist_nonneg) (fun n => ?_) (tendsto_bisect_width a b) + obtain ⟨-, -, h1, h2⟩ := bisect_mem_of_strictMonoOn hf hr hfr n + have hw := bisect_width f a b n + rw [Real.dist_eq, abs_le] + constructor <;> linarith + +/-- **The right endpoints converge to the zero.** -/ +theorem tendsto_bisect_snd {f : ℝ → ℝ} {a b r : ℝ} (hf : StrictMonoOn f (Icc a b)) + (hr : r ∈ Icc a b) (hfr : f r = 0) : + Tendsto (fun n => (bisect f a b n).2) atTop (𝓝 r) := by + rw [tendsto_iff_dist_tendsto_zero] + refine squeeze_zero (fun n => dist_nonneg) (fun n => ?_) (tendsto_bisect_width a b) + obtain ⟨-, -, h1, h2⟩ := bisect_mem_of_strictMonoOn hf hr hfr n + have hw := bisect_width f a b n + rw [Real.dist_eq, abs_le] + constructor <;> linarith + +/-- Monotone supply minus strictly decreasing demand is strictly increasing. -/ +theorem strictMonoOn_sub_of_monotoneOn_of_strictAntiOn {S D : ℝ → ℝ} {s : Set ℝ} + (hS : MonotoneOn S s) (hD : StrictAntiOn D s) : StrictMonoOn (fun r => S r - D r) s := + fun x hx y hy hxy => by + have := hS hx hy hxy.le + have := hD hx hy hxy + simp only + linarith + +/-! ### The log Aiyagari instance -/ + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-- **Excess supply along a selection of stationary distributions**: capital supplied under +`ν r` minus capital demanded at `r`. -/ +noncomputable def excessSupply (ν : ℝ → ProbabilityMeasure P.State) (D : ℝ → ℝ) (r : ℝ) : ℝ := + P.aggregateCapital (ν r) - D r + +/-- The two facts about a selection that the bisection needs, from one inequality on the cap: +supply along it is monotone, and it is the ONLY stationary distribution at each rate. The +constants are chosen as in `log_equilibriumRate_unique_of_cap`. -/ +theorem log_selection_of_cap {rlo rhi : ℝ} + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hcap : 2 * (P.discount : ℝ) * P.maxIncome < (1 - (P.discount : ℝ) * (1 + rhi)) * assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) : + MonotoneOn (fun r => P.aggregateCapital (ν r)) (Icc rlo rhi) ∧ + ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, ∀ μ : ProbabilityMeasure P.State, + (P.withRate r hrr).IsStationary μ → μ = ν r := by + obtain ⟨ε, hε, a₀, ha₀, hle, hcapε, hcorn⟩ := + P.exists_log_cap_corner hβ (R := 1 + rhi) (by linarith only [hrlo, hlohi]) hcap hmin + have hcap' : (P.discount : ℝ) * (P.maxIncome + (1 + rhi) * assetCap) < assetCap := by + nlinarith only [hcapε, mul_nonneg hε.le P.assetCap_nonneg] + refine ⟨P.log_monotoneOn_capitalSupply hu hunb hβ hrlo hlohi hβR hε hcapε hmono hz₀ hreach + ha₀ hle hcorn ν hν, fun r hr hrr μ hμ => ?_⟩ + exact (P.log_existsUnique_isStationary hu hunb hβ hβR hcap' hmono hz₀ hreach ha₀ hle hcorn + hr hrr).unique hμ (hν r hr hrr) + +/-- **Bisection on excess supply brackets and converges to the equilibrium rate of the log +economy**, along any selection of stationary distributions. -/ +theorem log_bisection_of_cap {rlo rhi α δ : ℝ} (hα0 : 0 < α) (hα1 : α < 1) (hδ : 0 < rlo + δ) + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) + (hβ : 0 < (P.discount : ℝ)) (hrlo : 0 < 1 + rlo) + (hlohi : rlo ≤ rhi) (hβR : (P.discount : ℝ) * (1 + rhi) < 1) + (hcap : 2 * (P.discount : ℝ) * P.maxIncome < (1 - (P.discount : ℝ) * (1 + rhi)) * assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) + {r : ℝ} (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) {μ : ProbabilityMeasure P.State} + (hμ : (P.withRate r hrr).IsStationary μ) + (he : P.aggregateCapital μ = normalisedDemand α δ r) : + (∀ n, r ∈ Icc (bisect (P.excessSupply ν (normalisedDemand α δ)) rlo rhi n).1 + (bisect (P.excessSupply ν (normalisedDemand α δ)) rlo rhi n).2) ∧ + (∀ n, |((bisect (P.excessSupply ν (normalisedDemand α δ)) rlo rhi n).1 + + (bisect (P.excessSupply ν (normalisedDemand α δ)) rlo rhi n).2) / 2 - r| + ≤ (rhi - rlo) / 2 ^ (n + 1)) ∧ + Tendsto (fun n => (bisect (P.excessSupply ν (normalisedDemand α δ)) rlo rhi n).1) atTop + (𝓝 r) := by + obtain ⟨hS, hid⟩ := P.log_selection_of_cap hu hunb hβ hrlo hlohi hβR hcap hmono hz₀ hmin + hreach ν hν + have hE : StrictMonoOn (P.excessSupply ν (normalisedDemand α δ)) (Icc rlo rhi) := + strictMonoOn_sub_of_monotoneOn_of_strictAntiOn hS (normalisedDemand_strictAntiOn hα0 hα1 hδ) + have hzero : P.excessSupply ν (normalisedDemand α δ) r = 0 := by + unfold excessSupply + rw [← hid r hr hrr μ hμ, he, sub_self] + exact ⟨fun n => (bisect_mem_of_strictMonoOn hE hr hzero n).2.2, + abs_bisect_mid_sub_le hE hr hzero, tendsto_bisect_fst hE hr hzero⟩ + +/-- **Bisection for normalized clearing with the Aiyagari log calibration.** On any +`[rlo, rhi] ⊂ (-δ, λ)` with the cap clearing `(48/25)·maxIncome/(1 - (24/25)(1+rhi))`, and along +any selection of stationary distributions, the bisection on excess supply brackets the +equilibrium rate at every step, its midpoint is within `(rhi - rlo)/2^(n+1)` after `n` steps, +and its endpoints converge to the rate. -/ +theorem aiyagari1994_log_bisection {rlo rhi : ℝ} + (hu : P.u = crraUtility 1) (hunb : P.Unbounded) (hβ : (P.discount : ℝ) = 24 / 25) + (hrlo : -2 / 25 < rlo) (hlohi : rlo ≤ rhi) (hlam : rhi < 1 / 24) + (hcap : 2 * (24 / 25) * P.maxIncome < (1 - 24 / 25 * (1 + rhi)) * assetCap) + (hmono : P.MonotoneTransitions) {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + (hmin : P.income z₀ = P.minIncome) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) + {r : ℝ} (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) {μ : ProbabilityMeasure P.State} + (hμ : (P.withRate r hrr).IsStationary μ) + (he : P.aggregateCapital μ = normalisedDemand (9 / 25) (2 / 25) r) : + (∀ n, r ∈ Icc (bisect (P.excessSupply ν (normalisedDemand (9 / 25) (2 / 25))) rlo rhi n).1 + (bisect (P.excessSupply ν (normalisedDemand (9 / 25) (2 / 25))) rlo rhi n).2) ∧ + (∀ n, |((bisect (P.excessSupply ν (normalisedDemand (9 / 25) (2 / 25))) rlo rhi n).1 + + (bisect (P.excessSupply ν (normalisedDemand (9 / 25) (2 / 25))) rlo rhi n).2) / 2 - r| + ≤ (rhi - rlo) / 2 ^ (n + 1)) ∧ + Tendsto (fun n => (bisect (P.excessSupply ν (normalisedDemand (9 / 25) (2 / 25))) rlo rhi n).1) + atTop (𝓝 r) := + P.log_bisection_of_cap (by norm_num) (by norm_num) (by linarith) hu hunb + (by rw [hβ]; norm_num) (by linarith) hlohi (by rw [hβ]; linarith) + (by rw [hβ]; exact hcap) hmono hz₀ hmin hreach ν hν hr hrr hμ he + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/CapitalSupplyMonotone.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/CapitalSupplyMonotone.lean new file mode 100644 index 000000000..3377c3e47 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/CapitalSupplyMonotone.lean @@ -0,0 +1,222 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Distribution.Uniqueness +public import LeanPool.AiyagariEquilibrium.Equilibrium.Uniqueness + +/-! +# Light (2018) Theorem 2: aggregate savings rise with the interest rate + +`Equilibrium.Uniqueness` proves single crossing — Light's Theorem 3 — but carries MONOTONE +CAPITAL SUPPLY as a hypothesis, with nothing in the development connecting that hypothesis to +the household side. This file supplies the connection. Given that every household saves more at +the higher rate, aggregate capital is larger there, so the hypothesis of `equilibriumRate_unique` +follows from the conclusion of Light's Theorem 1. + +That leaves the whole uniqueness argument resting on one thing: Theorem 1, itself reduced to the +Carroll and Kimball step in `IncomeFluctuationCarrollKimball`. + +## The argument + +Aggregate capital is mean assets under the stationary distribution, so what has to be compared +is two distributions, not two numbers. The comparison is first-order stochastic dominance in the +asset coordinate, `Dominates`, tested against bounded continuous functions that rise with assets +at each income state. + +Three facts do the work, and they are the three things a dominance argument always needs. + +* The Markov operator PRESERVES the test class: `markovOp` of a function rising in assets rises + in assets, because the policy does (`policy_mono`). Note where the income chain enters — it + does not. The probabilities depend on today's income state alone, so comparing two asset + levels at a FIXED income state leaves them untouched. +* A uniformly larger policy gives a larger operator, on the test class. +* Dominance survives weak limits, since the test functions are bounded and continuous. + +Then run both economies forward from the SAME initial distribution. They stay ordered at every +date by the first two facts, and each converges to its own stationary distribution by +`tendsto_pushProb_iterate` — the Doeblin convergence proved for step 4 of the roadmap, which has +had no consumer until now. The third fact passes the order to the limit. + +The Doeblin data appears as hypotheses at both rates. It is the same minorisation the uniqueness +theorem runs on: an atom at the borrowing constraint. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +variable {assetFloor assetCap : ℝ} + +/-! ### Stochastic dominance in the asset coordinate -/ + +omit [Fintype Z] [Nonempty Z] [DiscreteTopology Z] [MeasurableSpace Z] [BorelSpace Z] in +/-- Bounded continuous functions that rise with assets, at each income state separately. These +are the test functions of first-order stochastic dominance in the asset coordinate. -/ +def MonoAsset (h : (↥(Icc assetFloor assetCap) × Z) →ᵇ ℝ) : Prop := + ∀ (z : Z) (a b : ↥(Icc assetFloor assetCap)), a ≤ b → h (a, z) ≤ h (b, z) + +/-- **First-order stochastic dominance in assets.** -/ +def Dominates (μ ν : ProbabilityMeasure (↥(Icc assetFloor assetCap) × Z)) : Prop := + ∀ h : (↥(Icc assetFloor assetCap) × Z) →ᵇ ℝ, MonoAsset h → + ∫ s, h s ∂(μ : Measure _) ≤ ∫ s, h s ∂(ν : Measure _) + +omit [Fintype Z] [Nonempty Z] [DiscreteTopology Z] [BorelSpace Z] in +theorem Dominates.refl (μ : ProbabilityMeasure (↥(Icc assetFloor assetCap) × Z)) : + Dominates μ μ := fun _ _ => le_rfl + +omit [Fintype Z] [Nonempty Z] in +/-- **Dominance survives weak limits**, because the test functions are bounded and +continuous. -/ +theorem Dominates.of_tendsto [Finite Z] + {μs νs : ℕ → ProbabilityMeasure (↥(Icc assetFloor assetCap) × Z)} + {μ ν : ProbabilityMeasure (↥(Icc assetFloor assetCap) × Z)} + (hμ : Tendsto μs atTop (𝓝 μ)) (hν : Tendsto νs atTop (𝓝 ν)) + (hd : ∀ n, Dominates (μs n) (νs n)) : Dominates μ ν := by + let _ := Fintype.ofFinite Z + exact fun h hh => le_of_tendsto_of_tendsto + (ProbabilityMeasure.tendsto_iff_forall_integral_tendsto.mp hμ h) + (ProbabilityMeasure.tendsto_iff_forall_integral_tendsto.mp hν h) + (Eventually.of_forall fun n => hd n h hh) + +/-! ### The operator preserves the test class, and respects a larger policy -/ + +variable (P Q : IncomeFluctuation Z assetFloor assetCap) + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The Markov operator preserves the test class.** The expected value tomorrow of a payoff +that rises with assets itself rises with assets, because the policy does. -/ +theorem monoAsset_markovOp {h : P.State →ᵇ ℝ} (hh : MonoAsset h) : + MonoAsset (P.markovOp h) := by + intro z a b hab + simp only [markovOp_apply] + refine Finset.sum_le_sum fun z' _ => ?_ + refine mul_le_mul_of_nonneg_left ?_ (P.prob_nonneg _ _) + exact hh z' _ _ (P.policy_mono a.2 b.2 hab) + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **A uniformly larger policy gives a larger operator**, on the test class. -/ +theorem markovOp_le_of_policy_le + (hprob : ∀ z z' : Z, P.transitionMatrix z z' = Q.transitionMatrix z z') + (hpol : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → P.policy s ≤ Q.policy s) + {h : P.State →ᵇ ℝ} (hh : MonoAsset h) (s : P.State) : + P.markovOp h s ≤ Q.markovOp h s := by + simp only [markovOp_apply] + refine Finset.sum_le_sum fun z' _ => ?_ + rw [show P.prob s z' = Q.prob s z' from hprob _ _] + refine mul_le_mul_of_nonneg_left ?_ (Q.prob_nonneg _ _) + exact hh z' _ _ (hpol (P.incl s) (P.incl_mem s)) + +/-- **One period preserves dominance.** -/ +theorem Dominates.pushProb + (hprob : ∀ z z' : Z, P.transitionMatrix z z' = Q.transitionMatrix z z') + (hpol : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → P.policy s ≤ Q.policy s) + {μ ν : ProbabilityMeasure P.State} (hd : Dominates μ ν) : + Dominates (P.pushProb μ) (Q.pushProb ν) := by + intro h hh + rw [coe_pushProb, coe_pushProb, P.integral_push _ h, Q.integral_push _ h] + refine le_trans (integral_mono ((P.markovOp h).integrable _) + ((Q.markovOp h).integrable _) ?_) (hd _ (Q.monoAsset_markovOp hh)) + exact fun s => markovOp_le_of_policy_le P Q hprob hpol hh s + +/-! ### Theorem 2 -/ + +/-- **Stationary distributions are ordered when policies are.** Both economies are run forward +from the same starting distribution and stay ordered at every date, so their limits are ordered. + +Convergence is the hypothesis rather than the Doeblin data, because that is all the argument +uses; `dominates_stationary_of_policy_le` supplies it from the minorisation. -/ +theorem dominates_of_policy_le + (hprob : ∀ z z' : Z, P.transitionMatrix z z' = Q.transitionMatrix z z') + (hpol : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → P.policy s ≤ Q.policy s) + {μ₀ μ ν : ProbabilityMeasure P.State} + (hP : Tendsto (fun m => P.pushProb^[m] μ₀) atTop (𝓝 μ)) + (hQ : Tendsto (fun m => Q.pushProb^[m] μ₀) atTop (𝓝 ν)) : + Dominates μ ν := by + refine Dominates.of_tendsto hP hQ fun n => ?_ + induction n with + | zero => exact Dominates.refl μ₀ + | succ k ih => + rw [Function.iterate_succ_apply', Function.iterate_succ_apply'] + exact ih.pushProb P Q hprob hpol + +/-- The same, with convergence supplied by the Doeblin minorisation at each rate — the atom at +the borrowing constraint that `Distribution.Uniqueness` runs on. -/ +theorem dominates_stationary_of_policy_le + (hprob : ∀ z z' : Z, P.transitionMatrix z z' = Q.transitionMatrix z z') + (hpol : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → P.policy s ≤ Q.policy s) + {zP : Z} {sP : P.State} {pP : ℝ} {NP : ℕ} (hpP0 : 0 < pP) + (hpP : ∀ s : P.State, pP ≤ P.prob s zP) (hbadP : ∀ s, (P.badStep zP)^[NP] s = sP) + {zQ : Z} {sQ : Q.State} {pQ : ℝ} {NQ : ℕ} (hpQ0 : 0 < pQ) + (hpQ : ∀ s : Q.State, pQ ≤ Q.prob s zQ) (hbadQ : ∀ s, (Q.badStep zQ)^[NQ] s = sQ) + {μ ν : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) (hν : Q.IsStationary ν) : + Dominates μ ν := by + obtain ⟨s₀⟩ : Nonempty P.State := inferInstance + exact dominates_of_policy_le P Q hprob hpol + (μ₀ := ⟨Measure.dirac s₀, inferInstance⟩) + (P.tendsto_pushProb_iterate hpP0 hpP hbadP _ hμ) + (Q.tendsto_pushProb_iterate hpQ0 hpQ hbadQ _ hν) + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem monoAsset_assetCoord : MonoAsset P.assetCoord := fun _ _ _ hab => hab + +/-- **Light (2018) Theorem 2.** If every household saves at least as much in the second economy +as in the first, aggregate capital is at least as large. -/ +theorem aggregateCapital_le_of_policy_le + (hprob : ∀ z z' : Z, P.transitionMatrix z z' = Q.transitionMatrix z z') + (hpol : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → P.policy s ≤ Q.policy s) + {zP : Z} {sP : P.State} {pP : ℝ} {NP : ℕ} (hpP0 : 0 < pP) + (hpP : ∀ s : P.State, pP ≤ P.prob s zP) (hbadP : ∀ s, (P.badStep zP)^[NP] s = sP) + {zQ : Z} {sQ : Q.State} {pQ : ℝ} {NQ : ℕ} (hpQ0 : 0 < pQ) + (hpQ : ∀ s : Q.State, pQ ≤ Q.prob s zQ) (hbadQ : ∀ s, (Q.badStep zQ)^[NQ] s = sQ) + {μ ν : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) (hν : Q.IsStationary ν) : + P.aggregateCapital μ ≤ Q.aggregateCapital ν := + dominates_stationary_of_policy_le P Q hprob hpol hpP0 hpP hbadP hpQ0 hpQ hbadQ hμ hν + P.assetCoord (monoAsset_assetCoord P) + +/-- **Capital supply is monotone in the interest rate** whenever the household policy is. + +This is exactly the hypothesis `hS` of `equilibriumRate_unique`, so with Light's Theorem 1 in +hand — the policy rising in the rate — the equilibrium rate is unique. Theorem 1 is the only +thing still missing, and `IncomeFluctuationCarrollKimball` has reduced it to one step. -/ +theorem monotoneOn_capitalSupply_of_policy_mono {rlo rhi : ℝ} + (Pf : ℝ → IncomeFluctuation Z assetFloor assetCap) + (hprob : ∀ r r' : ℝ, ∀ z z' : Z, + (Pf r).transitionMatrix z z' = (Pf r').transitionMatrix z z') + (hpol : ∀ r ∈ Icc rlo rhi, ∀ r' ∈ Icc rlo rhi, r ≤ r' → + ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → (Pf r).policy s ≤ (Pf r').policy s) + (μ₀ : ProbabilityMeasure P.State) (ν : ℝ → ProbabilityMeasure P.State) + (hconv : ∀ r ∈ Icc rlo rhi, Tendsto (fun m => (Pf r).pushProb^[m] μ₀) atTop (𝓝 (ν r))) : + MonotoneOn (fun r => P.aggregateCapital (ν r)) (Icc rlo rhi) := fun r hr r' hr' hrr => + dominates_of_policy_le (Pf r) (Pf r') (hprob r r') (hpol r hr r' hr' hrr) + (hconv r hr) (hconv r' hr') P.assetCoord (monoAsset_assetCoord P) + +/-- **Capital supply is ordered when the policies are**, across two FAMILIES of economies rather +than across two rates of one. The patience comparative static consumes this: a more patient +population saves more at every rate, so its supply schedule lies above. -/ +theorem capitalSupply_le_of_policy_le {rlo rhi : ℝ} + (Pf Qf : ℝ → IncomeFluctuation Z assetFloor assetCap) + (hprob : ∀ r : ℝ, ∀ z z' : Z, (Pf r).transitionMatrix z z' = (Qf r).transitionMatrix z z') + (hpol : ∀ r ∈ Icc rlo rhi, ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → + (Pf r).policy s ≤ (Qf r).policy s) + (μ₀ : ProbabilityMeasure P.State) (ν ρ : ℝ → ProbabilityMeasure P.State) + (hconvP : ∀ r ∈ Icc rlo rhi, Tendsto (fun m => (Pf r).pushProb^[m] μ₀) atTop (𝓝 (ν r))) + (hconvQ : ∀ r ∈ Icc rlo rhi, Tendsto (fun m => (Qf r).pushProb^[m] μ₀) atTop (𝓝 (ρ r))) + {r : ℝ} (hr : r ∈ Icc rlo rhi) : + P.aggregateCapital (ν r) ≤ P.aggregateCapital (ρ r) := + dominates_of_policy_le (Pf r) (Qf r) (hprob r) (hpol r hr) (hconvP r hr) (hconvQ r hr) + P.assetCoord (monoAsset_assetCoord P) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/Firm.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/Firm.lean new file mode 100644 index 000000000..e1f4d33ad --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/Firm.lean @@ -0,0 +1,200 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariContinuity + +/-! +# The firm side + +`exists_aiyagari_equilibrium` takes any continuous capital demand schedule. This file supplies +one that is DERIVED from a production function rather than posited, so the equilibrium theorem +becomes a statement about a model rather than about a schedule. + +The technology is Cobb–Douglas with `α = 1/2`, one unit of labour and total factor +productivity `A`, so output is `A √K` and the marginal product of capital is `A / (2√K)`. +Setting the net marginal product equal to the interest rate, + + A / (2√K) - δ = r, + +and solving gives `K = A² / (4(r+δ)²)`. `capitalDemand_marginalProduct` checks that the +solution really does satisfy the first-order condition, which is what stops the formula from +being an arbitrary decreasing function with a suggestive name. + +Productivity is a PARAMETER rather than fixed at one because the demand schedule otherwise +carries a scale the household side has to be stretched to meet. `exists_firm_of_bounds` is what +that buys: given any rate interval and any two-sided bound on capital supply, some firm meets it +there. The firm can be calibrated to the households rather than the other way round. + +`α = 1/2` is chosen so the inversion is algebraic. A general exponent would need `rpow` and +would change nothing conceptually. + +## What this does and does not close + +Equilibrium needs excess demand to change sign across `[rlo, rhi]`. The LOW end is now a +theorem: capital demand diverges as `r` approaches `-δ`, while capital supply can never exceed +the asset cap, so `capitalDemand 1 δ rlo ≥ assetCap ≥ S rlo` for `rlo` close enough to `-δ`. + +The high-end sign inequality requires positive household capital supply. This module +exposes that condition; the later `PositiveCapital` and `SignChange` modules provide lower +bounds and calibrate the firm under their additional hypotheses. The uniformly impatient +`NonVacuity` household alone has zero stationary capital supply and does not discharge it. + +-/ + +@[expose] public section + +open Set Filter Topology MeasureTheory + +namespace LeanEconomics + +/-- Capital demand from a Cobb–Douglas firm with `α = 1/2`, unit labour and productivity `A`: +the capital stock at which the net marginal product equals `r`. -/ +noncomputable def capitalDemand (A δ r : ℝ) : ℝ := A ^ 2 / (4 * (r + δ) ^ 2) + +/-- **The demand schedule solves the firm's first-order condition.** -/ +theorem capitalDemand_marginalProduct {A δ r : ℝ} (hA : 0 < A) (h : 0 < r + δ) : + A / (2 * Real.sqrt (capitalDemand A δ r)) - δ = r := by + have hsq : capitalDemand A δ r = (A / (2 * (r + δ))) ^ 2 := by + simp only [capitalDemand]; field_simp; ring + rw [hsq, Real.sqrt_sq (by positivity)] + field_simp + ring + +theorem capitalDemand_pos {A δ r : ℝ} (hA : 0 < A) (h : 0 < r + δ) : + 0 < capitalDemand A δ r := by + simp only [capitalDemand]; positivity + +theorem continuousOn_capitalDemand {A δ rlo rhi : ℝ} (h : 0 < rlo + δ) : + ContinuousOn (capitalDemand A δ) (Icc rlo rhi) := by + refine ContinuousOn.div continuousOn_const ?_ fun r hr => ?_ + · exact (continuous_const.mul ((continuous_id.add continuous_const).pow 2)).continuousOn + · have : 0 < r + δ := lt_of_lt_of_le h (by linarith [hr.1]) + positivity + +/-- Capital demand falls as the interest rate rises. -/ +theorem capitalDemand_antitoneOn {A δ : ℝ} {rlo rhi : ℝ} (h : 0 < rlo + δ) : + AntitoneOn (capitalDemand A δ) (Icc rlo rhi) := by + intro a ha b hb hab + have ha' : 0 < a + δ := lt_of_lt_of_le h (by linarith [ha.1]) + have hb' : 0 < b + δ := lt_of_lt_of_le h (by linarith [hb.1]) + simp only [capitalDemand] + gcongr + +/-- **Capital demand diverges as the rate approaches `-δ`.** Given any bound -- in particular +the asset cap, which capital supply can never exceed -- some admissible rate demands more. -/ +theorem exists_capitalDemand_ge (δ : ℝ) {M : ℝ} (hM : 0 ≤ M) : + ∃ rlo : ℝ, 0 < rlo + δ ∧ M ≤ capitalDemand 1 δ rlo := by + have hM1 : (0 : ℝ) < M + 1 := by linarith + refine ⟨(1 / (2 * (M + 1))) - δ, by simp only [sub_add_cancel]; positivity, ?_⟩ + have hrw : (1 / (2 * (M + 1)) - δ) + δ = 1 / (2 * (M + 1)) := by ring + simp only [capitalDemand, hrw, one_pow] + rw [le_div_iff₀ (by positivity)] + have hexp : 4 * (1 / (2 * (M + 1))) ^ 2 = 1 / (M + 1) ^ 2 := by field_simp; ring + rw [hexp, mul_one_div, div_le_one (by positivity)] + nlinarith + +/-! ### Reading the two sides of the sign change off the technology + +`exists_capitalDemand_ge` and `exists_capitalDemand_le` FIND a rate at which demand clears a +bound. The two lemmas here do the opposite and more useful thing: given the rate, they say what +the technology has to satisfy. Both are the same inequality `4(r+δ)²K ≤ A²` read in the two +directions, which in root form is `2(r+δ)√K ≤ A` — the marginal product of `K` units at rate `r`. +-/ + +/-- **The technology is productive enough to demand `K` at rate `r`.** In root form, +`2(r+δ)√K ≤ A`. -/ +theorem le_capitalDemand_of_sq {A δ r K : ℝ} (hr : 0 < r + δ) + (h : 4 * (r + δ) ^ 2 * K ≤ A ^ 2) : K ≤ capitalDemand A δ r := by + simp only [capitalDemand] + rw [le_div_iff₀ (by positivity)] + linarith + +/-- **The technology is weak enough to demand no more than `K` at rate `r`.** In root form, +`A ≤ 2(r+δ)√K`. -/ +theorem capitalDemand_le_of_sq {A δ r K : ℝ} (hr : 0 < r + δ) + (h : A ^ 2 ≤ 4 * (r + δ) ^ 2 * K) : capitalDemand A δ r ≤ K := by + simp only [capitalDemand] + rw [div_le_iff₀ (by positivity)] + linarith + +/-! ### The implied rate + +Equilibrium is often stated the other way round: a rate `r` induces a capital stock, the capital +stock induces a marginal product, and equilibrium is where the induced rate returns `r`. That is +the same condition — `impliedRate` inverts `capitalDemand` — and `capitalDemand_eq_iff` says so. -/ + +/-- The net marginal product of capital at stock `K`: the rate a firm holding `K` would pay. -/ +noncomputable def impliedRate (A δ K : ℝ) : ℝ := A / (2 * Real.sqrt K) - δ + +/-- **`impliedRate` inverts `capitalDemand`.** Demanding `K` at rate `r` is the same as `K` +implying `r`. -/ +theorem capitalDemand_eq_iff {A δ r K : ℝ} (hA : 0 < A) (hr : 0 < r + δ) (hK : 0 < K) : + K = capitalDemand A δ r ↔ impliedRate A δ K = r := by + constructor + · rintro rfl + exact capitalDemand_marginalProduct hA hr + · intro h + have hsK : 0 < Real.sqrt K := Real.sqrt_pos.mpr hK + have hrw : A / (2 * Real.sqrt K) = r + δ := by + simp only [impliedRate] at h; linarith + have hsq : Real.sqrt K = A / (2 * (r + δ)) := by + field_simp at hrw ⊢ + linarith [hrw] + have hKeq : K = (Real.sqrt K) ^ 2 := (Real.sq_sqrt hK.le).symm + rw [hKeq, hsq] + simp only [capitalDemand] + field_simp + ring + +/-- **Equilibrium in implied-rate form.** A rate is an equilibrium exactly when the capital its +households hold implies it back. -/ +theorem isAiyagariEquilibrium_iff_impliedRate {Z : Type*} [Fintype Z] [Nonempty Z] + [TopologicalSpace Z] [DiscreteTopology Z] [MeasurableSpace Z] [BorelSpace Z] + {assetFloor assetCap : ℝ} + {Pf : ℝ → IncomeFluctuation Z assetFloor assetCap} {A δ r : ℝ} (hA : 0 < A) (hr : 0 < r + δ) : + IsAiyagariEquilibrium Pf (capitalDemand A δ) r ↔ + ∃ μ : ProbabilityMeasure (Pf r).State, (Pf r).IsStationary μ ∧ + 0 < (Pf r).aggregateCapital μ ∧ impliedRate A δ ((Pf r).aggregateCapital μ) = r := by + constructor + · rintro ⟨μ, hμ, hK⟩ + have hpos : 0 < (Pf r).aggregateCapital μ := by rw [hK]; exact capitalDemand_pos hA hr + exact ⟨μ, hμ, hpos, (capitalDemand_eq_iff hA hr hpos).mp hK⟩ + · rintro ⟨μ, hμ, hpos, himp⟩ + exact ⟨μ, hμ, (capitalDemand_eq_iff hA hr hpos).mpr himp⟩ + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **Capital supply never exceeds the asset cap**, because every household's assets are +capped. This is what makes the low end of the sign change a theorem. -/ +theorem aggregateCapital_le (μ : ProbabilityMeasure P.State) : + P.aggregateCapital μ ≤ assetCap := by + have hle : ∀ s : P.State, P.assetCoord s ≤ assetCap := fun s => s.1.2.2 + calc P.aggregateCapital μ ≤ ∫ _s, assetCap ∂(μ : Measure P.State) := + integral_mono (P.assetCoord.integrable _) (integrable_const _) hle + _ = assetCap := by simp + +/-- Capital supply never falls below the borrowing limit. -/ +theorem assetFloor_le_aggregateCapital (μ : ProbabilityMeasure P.State) : + assetFloor ≤ P.aggregateCapital μ := by + have hle : ∀ s : P.State, assetFloor ≤ P.assetCoord s := fun s => s.1.2.1 + calc assetFloor = ∫ _s, assetFloor ∂(μ : Measure P.State) := by simp + _ ≤ P.aggregateCapital μ := + integral_mono (integrable_const _) (P.assetCoord.integrable _) hle + +/-- **The low end of the sign change is a theorem.** -/ +theorem exists_rate_capitalDemand_ge (δ : ℝ) (μ : ProbabilityMeasure P.State) : + ∃ rlo : ℝ, 0 < rlo + δ ∧ P.aggregateCapital μ ≤ capitalDemand 1 δ rlo := by + obtain ⟨rlo, hrlo, hge⟩ := exists_capitalDemand_ge δ P.assetCap_nonneg + exact ⟨rlo, hrlo, le_trans (P.aggregateCapital_le μ) hge⟩ + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/PositiveCapital.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/PositiveCapital.lean new file mode 100644 index 000000000..ea7e845bc --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/PositiveCapital.lean @@ -0,0 +1,547 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Distribution.Feller +public import LeanPool.AiyagariEquilibrium.Equilibrium.Aiyagari +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationMonotone + +/-! +# Aggregate capital is strictly positive + +Every equilibrium result so far is conditional on excess demand changing sign, and nothing in +the development says that households save anything at all. This file supplies the missing +economics: under log utility, households rich enough in income save strictly, and the stationary +distribution then carries strictly positive aggregate capital. + +## Capital is the mean of the policy + +`markovOp_assetCoord` is the identity the whole argument runs on. The expected asset coordinate +next period is the policy today, because the asset the household carries forward does not depend +on which income shock arrives: + + `markovOp assetCoord = policyCoord`. + +With stationarity that gives `aggregateCapital μ = ∫ policy dμ` — aggregate capital is the mean +saving, not merely something computed from the distribution. Everything after is a lower bound +on that integral. + +## Rich households save, and the argument is the crudest deviation again + +Saving `h` rather than nothing costs `u resources - u (resources - h)` and gains +`β (cont h - cont 0)`. For LOG the cost is `log (m / (m - h))`, which SHRINKS as resources grow, +while the gain is a fixed positive number — positive because the value function is strictly +increasing in assets, which `valueFunction_lt_of_lt` proves from strict monotonicity of `u`, +itself recovered from strict concavity. So above an explicit income level the household saves +strictly, with no envelope theorem and no Euler equation. + +Since the cost falls with resources, it is enough to check the inequality at the household's +POOREST state in that income, `a = 0`, and it then holds at every asset level. + +## Reaching those households + +The income coordinate of the stationary distribution is bounded below by the worst transition +probability into that state, and that too is pure duality: the indicator of an income state is +CONTINUOUS here, `Z` being discrete, so it is a legitimate test function and +`markovOp (incomeIndicator z₁) s = transitionMatrix s.2 z₁`. Stationarity turns a uniform lower +bound on the column of the transition matrix into a lower bound on the mass. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### Strict monotonicity -/ + +/-- Period utility is STRICTLY increasing: a concave function that is flat on an interval is +affine there, which strict concavity forbids. -/ +theorem strictMonoOn_u : StrictMonoOn P.u (Ioi 0) := by + intro x hx y hy hxy + rcases lt_or_eq_of_le (P.monotoneOn_u hx hy hxy.le) with h | h + · exact h + · exfalso + have hx0 : (0 : ℝ) < x := hx + have hy0 : (0 : ℝ) < y := hy + have hhalf : (0 : ℝ) < 1 / 2 := by norm_num + have hsum : (1 : ℝ) / 2 + 1 / 2 = 1 := by norm_num + have hstrict := P.strictConcaveOn_u.2 hx hy (ne_of_lt hxy) hhalf hhalf hsum + simp only [smul_eq_mul] at hstrict + have hmid : (1 / 2 : ℝ) * x + (1 / 2 : ℝ) * y ∈ Ioi (0 : ℝ) := mem_Ioi.mpr (by linarith) + have hle : P.u ((1 / 2 : ℝ) * x + (1 / 2 : ℝ) * y) ≤ P.u y := + P.monotoneOn_u hmid hy (by linarith) + linarith + +/-- **The value function is strictly increasing in assets.** More assets buy strictly more +consumption at the same saving plan. -/ +theorem valueFunction_lt_of_lt (hpc : P.PositiveConsumption) {x y : ℝ} {z : Z} + (hx : x ∈ Icc assetFloor assetCap) + (hy : y ∈ Icc assetFloor assetCap) (hxy : x < y) : + P.toExtended.valueFunction (x, z) < P.toExtended.valueFunction (y, z) := by + have hbm : P.policy (x, z) ∈ P.toExtended.feasible (x, z) := P.policy_mem _ + have hbm' : P.policy (x, z) ∈ P.toExtended.feasible (y, z) := P.feasible_mono hxy.le hbm + have hcx : 0 < P.consumption (x, z) (P.policy (x, z)) := P.consumption_policy_pos hpc hx + have hresl : P.resources (x, z) < P.resources (y, z) := by + simp only [resources, max_eq_right hx.1, max_eq_right hy.1] + nlinarith [P.interest_gt_neg_one] + have hcy : 0 < P.consumption (y, z) (P.policy (x, z)) := by + simp only [consumption] at hcx ⊢; linarith + have hstep : P.objR (x, z) (P.policy (x, z)) < P.objR (y, z) (P.policy (x, z)) := by + simp only [objR] + have := P.strictMonoOn_u (mem_Ioi.mpr hcx) (mem_Ioi.mpr hcy) + (by simp only [consumption] at hcx ⊢; linarith) + linarith + have h1 := P.objR_le_of_mem hy hbm' (P.mem_dom_of_pos hcy) + have e1 : P.objR (x, z) (P.policy (x, z)) = P.toExtended.valueFunction (x, z) := by + simp only [objR, cont]; exact (P.valueFunction_eq_policy hx).symm + have e2 : P.objR (y, z) (P.policy (y, z)) = P.toExtended.valueFunction (y, z) := by + simp only [objR, cont]; exact (P.valueFunction_eq_policy hy).symm + rw [← e1, ← e2] + linarith + +/-- **The continuation is strictly increasing**, since some income state has positive +probability. -/ +theorem cont_lt_of_lt (hpc : P.PositiveConsumption) (z : Z) {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : x < y) : P.cont z x < P.cont z y := by + obtain ⟨z₀, hz₀⟩ : ∃ z₀ : Z, 0 < P.transitionMatrix z z₀ := by + by_contra hcon + push Not at hcon + have hz : ∑ z', P.transitionMatrix z z' = 0 := + Finset.sum_eq_zero fun z' _ => + le_antisymm (hcon z') (P.transitionMatrix_nonneg _ _) + rw [P.transitionMatrix_sum] at hz + norm_num at hz + simp only [cont] + refine Finset.sum_lt_sum (fun z' _ => ?_) ⟨z₀, Finset.mem_univ _, ?_⟩ + · exact mul_le_mul_of_nonneg_left (P.valueFunction_lt_of_lt hpc hx hy hxy).le + (P.transitionMatrix_nonneg _ _) + · exact mul_lt_mul_of_pos_left (P.valueFunction_lt_of_lt hpc hx hy hxy) hz₀ + +/-! ### Capital is the mean of the policy -/ + +theorem norm_policy_incl_le (s : P.State) : + ‖P.policy (P.incl s)‖ ≤ |assetFloor| + |assetCap| := by + have h := P.policy_mem_region (P.incl s) + rw [Real.norm_eq_abs, abs_le] + exact ⟨by linarith [h.1, neg_abs_le assetFloor, abs_nonneg assetCap], + by linarith [h.2, le_abs_self assetCap, abs_nonneg assetFloor]⟩ + +/-- Saving carried forward, as a bounded continuous function of the state. -/ +noncomputable def policyCoord : P.State →ᵇ ℝ := + ofNormedAddCommGroup (fun s => P.policy (P.incl s)) + (P.continuousOn_policy.comp_continuous P.continuous_incl P.incl_mem) + (|assetFloor| + |assetCap|) P.norm_policy_incl_le + +@[simp] theorem policyCoord_apply (s : P.State) : P.policyCoord s = P.policy (P.incl s) := rfl + +/-- **The expected asset coordinate next period is today's saving.** The asset carried forward +does not depend on which income shock arrives, so the expectation collapses. -/ +theorem markovOp_assetCoord : P.markovOp P.assetCoord = P.policyCoord := by + ext s + simp only [markovOp_apply, policyCoord_apply] + have hz : ∀ z' : Z, P.assetCoord (P.nextState s z') = P.policy (P.incl s) := fun _ => rfl + simp only [hz] + rw [← Finset.sum_mul, P.prob_sum, one_mul] + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- **Aggregate capital is mean saving.** -/ +theorem aggregateCapital_eq_integral_policy {μ : ProbabilityMeasure P.State} + (hμ : P.IsStationary μ) : + P.aggregateCapital μ = ∫ s, P.policyCoord s ∂(μ : Measure P.State) := by + calc P.aggregateCapital μ = P.aggregateCapital (P.pushProb μ) := by rw [hμ] + _ = ∫ s, P.markovOp P.assetCoord s ∂(μ : Measure P.State) := by + rw [aggregateCapital, coe_pushProb, P.integral_push] + _ = ∫ s, P.policyCoord s ∂(μ : Measure P.State) := by rw [P.markovOp_assetCoord] + +/-! ### The stationary distribution reaches every income state -/ + +open Classical in +/-- The indicator of an income state — continuous, because `Z` is discrete. -/ +noncomputable def incomeIndicator (z₁ : Z) : P.State →ᵇ ℝ := + ofNormedAddCommGroup (fun s => if s.2 = z₁ then 1 else 0) + ((continuous_of_discreteTopology (f := fun z : Z => if z = z₁ then (1 : ℝ) else 0)).comp + continuous_snd) 1 + (fun s => by by_cases h : s.2 = z₁ <;> simp [h]) + +omit [MeasurableSpace Z] [BorelSpace Z] in +open Classical in +@[simp] theorem incomeIndicator_apply (z₁ : Z) (s : P.State) : + P.incomeIndicator z₁ s = if s.2 = z₁ then 1 else 0 := rfl + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem markovOp_incomeIndicator (z₁ : Z) (s : P.State) : + P.markovOp (P.incomeIndicator z₁) s = P.transitionMatrix s.2 z₁ := by + classical + simp only [markovOp_apply, incomeIndicator_apply, nextState] + rw [Finset.sum_eq_single z₁ (fun b _ hb => by simp [hb]) (by simp)] + simp [prob] + +/-- **The stationary mass on an income state is at least the worst transition into it.** -/ +theorem le_integral_incomeIndicator {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) + {z₁ : Z} {p₀ : ℝ} (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) : + p₀ ≤ ∫ s, P.incomeIndicator z₁ s ∂(μ : Measure P.State) := by + have hprob : IsProbabilityMeasure (μ : Measure P.State) := μ.2 + calc p₀ = ∫ _s, p₀ ∂(μ : Measure P.State) := by simp + _ ≤ ∫ s, P.markovOp (P.incomeIndicator z₁) s ∂(μ : Measure P.State) := by + refine integral_mono (integrable_const _) ((P.markovOp _).integrable _) fun s => ?_ + rw [P.markovOp_incomeIndicator] + exact hp s.2 + _ = ∫ s, P.incomeIndicator z₁ s ∂(P.push (μ : Measure P.State)) := + (P.integral_push _ _).symm + _ = ∫ s, P.incomeIndicator z₁ s ∂(μ : Measure P.State) := by + rw [← coe_pushProb, hμ] + +/-! ### Rich households save -/ + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **A household with enough income saves strictly.** The cost of saving `h` instead of nothing +is `log (m / (m - h))`, which falls as resources grow, while the gain `β (cont h - cont 0)` is a +fixed positive number. So it is enough to check the comparison at `a = 0`, the poorest state in +that income, and it then holds at every asset level. -/ +theorem policy_pos_of_cost (hfl : assetFloor = 0) (z₁ : Z) {h κ : ℝ} (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hcost : ∀ R : ℝ, P.income z₁ ≤ R → P.u R - P.u (R - h) ≤ κ) + (hgain : κ < P.discount * (P.cont z₁ h - P.cont z₁ 0)) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + 0 < P.policy (a, z₁) := by + subst hfl + rcases lt_or_eq_of_le (P.policy_mem_region (a, z₁)).1 with hpos | hzero + · exact hpos + exfalso + have hm : P.income z₁ ≤ P.resources (a, z₁) := by + simp only [resources, max_eq_right ha.1] + nlinarith [P.interest_gt_neg_one, ha.1] + have hmh : h < P.resources (a, z₁) := lt_of_lt_of_le hhinc hm + have hfeas : h ∈ P.toExtended.feasible (a, z₁) := by + rw [P.feasible_eq, P.maxSaving_eq] + exact ⟨hh0.le, le_min hhcap hmh.le⟩ + have hch : 0 < P.consumption (a, z₁) h := by simp only [consumption]; linarith + have hopt := P.objR_le_of_mem ha hfeas (P.mem_dom_of_pos hch) + rw [← hzero] at hopt + simp only [objR, consumption, sub_zero] at hopt + linarith [hcost (P.resources (a, z₁)) hm] + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- The log instance of `policy_pos_of_cost`: the cost of saving `h` is `log (m / (m - h))`, +which falls as resources grow, so it is enough to check it at the poorest state in that income. -/ +theorem policy_pos_of_income (hfl : assetFloor = 0) (hu : P.u = Real.log) (z₁ : Z) {h : ℝ} + (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hgain : Real.log (P.income z₁ / (P.income z₁ - h)) + < P.discount * (P.cont z₁ h - P.cont z₁ 0)) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + 0 < P.policy (a, z₁) := by + subst hfl + have hinc0 : 0 < P.income z₁ := lt_trans hh0 hhinc + refine P.policy_pos_of_cost rfl z₁ hh0 hhcap hhinc (fun R hm => ?_) hgain ha + rw [hu] + have hR : 0 < R := lt_of_lt_of_le hinc0 hm + have hmono : R / (R - h) ≤ P.income z₁ / (P.income z₁ - h) := by + rw [div_le_div_iff₀ (by linarith) (by linarith)] + nlinarith [hm, hh0] + have hlog := Real.log_le_log (div_pos (by linarith) (by linarith)) hmono + rw [Real.log_div (by linarith) (by linarith)] at hlog + linarith + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- A uniform positive lower bound on saving across the asset region, by compactness. -/ +theorem exists_policy_lower_bound_of_cost (hfl : assetFloor = 0) (z₁ : Z) {h κ : ℝ} (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hcost : ∀ R : ℝ, P.income z₁ ≤ R → P.u R - P.u (R - h) ≤ κ) + (hgain : κ < P.discount * (P.cont z₁ h - P.cont z₁ 0)) : + ∃ ε > 0, ∀ a ∈ Icc assetFloor assetCap, ε ≤ P.policy (a, z₁) := by + subst hfl + have hne : (Icc (0 : ℝ) assetCap).Nonempty := ⟨0, ⟨le_rfl, P.assetFloor_le_assetCap⟩⟩ + have hcont : ContinuousOn (fun a : ℝ => P.policy (a, z₁)) (Icc (0 : ℝ) assetCap) := + P.continuousOn_policy.comp (by fun_prop) fun a ha => ha + obtain ⟨a₀, ha₀, hmin⟩ := isCompact_Icc.exists_isMinOn hne hcont + exact ⟨P.policy (a₀, z₁), P.policy_pos_of_cost rfl z₁ hh0 hhcap hhinc hcost hgain ha₀, + fun a ha => isMinOn_iff.mp hmin a ha⟩ + +/-! ### Positive aggregate capital -/ + +/-- **Aggregate capital is strictly positive.** Households in income state `z₁` save at least +`ε`, the stationary distribution puts at least `p₀` of its mass there, and aggregate capital is +mean saving. -/ +theorem aggregateCapital_pos_of_cost (hfl : assetFloor = 0) {μ : ProbabilityMeasure P.State} + (hμ : P.IsStationary μ) {z₁ : Z} {p₀ : ℝ} (hp0 : 0 < p₀) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + {h κ : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hcost : ∀ R : ℝ, P.income z₁ ≤ R → P.u R - P.u (R - h) ≤ κ) + (hgain : κ < P.discount * (P.cont z₁ h - P.cont z₁ 0)) : + 0 < P.aggregateCapital μ := by + subst hfl + obtain ⟨ε, hε, hεle⟩ := + P.exists_policy_lower_bound_of_cost rfl z₁ hh0 hhcap hhinc hcost hgain + rw [P.aggregateCapital_eq_integral_policy hμ] + have hbound : ∀ s : P.State, ε * P.incomeIndicator z₁ s ≤ P.policyCoord s := by + intro s + by_cases hz : s.2 = z₁ + · have hmem : ((s.1 : ℝ)) ∈ Icc (0 : ℝ) assetCap := s.1.2 + have hge := hεle _ hmem + have hind : P.incomeIndicator z₁ s = 1 := by simp [hz] + have hpc : P.policyCoord s = P.policy ((s.1 : ℝ), s.2) := rfl + rw [hind, mul_one, hpc, hz] + exact hge + · have hind : P.incomeIndicator z₁ s = 0 := by simp [hz] + rw [hind, mul_zero] + exact (P.policy_mem_region (P.incl s)).1 + calc (0 : ℝ) < ε * p₀ := by positivity + _ ≤ ε * ∫ s, P.incomeIndicator z₁ s ∂(μ : Measure P.State) := + mul_le_mul_of_nonneg_left (P.le_integral_incomeIndicator hμ hp) hε.le + _ = ∫ s, ε * P.incomeIndicator z₁ s ∂(μ : Measure P.State) := (integral_const_mul _ _).symm + _ ≤ ∫ s, P.policyCoord s ∂(μ : Measure P.State) := + integral_mono (((P.incomeIndicator z₁).integrable _).const_mul ε) + (P.policyCoord.integrable _) hbound + +/-- The log instance of `aggregateCapital_pos_of_cost`. -/ +theorem aggregateCapital_pos (hfl : assetFloor = 0) (hu : P.u = Real.log) + {μ : ProbabilityMeasure P.State} + (hμ : P.IsStationary μ) {z₁ : Z} {p₀ : ℝ} (hp0 : 0 < p₀) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + {h : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hgain : Real.log (P.income z₁ / (P.income z₁ - h)) + < P.discount * (P.cont z₁ h - P.cont z₁ 0)) : + 0 < P.aggregateCapital μ := by + subst hfl + have hinc0 : 0 < P.income z₁ := lt_trans hh0 hhinc + refine P.aggregateCapital_pos_of_cost rfl hμ hp0 hp hh0 hhcap hhinc (fun R hm => ?_) hgain + rw [hu] + have hmono : R / (R - h) ≤ P.income z₁ / (P.income z₁ - h) := by + rw [div_le_div_iff₀ (by linarith) (by linarith)] + nlinarith [hm, hh0] + have hlog := Real.log_le_log (div_pos (by linarith) (by linarith)) hmono + rw [Real.log_div (by linarith) (by linarith)] at hlog + linarith + +/-! ### The gain, bounded by primitives + +`policy_pos_of_income` asks for a lower bound on `cont z₁ h - cont z₁ 0`, which is an object of +the solved model rather than a primitive. It is bounded below by the same deviation trick applied +one level down, and WHICH TERM OF THE SUM IS KEPT is the economics. + +Bounding by the worst state — every `income z'` replaced by `maxIncome` — gives, for small `h`, a +condition amounting to `β (1 + r) > 1`. That is useless here: the natural asset bound wants +`β (1 + r) < 1`, so the two would collide. Keeping the LOW-income term instead gives +`P z z₀ · log (1 + (1+r) h / income z₀)`, and with `income z₀` small that term is large; the +condition becomes roughly `β (1 + r) · P z z₀ > minIncome / maxIncome`, satisfiable under ordinary +impatience provided income is dispersed. + +That difference is the precautionary motive. The household saves not because it is patient but +because with probability `P z z₀` it lands where consumption is small and the marginal value of +assets is large. The motive enters through the DISPERSION of income rather than through convexity +of `u'`, which is why it is available without a third derivative. -/ + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem valueFunction_le_of_le (hpc : P.PositiveConsumption) {x y : ℝ} {z : Z} + (hx : x ∈ Icc assetFloor assetCap) + (hy : y ∈ Icc assetFloor assetCap) (hxy : x ≤ y) : + P.toExtended.valueFunction (x, z) ≤ P.toExtended.valueFunction (y, z) := by + rcases eq_or_lt_of_le hxy with rfl | hlt + · exact le_rfl + · exact (P.valueFunction_lt_of_lt hpc hx hy hlt).le + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem resources_zero (hfl : assetFloor = 0) (z : Z) : P.resources (0, z) = P.income z := by + subst hfl + simp only [resources]; norm_num + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The value of extra assets is at least the one-period utility gain**, got by carrying the +poorer household's own saving plan forward. -/ +theorem valueFunction_sub_ge (hpc : P.PositiveConsumption) {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : x ≤ y) (z : Z) : + P.u (P.consumption (x, z) (P.policy (x, z)) + (1 + P.interest) * (y - x)) + - P.u (P.consumption (x, z) (P.policy (x, z))) + ≤ P.toExtended.valueFunction (y, z) - P.toExtended.valueFunction (x, z) := by + have hbm : P.policy (x, z) ∈ P.toExtended.feasible (x, z) := P.policy_mem _ + have hbm' : P.policy (x, z) ∈ P.toExtended.feasible (y, z) := P.feasible_mono hxy hbm + have hc0 : 0 < P.consumption (x, z) (P.policy (x, z)) := P.consumption_policy_pos hpc hx + have hres : P.resources (y, z) = P.resources (x, z) + (1 + P.interest) * (y - x) := by + simp only [resources, max_eq_right hx.1, max_eq_right hy.1]; ring + have hch : P.consumption (y, z) (P.policy (x, z)) + = P.consumption (x, z) (P.policy (x, z)) + (1 + P.interest) * (y - x) := by + simp only [consumption, hres]; ring + have hchpos : 0 < P.consumption (y, z) (P.policy (x, z)) := by + rw [hch] + have : 0 ≤ (1 + P.interest) * (y - x) := + mul_nonneg P.interest_gt_neg_one.le (by linarith) + linarith + have hopt := P.objR_le_of_mem hy hbm' (P.mem_dom_of_pos hchpos) + have e0 : P.objR (x, z) (P.policy (x, z)) = P.toExtended.valueFunction (x, z) := by + simp only [objR, cont]; exact (P.valueFunction_eq_policy hx).symm + have eh : P.objR (y, z) (P.policy (y, z)) = P.toExtended.valueFunction (y, z) := by + simp only [objR, cont]; exact (P.valueFunction_eq_policy hy).symm + simp only [objR, cont, hch] at hopt + simp only [objR, cont] at e0 eh + rw [← e0, ← eh] + linarith + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- The continuation rises with saving. -/ +theorem cont_le_of_le (hpc : P.PositiveConsumption) (z : Z) {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : x ≤ y) : P.cont z x ≤ P.cont z y := by + simp only [cont] + refine Finset.sum_le_sum fun z' _ => ?_ + exact mul_le_mul_of_nonneg_left (P.valueFunction_le_of_le hpc hx hy hxy) + (P.transitionMatrix_nonneg _ _) + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- A lower bound on the one-period utility gain in one successor state gives a +probability-weighted lower bound on the continuation gain. -/ +theorem cont_sub_ge_of_utility_bound (hpc : P.PositiveConsumption) (z z₀ : Z) + {x y bound : ℝ} (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : x ≤ y) + (hbound : ∀ c : ℝ, 0 < c → c ≤ P.income z₀ + (1 + P.interest) * x - assetFloor → + bound ≤ P.u (c + (1 + P.interest) * (y - x)) - P.u c) : + P.transitionMatrix z z₀ * bound ≤ P.cont z y - P.cont z x := by + have hterm : P.transitionMatrix z z₀ * + (P.toExtended.valueFunction (y, z₀) - P.toExtended.valueFunction (x, z₀)) + ≤ P.cont z y - P.cont z x := by + simp only [cont, ← Finset.sum_sub_distrib, ← mul_sub] + refine Finset.single_le_sum (f := fun z' : Z => P.transitionMatrix z z' * + (P.toExtended.valueFunction (y, z') - P.toExtended.valueFunction (x, z'))) + (fun z' _ => ?_) (Finset.mem_univ z₀) + exact mul_nonneg (P.transitionMatrix_nonneg _ _) + (sub_nonneg.mpr (P.valueFunction_le_of_le hpc hx hy hxy)) + have hc : 0 < P.consumption (x, z₀) (P.policy (x, z₀)) := P.consumption_policy_pos hpc hx + have hcle : P.consumption (x, z₀) (P.policy (x, z₀)) + ≤ P.income z₀ + (1 + P.interest) * x - assetFloor := by + simp only [consumption, resources, max_eq_right hx.1] + exact sub_le_sub_left (P.policy_mem_region (x, z₀)).1 _ + exact (mul_le_mul_of_nonneg_left + ((hbound _ hc hcle).trans (P.valueFunction_sub_ge hpc hx hy hxy z₀)) + (P.transitionMatrix_nonneg _ _)).trans hterm + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The continuation's gain between two saving levels, in primitives.** -/ +theorem log_cont_sub_ge_gen (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + (hu : P.u = Real.log) (z z₀ : Z) + {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) (hxy : x ≤ y) : + P.transitionMatrix z z₀ * Real.log (1 + (1 + P.interest) * (y - x) + / (P.income z₀ + (1 + P.interest) * x)) + ≤ P.cont z y - P.cont z x := by + subst hfl + have hRh : 0 ≤ (1 + P.interest) * (y - x) := + mul_nonneg P.interest_gt_neg_one.le (sub_nonneg.mpr hxy) + refine P.cont_sub_ge_of_utility_bound hpc z z₀ hx hy hxy ?_ + intro c hc hcle + simp only [sub_zero] at hcle + rw [hu] + have hden : 0 < P.income z₀ + (1 + P.interest) * x := lt_of_lt_of_le hc hcle + have hfrac : 0 ≤ (1 + P.interest) * (y - x) / (P.income z₀ + (1 + P.interest) * x) := + div_nonneg hRh hden.le + rw [← Real.log_div (by linarith) hc.ne'] + refine Real.log_le_log (by linarith) ?_ + rw [le_div_iff₀ hc] + have hkey : (1 + P.interest) * (y - x) / (P.income z₀ + (1 + P.interest) * x) * c + ≤ (1 + P.interest) * (y - x) := by + rw [div_mul_eq_mul_div, div_le_iff₀ hden] + exact mul_le_mul_of_nonneg_left hcle hRh + nlinarith only [hkey] + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **The continuation's gain from zero, in primitives.** Only the term for income state `z₀` is +kept; the rest are non-negative. For a LOW income `z₀` the logarithm is large, and that is the +precautionary motive. -/ +theorem log_cont_sub_ge (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + (hu : P.u = Real.log) (z z₀ : Z) {h : ℝ} + (hh : h ∈ Icc assetFloor assetCap) : + P.transitionMatrix z z₀ * Real.log (1 + (1 + P.interest) * h / P.income z₀) + ≤ P.cont z h - P.cont z 0 := by + subst hfl + have h0 : (0 : ℝ) ∈ Icc (0 : ℝ) assetCap := ⟨le_rfl, P.assetFloor_le_assetCap⟩ + have := P.log_cont_sub_ge_gen rfl hpc hu z z₀ h0 hh hh.1 + simpa using this + +/-- **Aggregate capital is strictly positive, from primitives alone.** Every hypothesis is stated +in `β`, `r`, the income levels and the transition matrix. -/ +theorem aggregateCapital_pos_of_primitives (hfl : assetFloor = 0) + (hpc : P.PositiveConsumption) (hu : P.u = Real.log) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) {z₁ z₀ : Z} {p₀ : ℝ} (hp0 : 0 < p₀) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + {h : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hgain : Real.log (P.income z₁ / (P.income z₁ - h)) + < P.discount * (P.transitionMatrix z₁ z₀ + * Real.log (1 + (1 + P.interest) * h / P.income z₀))) : + 0 < P.aggregateCapital μ := by + subst hfl + refine P.aggregateCapital_pos rfl hu hμ hp0 hp hh0 hhcap hhinc (lt_of_lt_of_le hgain ?_) + exact mul_le_mul_of_nonneg_left (P.log_cont_sub_ge rfl hpc hu z₁ z₀ ⟨hh0.le, hhcap⟩) + P.discount.coe_nonneg + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- **A quantitative lower bound on saving.** A household with enough income saves at least +`h/2`, not merely something positive — which is what a sign change needs, since the rate at +which demand falls below supply has to be chosen from a number. -/ +theorem policy_ge_of_cost (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) (z₁ : Z) + {h κ : ℝ} (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hcost : ∀ R b : ℝ, P.income z₁ ≤ R → 0 ≤ b → b ≤ h → P.u (R - b) - P.u (R - h) ≤ κ) + (hgain : κ < P.discount * (P.cont z₁ h - P.cont z₁ (h / 2))) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + h / 2 ≤ P.policy (a, z₁) := by + subst hfl + by_contra hcon + rw [not_le] at hcon + have hinc0 : 0 < P.income z₁ := lt_trans hh0 hhinc + have hm : P.income z₁ ≤ P.resources (a, z₁) := by + simp only [resources, max_eq_right ha.1] + nlinarith [P.interest_gt_neg_one, ha.1] + have hmh : h < P.resources (a, z₁) := lt_of_lt_of_le hhinc hm + have hfeas : h ∈ P.toExtended.feasible (a, z₁) := by + rw [P.feasible_eq, P.maxSaving_eq] + exact ⟨hh0.le, le_min hhcap hmh.le⟩ + have hch : 0 < P.consumption (a, z₁) h := by simp only [consumption]; linarith + have hopt := P.objR_le_of_mem ha hfeas (P.mem_dom_of_pos hch) + have hbreg : P.policy (a, z₁) ∈ Icc (0 : ℝ) assetCap := P.policy_mem_region _ + have hhalfreg : h / 2 ∈ Icc (0 : ℝ) assetCap := ⟨by linarith, by linarith⟩ + have hmono : P.cont z₁ (P.policy (a, z₁)) ≤ P.cont z₁ (h / 2) := + P.cont_le_of_le hpc z₁ hbreg hhalfreg hcon.le + have hcostle := hcost (P.resources (a, z₁)) (P.policy (a, z₁)) hm hbreg.1 (by linarith) + simp only [objR, consumption] at hopt + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hscaled := mul_le_mul_of_nonneg_left hmono hβ + have hdist : (P.discount : ℝ) * (P.cont z₁ h - P.cont z₁ (h / 2)) + = (P.discount : ℝ) * P.cont z₁ h - (P.discount : ℝ) * P.cont z₁ (h / 2) := by ring + linarith [hopt, hcostle, hgain, hscaled, hdist.le, hdist.ge] + +omit [MeasurableSpace Z] [BorelSpace Z] in +/-- The log instance of `policy_ge_of_cost`. -/ +theorem policy_ge_of_gain (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + (hu : P.u = Real.log) (z₁ : Z) + {h : ℝ} (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hgain : Real.log (P.income z₁ / (P.income z₁ - h)) + < P.discount * (P.cont z₁ h - P.cont z₁ (h / 2))) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + h / 2 ≤ P.policy (a, z₁) := by + subst hfl + have hinc0 : 0 < P.income z₁ := lt_trans hh0 hhinc + refine P.policy_ge_of_cost rfl hpc z₁ hh0 hhcap hhinc (fun R b hm hb0 hbh => ?_) hgain ha + rw [hu] + have hratio : (R - b) / (R - h) ≤ P.income z₁ / (P.income z₁ - h) := by + rw [div_le_div_iff₀ (by linarith) (by linarith)] + nlinarith [hm, hh0, hb0, hhinc] + rw [← Real.log_div (by linarith) (by linarith)] + exact Real.log_le_log (div_pos (by linarith) (by linarith)) hratio + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/SignChange.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/SignChange.lean new file mode 100644 index 000000000..fe348d800 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/SignChange.lean @@ -0,0 +1,444 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Equilibrium.Firm +public import LeanPool.AiyagariEquilibrium.Equilibrium.PositiveCapital +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationConsumption +public import LeanPool.AiyagariEquilibrium.Models.MinimalMPC + +/-! +# The sign change, both ends + +`Equilibrium.Firm` proved the LOW end of the sign change — capital demand diverges as the rate +approaches `-δ`, while capital supply can never exceed the asset cap — and recorded that the HIGH +end was open, needing `capitalDemand δ rhi ≤ S rhi` and hence `S rhi > 0`, which it called +"obvious economically and unproved here". + +That is now proved. `PositiveCapital` gives strictly positive capital supply, and what remains is +to make the bound QUANTITATIVE: the sign change needs a number, not merely positivity, because +`rhi` has to be chosen from it. `le_aggregateCapital` supplies the number from a uniform lower +bound on saving, and `exists_capitalDemand_le` is the mirror of `exists_capitalDemand_ge` — +Cobb–Douglas demand vanishes at high rates, so any positive supply floor is eventually met. + +`exists_sign_change` then brackets the equilibrium from two-sided bounds on capital supply, and +`exists_equilibrium_of_bounds` hands the bracket to `exists_aiyagari_equilibrium`. + +## What is still assumed, and it is no longer the demand side + +The bounds `m ≤ S r ≤ M` are required at every rate. The upper one is free, from +`aggregateCapital_le`. +The lower one is the live hypothesis: `PositiveCapital` proves it at a rate, and making it uniform +over an interval wide enough for the curves to cross is what the crude constants make hard — the +decline condition alone needs `ε > r/(1+r)`, and with `ε ≈ 0.27` that caps the interval near +`r = 0.37`. So the remaining obstruction is a QUANTITATIVE one about the household side, not a +structural gap in the equilibrium argument. +-/ + +@[expose] public section + +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +/-- **Capital demand vanishes at high rates.** The mirror of `exists_capitalDemand_ge`, and what +makes the high end of the sign change a theorem. -/ +theorem exists_capitalDemand_le (A δ : ℝ) {m : ℝ} (hm : 0 < m) (r₀ : ℝ) : + ∃ rhi : ℝ, r₀ ≤ rhi ∧ 0 < rhi + δ ∧ capitalDemand A δ rhi ≤ m := by + have hsq : 0 < Real.sqrt m := Real.sqrt_pos.mpr hm + set t : ℝ := (|A| + 1) / (2 * Real.sqrt m) with ht + have ht0 : 0 < t := by rw [ht]; positivity + refine ⟨max r₀ (t - δ), le_max_left _ _, ?_, ?_⟩ + · have := le_max_right r₀ (t - δ) + linarith + · have hge : t ≤ max r₀ (t - δ) + δ := by + have := le_max_right r₀ (t - δ); linarith + have hpos : 0 < max r₀ (t - δ) + δ := lt_of_lt_of_le ht0 hge + have hts : t ^ 2 * (4 * m) = (|A| + 1) ^ 2 := by + rw [ht] + field_simp + rw [Real.sq_sqrt hm.le] + ring + have hsqle : t ^ 2 ≤ (max r₀ (t - δ) + δ) ^ 2 := by nlinarith [hge, ht0.le] + have hA : A ^ 2 ≤ (|A| + 1) ^ 2 := by nlinarith [abs_nonneg A, sq_abs A] + simp only [capitalDemand] + rw [div_le_iff₀ (by positivity)] + nlinarith [hsqle, hm, hts, hA] + +/-- **The sign change, from two-sided bounds on capital supply.** -/ +theorem exists_sign_change (δ : ℝ) {S : ℝ → ℝ} {m M : ℝ} (hm : 0 < m) + (hlb : ∀ r, m ≤ S r) (hub : ∀ r, S r ≤ M) : + ∃ rlo rhi : ℝ, 0 < rlo + δ ∧ rlo ≤ rhi ∧ S rlo ≤ capitalDemand 1 δ rlo + ∧ capitalDemand 1 δ rhi ≤ S rhi := by + have hM : 0 ≤ M := le_trans hm.le (le_trans (hlb 0) (hub 0)) + obtain ⟨rlo, hrlo, hD⟩ := exists_capitalDemand_ge δ hM + obtain ⟨rhi, hge, _, hD'⟩ := exists_capitalDemand_le 1 δ hm rlo + exact ⟨rlo, rhi, hrlo, hge, le_trans (hub rlo) hD, le_trans hD' (hlb rhi)⟩ + +/-- **The firm can be calibrated to the households**, rather than the other way round. Given any +rate interval and any two-sided bound on capital supply, some productivity and depreciation put +capital demand above supply at the low end and below it at the high end. + +This is what the productivity parameter buys. With `A` fixed at one the demand schedule carries a +scale of its own, and the household economy has to be stretched — in practice to absurd +depreciation rates — for the curves to meet. Here `A` sets the scale and `δ` the curvature, and +the construction is explicit: `η = d √m / (√M + √m)` with `d = rhi - rlo`, then `δ = η - rlo` and +`A = 2 η √M`, at which the low end holds with EQUALITY. -/ +theorem exists_firm_of_bounds {rlo rhi m M : ℝ} (hlt : rlo < rhi) (hm : 0 < m) (hmM : m ≤ M) : + ∃ A δ : ℝ, 0 < A ∧ 0 < rlo + δ ∧ M ≤ capitalDemand A δ rlo ∧ capitalDemand A δ rhi ≤ m := by + have hM : 0 < M := lt_of_lt_of_le hm hmM + set sm : ℝ := Real.sqrt m with hsmdef + set sM : ℝ := Real.sqrt M with hsMdef + have hsm0 : 0 < sm := Real.sqrt_pos.mpr hm + have hsM0 : 0 < sM := Real.sqrt_pos.mpr hM + have hsm2 : sm ^ 2 = m := Real.sq_sqrt hm.le + have hsM2 : sM ^ 2 = M := Real.sq_sqrt hM.le + set d : ℝ := rhi - rlo with hddef + have hd0 : 0 < d := by rw [hddef]; linarith + set η : ℝ := d * sm / (sM + sm) with hηdef + have hη0 : 0 < η := by rw [hηdef]; positivity + have hηkey : η * (sM + sm) = d * sm := by rw [hηdef]; field_simp + refine ⟨2 * η * sM, η - rlo, by positivity, by linarith, ?_, ?_⟩ + · have hr : rlo + (η - rlo) = η := by ring + simp only [capitalDemand, hr] + rw [le_div_iff₀ (by positivity)] + nlinarith [hsM2, hη0] + · have hr : rhi + (η - rlo) = d + η := by rw [hddef]; ring + have hkey : η * sM ≤ sm * (d + η) := by nlinarith [hηkey, hη0, hsm0] + have hsq : (η * sM) ^ 2 ≤ (sm * (d + η)) ^ 2 := by + nlinarith [hkey, mul_pos hη0 hsM0, mul_pos hsm0 (by linarith : (0 : ℝ) < d + η)] + simp only [capitalDemand, hr] + rw [div_le_iff₀ (by positivity)] + nlinarith [hsq, hsM2, hsm2] + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable [MeasurableSpace Z] [BorelSpace Z] +-- Stated at a zero borrowing limit: the quantitative saving bounds and Light's rescaling +-- that these results rest on are proved there. +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-- **A quantitative lower bound on capital supply.** Households in income state `z₁` save at +least `ε`, the stationary distribution puts at least `p₀` of its mass there, and capital is mean +saving — so capital is at least `ε p₀`. Positivity alone would not do: the sign change needs a +number from which to choose `rhi`. -/ +theorem le_aggregateCapital {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) + {z₁ : Z} {p₀ ε : ℝ} (hε : 0 ≤ ε) (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + (hpol : ∀ a ∈ Icc (0 : ℝ) assetCap, ε ≤ P.policy (a, z₁)) : + ε * p₀ ≤ P.aggregateCapital μ := by + rw [P.aggregateCapital_eq_integral_policy hμ] + have hbound : ∀ s : P.State, ε * P.incomeIndicator z₁ s ≤ P.policyCoord s := by + intro s + by_cases hz : s.2 = z₁ + · have hge := hpol _ (s.1.2 : ((s.1 : ℝ)) ∈ Icc (0 : ℝ) assetCap) + have hind : P.incomeIndicator z₁ s = 1 := by simp [hz] + have hpc : P.policyCoord s = P.policy ((s.1 : ℝ), s.2) := rfl + rw [hind, mul_one, hpc, hz] + exact hge + · have hind : P.incomeIndicator z₁ s = 0 := by simp [hz] + rw [hind, mul_zero] + exact (P.policy_mem_region (P.incl s)).1 + calc ε * p₀ ≤ ε * ∫ s, P.incomeIndicator z₁ s ∂(μ : Measure P.State) := + mul_le_mul_of_nonneg_left (P.le_integral_incomeIndicator hμ hp) hε + _ = ∫ s, ε * P.incomeIndicator z₁ s ∂(μ : Measure P.State) := (integral_const_mul _ _).symm + _ ≤ ∫ s, P.policyCoord s ∂(μ : Measure P.State) := + integral_mono (((P.incomeIndicator z₁).integrable _).const_mul ε) + (P.policyCoord.integrable _) hbound + +/-- **The supply floor, in primitives.** Combining the quantitative saving bound with the +primitive bound on the continuation's gain: every stationary distribution carries at least +`h/2 · p₀` of capital. -/ +theorem le_aggregateCapital_of_gain (hpc : P.PositiveConsumption) (hu : P.u = Real.log) + {μ : ProbabilityMeasure P.State} + (hμ : P.IsStationary μ) {z₁ z₀ : Z} {p₀ h : ℝ} (hh0 : 0 < h) + (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + (hgain : Real.log (P.income z₁ / (P.income z₁ - h)) + < P.discount * (P.transitionMatrix z₁ z₀ * Real.log (1 + (1 + P.interest) * (h / 2) + / (P.income z₀ + (1 + P.interest) * (h / 2))))) : + h / 2 * p₀ ≤ P.aggregateCapital μ := by + refine P.le_aggregateCapital hμ (by linarith) hp fun a ha => ?_ + refine P.policy_ge_of_gain rfl hpc hu z₁ hh0 hhcap hhinc ?_ ha + refine lt_of_lt_of_le hgain (mul_le_mul_of_nonneg_left ?_ P.discount.coe_nonneg) + have hhalf : h / 2 ∈ Icc (0 : ℝ) assetCap := ⟨by linarith, by linarith⟩ + have hfull : h ∈ Icc (0 : ℝ) assetCap := ⟨hh0.le, hhcap⟩ + have hgen := P.log_cont_sub_ge_gen rfl hpc hu z₁ z₀ hhalf hfull (by linarith) + rw [show h - h / 2 = h / 2 from by ring] at hgen + exact hgen + +/-! ### The equilibrium, from uniqueness and a floor + +Everything the equilibrium theorem wants is now available on the household side: uniqueness of the +stationary distribution at each rate (from the uniform corner and decline conditions) and a +positive floor under capital supply (from the gain condition, checked at the low end). This +assembles them. + +The selection `ν` is built by choosing the unique stationary distribution at the CLAMPED rate, so +it is defined at every real number while agreeing with the intended one on the interval — the same +device `rateFamily` uses, and for the same reason. Uniqueness then gives both halves of what +`exists_equilibrium_of_selection` asks: that `ν r` is stationary, and that nothing else is. + +`exists_equilibrium_of_technology` is the primitive statement: the technology `(A, δ)` is GIVEN, +and two inequalities on it — enough demand at `rlo` to clear the asset cap, little enough at `rhi` +to fall under the supply floor — deliver the equilibrium. +`exists_equilibrium_of_uniqueness_and_floor` is then the corollary in which the firm is chosen +last, by `exists_firm_of_bounds`, from the supply floor `m` and the asset cap. +-/ + +omit [MeasurableSpace Z] [BorelSpace Z] in +theorem withRate_congr {a b : ℝ} (hab : a = b) (h₁ : P.RateOK a) (h₂ : P.RateOK b) : + P.withRate a h₁ = P.withRate b h₂ := by + subst hab; rfl + +/-- **The same for any continuous demand schedule.** The ceiling at `rlo` and the floor at +`rhi` are compared with `D` there; the selection of stationary distributions supplies the +continuity of supply in between. -/ +theorem exists_equilibrium_of_ceiling_and_floor_of_demand {rlo rhi : ℝ} (hrlo : 0 < 1 + rlo) + (hlt : rlo < rhi) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, + ∃! μ : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary μ) + {m M : ℝ} + (hceil : ∀ hrlo' : P.RateOK rlo, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rlo hrlo').IsStationary μ → P.aggregateCapital μ ≤ M) + (hfloor : ∀ hrhi : P.RateOK rhi, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rhi hrhi).IsStationary μ → m ≤ P.aggregateCapital μ) + (D : ℝ → ℝ) (hD : ContinuousOn D (Icc rlo rhi)) (hDlo : M ≤ D rlo) (hDhi : D rhi ≤ m) : + ∃ r ∈ Icc rlo rhi, IsAiyagariEquilibrium (P.rateFamily hrlo hlt.le) D r := by + classical + have hle : rlo ≤ rhi := hlt.le + have hclamp : ∀ r : ℝ, clampRate rlo rhi r ∈ Icc rlo rhi := clampRate_mem hle + have hpos : ∀ r : ℝ, P.RateOK (clampRate rlo rhi r) := + fun r => rateOK_of_floor_zero (one_add_clampRate_pos hrlo hle r) + have hE : ∀ r : ℝ, ∃! μ : ProbabilityMeasure P.State, + (P.withRate (clampRate rlo rhi r) (hpos r)).IsStationary μ := + fun r => huniq _ (hclamp r) (hpos r) + set ν : ℝ → ProbabilityMeasure P.State := fun r => (hE r).choose with hνdef + have hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r) := by + intro r hr hrr + have hc : clampRate rlo rhi r = r := clampRate_eq hr + rw [← P.withRate_congr hc (hpos r) hrr] + exact (hE r).choose_spec.1 + have hunique : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, ∀ μ : ProbabilityMeasure P.State, + (P.withRate r hrr).IsStationary μ → μ = ν r := by + intro r hr hrr μ hμ + have hc : clampRate rlo rhi r = r := clampRate_eq hr + refine (hE r).choose_spec.2 μ ?_ + rw [P.withRate_congr hc (hpos r) hrr] + exact hμ + have hlo' : rlo ∈ Icc rlo rhi := ⟨le_rfl, hle⟩ + have hhi' : rhi ∈ Icc rlo rhi := ⟨hle, le_rfl⟩ + have hrlo' : P.RateOK rlo := rateOK_of_floor_zero hrlo + have hrhi : P.RateOK rhi := rateOK_of_floor_zero (by linarith) + have hub : P.aggregateCapital (ν rlo) ≤ M := hceil hrlo' _ (hν rlo hlo' hrlo') + have hlbhi : m ≤ P.aggregateCapital (ν rhi) := hfloor hrhi _ (hν rhi hhi' hrhi) + exact P.exists_equilibrium_of_selection hrlo hle ν hν hunique D hD + (le_trans hub hDlo) (le_trans hDhi hlbhi) + +/-- **An Aiyagari equilibrium with the technology GIVEN.** The firm is no longer chosen to fit +the household: any `(A, δ)` whose demand clears the asset cap at the bottom of the interval and +falls below the supply floor at the top produces an equilibrium inside it. + +The two hypotheses are inequalities on the technology and nothing else — `le_capitalDemand_of_sq` +and `capitalDemand_le_of_sq` turn them into `4(rlo+δ)²·assetCap ≤ A²` and +`A² ≤ 4(rhi+δ)²·m`, which is the band `2(rlo+δ)√assetCap ≤ A ≤ 2(rhi+δ)√m`. Such an `A` exists +exactly when `(rlo+δ)√assetCap ≤ (rhi+δ)√m`, so the band is non-empty for small `δ` whenever the +household has a positive supply floor: that is the content of +`exists_equilibrium_of_uniqueness_and_floor`, which is now a corollary. + +Note which bound is used where. The ceiling is the free one, `aggregateCapital_le`; only the floor +`m` is household work. -/ +theorem exists_equilibrium_of_ceiling_and_floor {rlo rhi : ℝ} (hrlo : 0 < 1 + rlo) + (hlt : rlo < rhi) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, + ∃! μ : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary μ) + {m M : ℝ} + (hceil : ∀ hrlo' : P.RateOK rlo, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rlo hrlo').IsStationary μ → P.aggregateCapital μ ≤ M) + (hfloor : ∀ hrhi : P.RateOK rhi, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rhi hrhi).IsStationary μ → m ≤ P.aggregateCapital μ) + (A δ : ℝ) (hδ : 0 < rlo + δ) + (hDlo : M ≤ capitalDemand A δ rlo) (hDhi : capitalDemand A δ rhi ≤ m) : + ∃ r ∈ Icc rlo rhi, + IsAiyagariEquilibrium (P.rateFamily hrlo hlt.le) (capitalDemand A δ) r := + P.exists_equilibrium_of_ceiling_and_floor_of_demand hrlo hlt huniq hceil hfloor + (capitalDemand A δ) (continuousOn_capitalDemand hδ) hDlo hDhi + +/-- **The same with the free ceiling.** Capital supply can never exceed the asset cap, so a +technology that clears the cap at `rlo` needs no household-side ceiling at all. This is the form +every witness used before `crra_aggregateCapital_le_of_minMPC` gave a sharper one. -/ +theorem exists_equilibrium_of_technology {rlo rhi : ℝ} (hrlo : 0 < 1 + rlo) (hlt : rlo < rhi) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, + ∃! μ : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary μ) + {m : ℝ} + (hfloor : ∀ hrhi : P.RateOK rhi, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rhi hrhi).IsStationary μ → m ≤ P.aggregateCapital μ) + (A δ : ℝ) (hδ : 0 < rlo + δ) + (hDlo : assetCap ≤ capitalDemand A δ rlo) (hDhi : capitalDemand A δ rhi ≤ m) : + ∃ r ∈ Icc rlo rhi, + IsAiyagariEquilibrium (P.rateFamily hrlo hlt.le) (capitalDemand A δ) r := + P.exists_equilibrium_of_ceiling_and_floor hrlo hlt huniq + (fun _ μ _ => P.aggregateCapital_le μ) hfloor A δ hδ hDlo hDhi + +/-- **An Aiyagari equilibrium from household-side hypotheses alone.** Uniqueness of the stationary +distribution at each rate, and a positive floor under capital supply AT THE TOP OF THE INTERVAL, +suffice: the firm is then calibrated to meet them. + +The floor is needed only at `rhi`, which matters: `gain_term_mono` says the gain condition is +easier at higher rates, so the floor may be taken at the best rate rather than the worst. -/ +theorem exists_equilibrium_of_uniqueness_and_floor {rlo rhi : ℝ} (hrlo : 0 < 1 + rlo) + (hlt : rlo < rhi) + (huniq : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, + ∃! μ : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary μ) + {m : ℝ} (hm : 0 < m) + (hfloor : ∀ hrhi : P.RateOK rhi, ∀ μ : ProbabilityMeasure P.State, + (P.withRate rhi hrhi).IsStationary μ → m ≤ P.aggregateCapital μ) : + ∃ A δ : ℝ, 0 < A ∧ 0 < rlo + δ ∧ ∃ r ∈ Icc rlo rhi, + IsAiyagariEquilibrium (P.rateFamily hrlo hlt.le) (capitalDemand A δ) r := by + classical + have hle : rlo ≤ rhi := hlt.le + have hrhi : P.RateOK rhi := rateOK_of_floor_zero (by linarith) + -- the household side supplies the two numbers the firm has to be squeezed between + obtain ⟨μ₀, hμ₀⟩ := (huniq rhi ⟨hle, le_rfl⟩ hrhi).exists + have hmM : m ≤ assetCap := + le_trans (hfloor hrhi μ₀ hμ₀) (P.aggregateCapital_le _) + obtain ⟨A, δ, hA, hrδ, hDlo, hDhi⟩ := exists_firm_of_bounds hlt hm hmM + exact ⟨A, δ, hA, hrδ, + P.exists_equilibrium_of_technology hrlo hlt huniq hfloor A δ hrδ hDlo hDhi⟩ + +/-- **Capital supply is bounded above by the impatience of the household.** Every household saves +at most `1 - ε` of its resources, capital is mean saving, and resources are income plus the return +on capital — so the bound closes on itself. + +This is the quantitative form of "assets are small when the household is impatient", and it blows +up exactly as `(1-ε)(1+r) → 1`, which is where the household stops running its assets down. -/ +theorem aggregateCapital_le_of_consumption_bound {μ : ProbabilityMeasure P.State} + (hμ : P.IsStationary μ) {ε : ℝ} (hε1 : ε ≤ 1) + (hlt : (1 - ε) * (1 + P.interest) < 1) + (hlb : ∀ (z : Z), ∀ a ∈ Icc (0 : ℝ) assetCap, ε * P.resources (a, z) ≤ P.consumptionFn z a) : + P.aggregateCapital μ ≤ (1 - ε) * P.maxIncome / (1 - (1 - ε) * (1 + P.interest)) := by + have hε0 : (0 : ℝ) ≤ 1 - ε := by linarith + have hr : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + set K : ℝ := P.aggregateCapital μ with hKdef + have hK : K = ∫ s, P.policyCoord s ∂(μ : Measure P.State) := + P.aggregateCapital_eq_integral_policy hμ + have hA : ∫ s, P.assetCoord s ∂(μ : Measure P.State) = K := rfl + have hbound : ∀ s : P.State, P.policyCoord s + ≤ (1 - ε) * P.maxIncome + (1 - ε) * (1 + P.interest) * P.assetCoord s := by + intro s + have hmem : ((s.1 : ℝ)) ∈ Icc (0 : ℝ) assetCap := s.1.2 + have hlbs := hlb s.2 _ hmem + have hres : P.resources ((s.1 : ℝ), s.2) = P.income s.2 + (1 + P.interest) * (s.1 : ℝ) := by + simp only [resources, max_eq_right hmem.1] + have hpol : P.policyCoord s + = P.resources ((s.1 : ℝ), s.2) - P.consumptionFn s.2 (s.1 : ℝ) := by + simp only [policyCoord_apply, incl, consumptionFn, consumption]; ring + have hinc : P.income s.2 ≤ P.maxIncome := P.le_maxIncome _ + have hacoord : P.assetCoord s = (s.1 : ℝ) := rfl + rw [hpol, hres, hacoord] + rw [hres] at hlbs + nlinarith [hlbs, hinc, hε0, hr, hmem.1] + have hsplit : ∫ s, ((1 - ε) * P.maxIncome + (1 - ε) * (1 + P.interest) * P.assetCoord s) + ∂(μ : Measure P.State) + = (1 - ε) * P.maxIncome + (1 - ε) * (1 + P.interest) * K := by + have hprob : IsProbabilityMeasure (μ : Measure P.State) := μ.2 + rw [integral_add (integrable_const _) ((P.assetCoord.integrable _).const_mul _)] + have h1 : ∫ _s : P.State, (1 - ε) * P.maxIncome ∂(μ : Measure P.State) + = (1 - ε) * P.maxIncome := by simp + have h2 : ∫ s, (1 - ε) * (1 + P.interest) * P.assetCoord s ∂(μ : Measure P.State) + = (1 - ε) * (1 + P.interest) * K := by rw [integral_const_mul, hA] + rw [h1, h2] + have hint : ∫ s, P.policyCoord s ∂(μ : Measure P.State) + ≤ (1 - ε) * P.maxIncome + (1 - ε) * (1 + P.interest) * K := by + rw [← hsplit] + exact integral_mono (P.policyCoord.integrable _) + ((integrable_const _).add ((P.assetCoord.integrable _).const_mul _)) hbound + rw [← hK] at hint + rw [le_div_iff₀ (by linarith)] + nlinarith [hint] + +/-- **A quantitative ceiling on capital supply, from primitives.** The minimal-MPC bound says the +household consumes at least the share `κ` of cash on hand; feeding that to +`aggregateCapital_le_of_consumption_bound` replaces the asset cap, which is bookkeeping, by a +number the calibration determines. The denominator is `1 - Þ`, so the ceiling blows up exactly as +the household stops being impatient. -/ +theorem crra_aggregateCapital_le_of_minMPC {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcapit : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) : + P.aggregateCapital μ + ≤ (1 - P.minMPC γ) * P.maxIncome / (1 - (1 - P.minMPC γ) * (1 + P.interest)) := by + refine P.aggregateCapital_le_of_consumption_bound hμ + IncomeFluctuation.minMPC_le_one ?_ ?_ + · rw [IncomeFluctuation.one_sub_minMPC_mul] + exact IncomeFluctuation.patience_lt_one hγ0 hβR + · exact fun z a ha => P.crra_minMPC_mul_le_consumptionFn hγ0 hu rfl hκ hβ hpos hcapit z ha + +/-- **The ceiling for shifted CRRA.** The subsistence level enters through `minMPCShare`, which +charges the constant in `κ(η+m) ≤ η+c` against the smallest cash on hand there can be. At `η = 0` +it is the CRRA statement. -/ +theorem hara_aggregateCapital_le_of_minMPC {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hshare : (1 - P.minMPCShare γ η) * (1 + P.interest) < 1) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcapit : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) : + P.aggregateCapital μ + ≤ (1 - P.minMPCShare γ η) * P.maxIncome + / (1 - (1 - P.minMPCShare γ η) * (1 + P.interest)) := by + refine P.aggregateCapital_le_of_consumption_bound hμ ?_ hshare ?_ + · have hκ : P.minMPC γ ≤ 1 := IncomeFluctuation.minMPC_le_one + have hy : 0 < P.minIncome := P.minIncome_pos + have : (0 : ℝ) ≤ (1 - P.minMPC γ) * η / P.minIncome := by + apply div_nonneg (mul_nonneg (by linarith) hη) hy.le + simp only [IncomeFluctuation.minMPCShare] + linarith + · exact fun z a ha => + P.hara_minMPCShare_mul_le_consumptionFn hγ0 hη hu rfl hκ hβ hpos hcapit z ha + +end IncomeFluctuation + +/-! ### Uniformity in the interest rate + +The supply floor has to hold at every rate in the interval, and the gain condition is MONOTONE in +the rate: raising `r` raises `(1+r) t / (y₀ + (1+r) t)` towards one, so the right-hand side grows +while the left does not move. Checking the condition at the LOW end of the interval therefore +covers all of it. -/ + +theorem gain_term_mono {y₀ t : ℝ} (hy₀ : 0 < y₀) (ht : 0 < t) {s₀ s₁ : ℝ} + (hs₀ : 0 < s₀) (hs : s₀ ≤ s₁) : + Real.log (1 + s₀ * t / (y₀ + s₀ * t)) ≤ Real.log (1 + s₁ * t / (y₀ + s₁ * t)) := by + have h0 : 0 < y₀ + s₀ * t := by positivity + have h1 : 0 < y₀ + s₁ * t := by nlinarith + refine Real.log_le_log (by positivity) ?_ + have hfrac : s₀ * t / (y₀ + s₀ * t) ≤ s₁ * t / (y₀ + s₁ * t) := by + rw [div_le_div_iff₀ h0 h1] + nlinarith [mul_le_mul_of_nonneg_right hs (mul_nonneg ht.le hy₀.le)] + linarith + +/-- **An Aiyagari equilibrium with the Cobb–Douglas firm**, from two-sided bounds on capital +supply. Both ends of the sign change are now theorems; what is assumed is a positive floor under +capital supply, uniform in the rate. -/ +theorem exists_equilibrium_of_bounds {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] + [DiscreteTopology Z] [MeasurableSpace Z] [BorelSpace Z] {assetFloor assetCap : ℝ} + {Pf : ℝ → IncomeFluctuation Z assetFloor assetCap} {S : ℝ → ℝ} (δ : ℝ) {m M : ℝ} (hm : 0 < m) + (hlb : ∀ r, m ≤ S r) (hub : ∀ r, S r ≤ M) + (hS : ∀ r, ∃ μ : ProbabilityMeasure (Pf r).State, + (Pf r).IsStationary μ ∧ (Pf r).aggregateCapital μ = S r) + (hScont : ∀ rlo rhi : ℝ, ContinuousOn S (Icc rlo rhi)) : + ∃ rlo rhi : ℝ, rlo ≤ rhi ∧ ∃ r ∈ Icc rlo rhi, + IsAiyagariEquilibrium Pf (capitalDemand 1 δ) r := by + obtain ⟨rlo, rhi, hrlo, hle, hloS, hhiS⟩ := exists_sign_change δ hm hlb hub + refine ⟨rlo, rhi, hle, exists_aiyagari_equilibrium hle hS (hScont rlo rhi) + (continuousOn_capitalDemand hrlo) hloS hhiS⟩ + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Equilibrium/Uniqueness.lean b/LeanPool/AiyagariEquilibrium/Equilibrium/Uniqueness.lean new file mode 100644 index 000000000..2a5c2448c --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Equilibrium/Uniqueness.lean @@ -0,0 +1,118 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Equilibrium.Firm + +/-! +# Uniqueness of the equilibrium interest rate + +Existence came from the intermediate value theorem. Uniqueness comes from single crossing: +capital demand falls in the interest rate, so if capital supply RISES in it the two curves +meet at most once. This file proves that skeleton and supplies the demand half. + +## What the literature says, and why only the skeleton is here + +The supply half is the whole difficulty, and it is not a theorem about the model — it is a +restriction on the calibration. + +Light (2018) proves uniqueness for CRRA utility and Cobb–Douglas production when relative +risk aversion `γ ≤ 1`. The argument is exactly the one below: individual savings rise with +the interest rate (his Theorem 1), hence aggregate savings do (Theorem 2), and since the +capital-to-wage ratio `k(R)/w(R)` strictly falls, the curves cross once (Theorem 3). The +economics of `γ ≤ 1` is that the substitution effect dominates the income effect: a higher +rate raises the price of current consumption, but also makes existing wealth worth more, and +only when `γ ≤ 1` does the first dominate. + +For `γ > 1` supply need not rise. Açıkgöz (2018) exhibits multiple stationary equilibria at +`γ = 6.5`. Walsh and Young's +[November 19, 2025 draft](https://www.aeaweb.org/conference/2026/preliminary/paper/i57n5khB) +uses 24,000 parameter draws, retains 23,673 after numerical screening, and reports 250 cases +with three equilibria. They report that they never see multiplicity with risk +aversion below about 1.49, depreciation below about 0.19, or income persistence below about +0.47, and only ever with a disaster state for income. Their mechanism is precautionary: with +a disaster state, persistent income and high risk aversion, agents facing a low return still +amass capital to insure against near-zero income, so capital supply bends back and can cross +downward-sloping demand more than once. +These findings concern their competitive economies, not counterexamples instantiating this +capped household and its prescribed normalized demand. + +Kirkby (2018) approaches it computationally and finds both Aiyagari (1994) and Pijoan-Mas +(2006) "almost certainly have a unique general equilibrium". + +So the honest formal target is CONDITIONAL uniqueness — uniqueness given monotone supply — +plus, separately, whatever route establishes monotone supply for a restricted class. That is +what is proved here; monotone supply remains an explicit input rather than a claim about every +calibration. +-/ + +@[expose] public section + +open Set + +namespace LeanEconomics + +/-- **Single crossing.** A non-decreasing supply curve meets a strictly decreasing demand +curve at most once. This is the skeleton of Light (2018) Theorem 3. -/ +theorem eq_of_monotoneOn_of_strictAntiOn {S D : ℝ → ℝ} {s : Set ℝ} + (hS : MonotoneOn S s) (hD : StrictAntiOn D s) {r₁ r₂ : ℝ} (h₁ : r₁ ∈ s) (h₂ : r₂ ∈ s) + (he₁ : S r₁ = D r₁) (he₂ : S r₂ = D r₂) : r₁ = r₂ := by + by_contra hne + rcases lt_or_gt_of_ne hne with h | h + · have hs := hS h₁ h₂ h.le + have hd := hD h₁ h₂ h + rw [he₁, he₂] at hs + exact absurd hs (not_le.mpr hd) + · have hs := hS h₂ h₁ h.le + have hd := hD h₂ h₁ h + rw [he₁, he₂] at hs + exact absurd hs (not_le.mpr hd) + +/-- Capital demand falls STRICTLY in the interest rate, which is the half of single crossing +that is a theorem about the technology rather than about the households. -/ +theorem capitalDemand_strictAntiOn {A δ rlo rhi : ℝ} (hA : A ≠ 0) (h : 0 < rlo + δ) : + StrictAntiOn (capitalDemand A δ) (Icc rlo rhi) := by + intro a ha b hb hab + have ha' : 0 < a + δ := lt_of_lt_of_le h (by linarith [ha.1]) + have hb' : 0 < b + δ := by linarith + have hA2 : 0 < A ^ 2 := by positivity + simp only [capitalDemand] + rw [div_lt_div_iff₀ (by positivity) (by positivity)] + have hsq : (a + δ) ^ 2 < (b + δ) ^ 2 := by nlinarith [ha', hb'] + nlinarith [hA2, hsq] + +/-- **Conditional uniqueness of the equilibrium interest rate.** Given that capital supply is +non-decreasing in the rate, the equilibrium rate is unique. + +The hypothesis `hS` is the entire economic content, and it is restrictive: it holds for CRRA +with `γ ≤ 1` (Light 2018) and fails for the calibrations of Açıkgöz (2018) and Walsh and +Young (2025). See the module docstring. -/ +theorem equilibriumRate_unique {A δ rlo rhi : ℝ} (hA : A ≠ 0) (hδ : 0 < rlo + δ) {S : ℝ → ℝ} + (hS : MonotoneOn S (Icc rlo rhi)) {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Icc rlo rhi) (h₂ : r₂ ∈ Icc rlo rhi) + (he₁ : S r₁ = capitalDemand A δ r₁) (he₂ : S r₂ = capitalDemand A δ r₂) : r₁ = r₂ := + eq_of_monotoneOn_of_strictAntiOn hS (capitalDemand_strictAntiOn hA hδ) h₁ h₂ he₁ he₂ + +/-- **A larger capital supply means a lower equilibrium rate.** With demand strictly decreasing, +two economies whose supply schedules are ordered have their equilibrium rates ordered the other +way. This is what uniqueness buys: without it "the" equilibrium rate is not a number to compare. + +The hypothesis is only needed at `r₂`, and only monotonicity of the SMALLER supply is used. -/ +theorem equilibriumRate_le_of_supply_le {A δ rlo rhi : ℝ} (hA : A ≠ 0) (hδ : 0 < rlo + δ) + {S T : ℝ → ℝ} (hS : MonotoneOn S (Icc rlo rhi)) (hle : ∀ r ∈ Icc rlo rhi, S r ≤ T r) + {r₁ r₂ : ℝ} (h₁ : r₁ ∈ Icc rlo rhi) (h₂ : r₂ ∈ Icc rlo rhi) + (he₁ : S r₁ = capitalDemand A δ r₁) (he₂ : T r₂ = capitalDemand A δ r₂) : r₂ ≤ r₁ := by + by_contra hcon + rw [not_le] at hcon + have hD : capitalDemand A δ r₂ < capitalDemand A δ r₁ := + capitalDemand_strictAntiOn hA hδ h₁ h₂ hcon + have hchain : capitalDemand A δ r₁ ≤ capitalDemand A δ r₂ := by + calc capitalDemand A δ r₁ = S r₁ := he₁.symm + _ ≤ S r₂ := hS h₁ h₂ hcon.le + _ ≤ T r₂ := hle r₂ h₂ + _ = capitalDemand A δ r₂ := he₂ + linarith + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Imports.lean b/LeanPool/AiyagariEquilibrium/Imports.lean new file mode 100644 index 000000000..5a8d445cc --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Imports.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Lean Pool contributors. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Lean Pool contributors +-/ +module -- shake: keep-all --deprecated_module: ignore + +-- Generated project imports; run `lake exe mk_all`. +public import LeanPool.AiyagariEquilibrium +public import LeanPool.AiyagariEquilibrium.Analysis.DecreasingIncrements +public import LeanPool.AiyagariEquilibrium.Analysis.DifferentiableSandwich +public import LeanPool.AiyagariEquilibrium.Analysis.HARA +public import LeanPool.AiyagariEquilibrium.Analysis.PowerMean +public import LeanPool.AiyagariEquilibrium.Analysis.RelativeRiskAversion +public import LeanPool.AiyagariEquilibrium.Distribution.Feller +public import LeanPool.AiyagariEquilibrium.Distribution.Stationary +public import LeanPool.AiyagariEquilibrium.Distribution.Uniqueness +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Bellman +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Blackwell +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Extended +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.ExtendedStochastic +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Stochastic +public import LeanPool.AiyagariEquilibrium.Equilibrium.Aiyagari +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariContinuity +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariUniqueness +public import LeanPool.AiyagariEquilibrium.Equilibrium.Bisection +public import LeanPool.AiyagariEquilibrium.Equilibrium.CapitalSupplyMonotone +public import LeanPool.AiyagariEquilibrium.Equilibrium.Firm +public import LeanPool.AiyagariEquilibrium.Equilibrium.PositiveCapital +public import LeanPool.AiyagariEquilibrium.Equilibrium.SignChange +public import LeanPool.AiyagariEquilibrium.Equilibrium.Uniqueness +public import LeanPool.AiyagariEquilibrium.Models.AiyagariTauchenWitness +public import LeanPool.AiyagariEquilibrium.Models.BoundedIncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Models.CRRA +public import LeanPool.AiyagariEquilibrium.Models.CRRAConstants +public import LeanPool.AiyagariEquilibrium.Models.ConsumptionFloor +public import LeanPool.AiyagariEquilibrium.Models.ConsumptionSavingsUnbounded +public import LeanPool.AiyagariEquilibrium.Models.EulerCorner +public import LeanPool.AiyagariEquilibrium.Models.ImpatientDecline +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationAugmented +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationBounds +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationCarrollKimball +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationConsumption +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationCrossRate +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEnvelope +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEuler +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationHARA +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationIterate +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationLipschitz +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationMonotone +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRate +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRateMonotone +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRateStep +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRegion +public import LeanPool.AiyagariEquilibrium.Models.LargeBetaLight +public import LeanPool.AiyagariEquilibrium.Models.LogAiyagari +public import LeanPool.AiyagariEquilibrium.Models.MinimalMPC +public import LeanPool.AiyagariEquilibrium.Models.MonotoneTransitions +public import LeanPool.AiyagariEquilibrium.Models.NaturalAssetBound +public import LeanPool.AiyagariEquilibrium.Models.Tauchen +public import LeanPool.AiyagariEquilibrium.Topology.Berge +public import LeanPool.AiyagariEquilibrium.Topology.BergeEReal +public import LeanPool.AiyagariEquilibrium.Topology.IccCorrespondence diff --git a/LeanPool/AiyagariEquilibrium/Models/AiyagariTauchenWitness.lean b/LeanPool/AiyagariEquilibrium/Models/AiyagariTauchenWitness.lean new file mode 100644 index 000000000..1147a421d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/AiyagariTauchenWitness.lean @@ -0,0 +1,131 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.Tauchen +public import LeanPool.AiyagariEquilibrium.Equilibrium.AiyagariUniqueness + +/-! +# Finite Tauchen earnings for normalized capped-log clearing + +The earnings chain discretizes `log l' = ρ log l + σ √(1 - ρ²) ε`, with standard normal `ε`, +on `n + 2` grid points in `[-3σ, 3σ]`. Its earnings scale `c > 0` is arbitrary: no mean-one +endowment normalization is imposed. Monotone transitions and positive one-step access to the +lowest state follow from `monotoneTransitions_of_tauchen` and `tauchen_pos_zero`. + +The household has discount `24/25`, log utility, no borrowing and a fixed finite asset cap. +The rate-uniqueness endpoint uses the prescribed unit-wage normalized clearing equation +`aggregateCapital μ = normalisedDemand (9/25) (2/25) r`, with demand `9 / (16 * (r + 2/25))`, +and an explicit cap-slack inequality at the larger rate. + +A bridge from these household units and the fixed cap to full competitive market clearing +for arbitrary earnings scales is not established here. The theorem concerns this explicit +normalized clearing equation. +-/ + +@[expose] public section + +open Set MeasureTheory + +namespace LeanEconomics + +/-- A capped log household with a Tauchen earnings chain: `β = 0.96`, no borrowing, +`n + 2` grid points on `[-3σ, 3σ]`, persistence `ρ`, unconditional standard deviation `σ` +and arbitrary positive earnings scale `c`. -/ +noncomputable def aiyagariTauchen (n : ℕ) (ρ σ c cap : ℝ) (hρ0 : 0 ≤ ρ) (hρ1 : ρ < 1) + (hσ : 0 < σ) (hc : 0 < c) (hcap : 0 ≤ cap) : IncomeFluctuation (Fin (n + 2)) 0 cap where + income i := c * Real.exp (-3 * σ + i * (6 * σ / (n + 1))) + transitionMatrix := tauchen ρ (σ * Real.sqrt (1 - ρ ^ 2)) (-3 * σ) (6 * σ / (n + 1)) (n + 1) + interest := 0 + discount := 24 / 25 + u := crraUtility 1 + minIncome := c * Real.exp (-3 * σ) + maxIncome := c * Real.exp (3 * σ) + minIncome_pos := mul_pos hc (Real.exp_pos _) + minIncome_le i := by + apply mul_le_mul_of_nonneg_left _ hc.le + apply Real.exp_le_exp.mpr + have hi : (0 : ℝ) ≤ i := Nat.cast_nonneg _ + have hw : (0 : ℝ) ≤ 6 * σ / (n + 1) := by positivity + nlinarith + le_maxIncome i := by + apply mul_le_mul_of_nonneg_left _ hc.le + apply Real.exp_le_exp.mpr + have hi : (i : ℝ) ≤ n + 1 := by exact_mod_cast Nat.le_of_lt_succ i.isLt + have hw : (0 : ℝ) ≤ 6 * σ / (n + 1) := by positivity + have h1 : (i : ℝ) * (6 * σ / (n + 1)) ≤ (n + 1) * (6 * σ / (n + 1)) := + mul_le_mul_of_nonneg_right hi hw + have h2 : ((n : ℝ) + 1) * (6 * σ / (n + 1)) = 6 * σ := by field_simp + linarith + transitionMatrix_nonneg i j := + tauchen_nonneg (mul_pos hσ (Real.sqrt_pos.mpr (by nlinarith))) (by positivity) i j + transitionMatrix_sum i := tauchen_sum i + interest_gt_neg_one := by norm_num + assetFloor_le_assetCap := hcap + assetCap_nonneg := hcap + minConsumption_pos := by + have := mul_pos hc (Real.exp_pos (-3 * σ)) + linarith + minConsumption_le_floor := le_rfl + discount_lt_one := by norm_num + dom := Ioi 0 + Ioi_subset_dom := subset_rfl + dom_subset_Ici := Ioi_subset_Ici_self + continuousOn_u_dom := continuousOn_crraUtility 1 + monotoneOn_u_dom := monotoneOn_crraUtility 1 + strictConcaveOn_u_dom := strictConcaveOn_crraUtility one_pos + continuousOn_extendDom := + continuousOn_extendDom_Ioi (continuousOn_crraUtility 1) (tendsto_atBot_crraUtility le_rfl) + +variable {n : ℕ} {ρ σ c cap : ℝ} {hρ0 : 0 ≤ ρ} {hρ1 : ρ < 1} {hσ : 0 < σ} {hc : 0 < c} + {hcap : 0 ≤ cap} + +/-- Income rises strictly with the state. -/ +theorem aiyagariTauchen_income_strictMono : + StrictMono (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).income := by + intro i j hij + apply mul_lt_mul_of_pos_left _ hc + apply Real.exp_lt_exp.mpr + have hw : (0 : ℝ) < 6 * σ / (n + 1) := by positivity + have : (i : ℝ) < j := by exact_mod_cast hij + nlinarith + +/-- The lowest state has the lowest income, which is `minIncome`. -/ +theorem aiyagariTauchen_income_zero : + (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).income 0 + = (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).minIncome := by + change c * Real.exp (-3 * σ + ((0 : Fin (n + 2)) : ℝ) * (6 * σ / (n + 1))) = c * Real.exp (-3 * σ) + simp + +/-- The chain has monotone transitions. -/ +theorem aiyagariTauchen_monotone : + (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).MonotoneTransitions := + monotoneTransitions_of_tauchen _ rfl aiyagariTauchen_income_strictMono hρ0 + (mul_pos hσ (Real.sqrt_pos.mpr (by nlinarith))) (by positivity) + +/-- **Two normalized clearing rates in `(-δ, λ)` coincide** for the finite Tauchen household, +for any number of states, `ρ ∈ [0, 1)`, `σ > 0` and positive earnings scale, given the cap +condition at the larger rate. The hypotheses use the explicit prescribed normalized demand. -/ +theorem aiyagariTauchen_equilibriumRate_unique_Ioo {r₁ r₂ : ℝ} + (h₁ : r₁ ∈ Ioo (-2 / 25 : ℝ) (1 / 24)) (h₂ : r₂ ∈ Ioo (-2 / 25 : ℝ) (1 / 24)) + (hcapc : 2 * (24 / 25) * (c * Real.exp (3 * σ)) < (1 - 24 / 25 * (1 + max r₁ r₂)) * cap) + (hrr₁ : (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).RateOK r₁) + (hrr₂ : (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).RateOK r₂) + {μ₁ μ₂ : ProbabilityMeasure (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).State} + (hμ₁ : ((aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).withRate r₁ hrr₁).IsStationary μ₁) + (hμ₂ : ((aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).withRate r₂ hrr₂).IsStationary μ₂) + (he₁ : (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).aggregateCapital μ₁ + = normalisedDemand (9 / 25) (2 / 25) r₁) + (he₂ : (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).aggregateCapital μ₂ + = normalisedDemand (9 / 25) (2 / 25) r₂) : r₁ = r₂ := + (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).aiyagari1994_log_equilibriumRate_unique_Ioo + rfl rfl rfl aiyagariTauchen_monotone (z₀ := 0) + (fun z => by + rw [aiyagariTauchen_income_zero] + exact (aiyagariTauchen n ρ σ c cap hρ0 hρ1 hσ hc hcap).minIncome_le z) + aiyagariTauchen_income_zero (fun z => tauchen_pos_zero z) h₁ h₂ hcapc hrr₁ hrr₂ hμ₁ hμ₂ he₁ he₂ + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/BoundedIncomeFluctuation.lean b/LeanPool/AiyagariEquilibrium/Models/BoundedIncomeFluctuation.lean new file mode 100644 index 000000000..ac92e7b7b --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/BoundedIncomeFluctuation.lean @@ -0,0 +1,251 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.CRRA +public import LeanPool.AiyagariEquilibrium.Models.ConsumptionFloor +public import LeanPool.AiyagariEquilibrium.Distribution.Stationary +public import LeanPool.AiyagariEquilibrium.Equilibrium.PositiveCapital +public import Mathlib.Analysis.Convex.SpecificFunctions.Pow + +/-! +# CES with `γ < 1`: utility bounded below + +`crra_pinch` showed that CRRA was confined to log: `tendsto_atBot_u` forced `γ ≥ 1`, and Light's +uniqueness condition forced `γ ≤ 1`. This file removes the first half of the pincer. + +## What had to change, and where + +The obstruction was never the axiom by itself. `tendsto_atBot_u` is what makes `extendBot u` +CONTINUOUS into `EReal`, and continuity of the reward is what Berge's maximum theorem runs on. +With `u (0⁺)` finite, `extendBot u` drops from a finite limit to `⊥` at zero consumption: not even +upper semicontinuous, so the maximum need not be attained. Deleting the axiom would have broken +`exists_optimal_action`, not weakened it. + +`IncomeFluctuation` was therefore refactored over a utility DOMAIN, with `continuousOn_extendDom` +in place of `tendsto_atBot_u`. All that remains here is to take `dom = Ici 0` and check that CES +with `γ < 1` fits. + +## Two things it costs + +`continuousOn_extendDom` is discharged differently — `continuousOn_extendDom_Ici` below, where the +`⊥` branch is simply unreachable — and that part is free. + +Positive consumption at the optimum is not. In the unbounded model it is a corollary of the reward +being `⊥` there. Here the reward at `c = 0` is `u 0`, a perfectly ordinary number, and the +household has to be shown not to want it. That is `MarginalInadaOn`: marginal utility explodes at +zero even though utility itself does not, and `positiveConsumption_of_marginalInada` turns it into +the `PositiveConsumption` the rest of the development asks for. Note that the condition has to +reach `c = 0` itself, which is why `MarginalInada` was generalised to a domain. + +## What comes for free + +Everything stated for `IncomeFluctuation`: the Bellman equation, uniqueness of the optimal action, +concavity and monotonicity of the value function, `policy_mono`, the Feller property, stationary +distributions, the Aiyagari equilibrium machinery. There is no second structure to carry them to. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology MeasureTheory + +namespace LeanEconomics + +/-! ### The domain `Ici 0` -/ + +/-- **The bounded case discharges the continuity requirement trivially.** On `Ici 0` the `⊥` +branch of `extendDom` is never taken, so what is left is continuity of `u` itself. -/ +theorem continuousOn_extendDom_Ici {u : ℝ → ℝ} (hc : ContinuousOn u (Ici 0)) : + ContinuousOn (extendDom (Ici 0) u) (Ici 0) := + (continuous_coe_real_ereal.comp_continuousOn hc).congr fun _ hx => extendDom_of_mem hx + +/-! ### CES with `0 < γ < 1` -/ + +theorem continuousOn_crraUtility_Ici {γ : ℝ} (hγ1 : γ < 1) : + ContinuousOn (crraUtility γ) (Ici 0) := by + have he : crraUtility γ = fun c : ℝ => c ^ (1 - γ) / (1 - γ) := + funext (crraUtility_of_ne (by linarith)) + rw [he] + refine ContinuousOn.div_const (fun x _ => ?_) _ + exact (Real.continuousAt_rpow_const x (1 - γ) (Or.inr (by linarith))).continuousWithinAt + +theorem monotoneOn_crraUtility_Ici {γ : ℝ} (hγ1 : γ < 1) : + MonotoneOn (crraUtility γ) (Ici 0) := by + intro x hx y hy hxy + rw [crraUtility_of_ne (by linarith), crraUtility_of_ne (by linarith)] + exact div_le_div_of_nonneg_right (Real.rpow_le_rpow hx hxy (by linarith)) (by linarith) + +theorem strictConcaveOn_crraUtility_Ici {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) : + StrictConcaveOn ℝ (Ici 0) (crraUtility γ) := by + have hpos : (0 : ℝ) < 1 - γ := by linarith + refine ⟨convex_Ici 0, fun x hx y hy hxy a b ha hb hab => ?_⟩ + have key := (Real.strictConcaveOn_rpow hpos (by linarith)).2 hx hy hxy ha hb hab + simp only [smul_eq_mul] at key ⊢ + simp only [crraUtility_of_ne (show γ ≠ 1 by linarith)] + rw [show a * (x ^ (1 - γ) / (1 - γ)) + b * (y ^ (1 - γ) / (1 - γ)) + = (a * x ^ (1 - γ) + b * y ^ (1 - γ)) / (1 - γ) from by ring] + gcongr + +theorem crraUtility_zero {γ : ℝ} (hγ1 : γ < 1) : crraUtility γ 0 = 0 := by + rw [crraUtility_of_ne (by linarith), Real.zero_rpow (by linarith)] + simp + +/-- **CES with `γ < 1` has unbounded marginal value AT zero, not merely near it.** The level +condition `tendsto_atBot_u` fails here, and this is what replaces it: the household still refuses +to consume nothing, because the first unit of consumption is worth more than any bounded gain from +the saving it displaces. -/ +theorem marginalInadaOn_Ici_crraUtility {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) : + MarginalInadaOn (Ici 0) (crraUtility γ) := by + intro M + obtain ⟨δ₁, hδ₁, hspec⟩ := marginalInada_crraUtility hγ0 M + obtain ⟨δ₂, hδ₂M, hδ₂0⟩ := + (((tendsto_rpow_neg_atTop hγ0).eventually_ge_atTop M).and self_mem_nhdsWithin).exists + refine ⟨min δ₁ δ₂, lt_min hδ₁ hδ₂0, fun c c' hc hcc' hc'δ => ?_⟩ + rcases (mem_Ici.mp hc).lt_or_eq with hcpos | hc0 + · exact hspec c c' hcpos hcc' (le_trans hc'δ (min_le_left _ _)) + -- the new case: `c = 0`, where the classical condition has nothing to say + have hc' : 0 < c' := hc0 ▸ hcc' + have hM : M ≤ c' ^ (-γ) := + hδ₂M.trans (rpow_neg_antitone hγ0 hc' (le_trans hc'δ (min_le_right _ _))) + have hmul : c' ^ (-γ) * c' = c' ^ (1 - γ) := by + have hadd := Real.rpow_add hc' (-γ) 1 + rw [Real.rpow_one] at hadd + rw [← hadd, show -γ + 1 = 1 - γ from by ring] + have h1 : M * c' ≤ c' ^ (1 - γ) := by + rw [← hmul]; exact mul_le_mul_of_nonneg_right hM hc'.le + have h2 : (0 : ℝ) ≤ c' ^ (1 - γ) := (Real.rpow_pos_of_pos hc' _).le + rw [← hc0, crraUtility_zero hγ1, crraUtility_of_ne (show γ ≠ 1 by linarith)] + rw [sub_zero, sub_zero] + rw [le_div_iff₀ (show (0 : ℝ) < 1 - γ by linarith)] + nlinarith + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- Utility is bounded below: the domain is the closed half-line. -/ +def Bounded : Prop := P.dom = Ici 0 + +/-- **Positive consumption in the bounded case.** The `⊥` at zero consumption is gone, so the +argument that used to be free is replaced by a marginal Inada condition. -/ +theorem positiveConsumption_of_bounded_crra {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) + (hb : P.Bounded) (hu : P.u = crraUtility γ) : P.PositiveConsumption := by + refine P.positiveConsumption_of_marginalInada ?_ + rw [hb, hu] + exact marginalInadaOn_Ici_crraUtility hγ0 hγ1 + +end IncomeFluctuation + +/-! ### A CES economy with `γ = 1/2` + +`crraUtility (1/2) c = 2 √c`, which is finite at `c = 0` and therefore inadmissible under the old +structure. The parameters are those of `dispersed`, so the two economies differ only in the +curvature of `u`. -/ + +/-- A household with CES utility at `γ = 1/2`: square-root utility, bounded below. -/ +noncomputable def sqrtCES : IncomeFluctuation (Fin 2) 0 1 where + income z := if z = 0 then 1 / 100 else 1 + transitionMatrix _ _ := 1 / 2 + interest := 0 + discount := 1 / 8 + u := crraUtility (1 / 2) + minIncome := 1 / 100 + maxIncome := 1 + minIncome_pos := by norm_num + minIncome_le z := by fin_cases z <;> norm_num + le_maxIncome z := by fin_cases z <;> norm_num + transitionMatrix_nonneg _ _ := by norm_num + transitionMatrix_sum _ := by simp + interest_gt_neg_one := by norm_num + assetFloor_le_assetCap := by norm_num + assetCap_nonneg := by norm_num + minConsumption_pos := by norm_num + minConsumption_le_floor := le_rfl + discount_lt_one := by norm_num + dom := Ici 0 + Ioi_subset_dom := Ioi_subset_Ici_self + dom_subset_Ici := subset_rfl + continuousOn_u_dom := continuousOn_crraUtility_Ici (by norm_num) + monotoneOn_u_dom := monotoneOn_crraUtility_Ici (by norm_num) + strictConcaveOn_u_dom := strictConcaveOn_crraUtility_Ici (by norm_num) (by norm_num) + continuousOn_extendDom := continuousOn_extendDom_Ici (continuousOn_crraUtility_Ici (by norm_num)) + +@[simp] theorem sqrtCES_u : sqrtCES.u = crraUtility (1 / 2) := rfl + +theorem sqrtCES_bounded : sqrtCES.Bounded := rfl + +/-- Utility at zero consumption is finite. This is the fact the old structure forbade. -/ +theorem sqrtCES_u_zero : sqrtCES.u 0 = 0 := crraUtility_zero (by norm_num) + +/-- And it is not admissible under the level condition: `crra_pinch` rejected exactly this. -/ +theorem sqrtCES_not_tendsto_atBot : + ¬ Tendsto (crraUtility (1 / 2)) (𝓝[>] (0 : ℝ)) atBot := + not_tendsto_atBot_crraUtility (by norm_num) + +/-- The reward is nonetheless real at zero consumption, so the household could in principle +choose it -- and `sqrtCES_positiveConsumption` is the proof that it does not. -/ +theorem sqrtCES_not_unbounded : ¬ sqrtCES.Unbounded := by + intro h + have : (0 : ℝ) ∈ sqrtCES.dom := mem_Ici.mpr le_rfl + rw [h] at this + exact absurd this (by simp) + +/-- **Positive consumption, earned from the margin.** -/ +theorem sqrtCES_positiveConsumption : sqrtCES.PositiveConsumption := + sqrtCES.positiveConsumption_of_bounded_crra (by norm_num) (by norm_num) rfl rfl + +/-! ### The whole development applies, unchanged -/ + +/-- The Bellman equation, with honest utility. -/ +theorem sqrtCES_bellman {s : ℝ × Fin 2} (hs : s.1 ∈ Icc (0 : ℝ) 1) : + ∃ a' ∈ Icc 0 (sqrtCES.maxSaving s), sqrtCES.consumption s a' ∈ sqrtCES.dom ∧ + sqrtCES.toExtended.valueFunction s + = sqrtCES.u (sqrtCES.consumption s a') + + sqrtCES.discount * ∑ z', sqrtCES.transitionMatrix s.2 z' + * sqrtCES.toExtended.valueFunction (a', z') := + sqrtCES.exists_optimal_saving hs + +/-- The optimal action is unique, from strict concavity on `Ici 0`. -/ +theorem sqrtCES_optimal_action_unique {s : ℝ × Fin 2} (hs : s.1 ∈ Icc (0 : ℝ) 1) {a₀ a₁ : ℝ} + (h₀ : a₀ ∈ sqrtCES.toExtended.feasible s) (h₁ : a₁ ∈ sqrtCES.toExtended.feasible s) + (hm₀ : sqrtCES.toExtended.objectiveE sqrtCES.toExtended.valueFunction s a₀ + = ((sqrtCES.toExtended.bellmanFn sqrtCES.toExtended.valueFunction s : ℝ) : EReal)) + (hm₁ : sqrtCES.toExtended.objectiveE sqrtCES.toExtended.valueFunction s a₁ + = ((sqrtCES.toExtended.bellmanFn sqrtCES.toExtended.valueFunction s : ℝ) : EReal)) : + a₀ = a₁ := + sqrtCES.optimal_action_unique hs h₀ h₁ hm₀ hm₁ + +/-- **The policy is increasing in assets at a fixed interest rate.** This specializes the +asset-monotonicity theorem for `IncomeFluctuation`. -/ +theorem sqrtCES_policy_mono {a a' : ℝ} {z : Fin 2} (ha : a ∈ Icc (0 : ℝ) 1) + (ha' : a' ∈ Icc (0 : ℝ) 1) (hle : a ≤ a') : + sqrtCES.policy (a, z) ≤ sqrtCES.policy (a', z) := + sqrtCES.policy_mono ha ha' hle + +/-- Consumption is positive, and increasing in assets. -/ +theorem sqrtCES_consumptionFn_pos {a : ℝ} (ha : a ∈ Icc (0 : ℝ) 1) (z : Fin 2) : + 0 < sqrtCES.consumptionFn z a := + sqrtCES.consumptionFn_pos sqrtCES_positiveConsumption ha z + +theorem sqrtCES_monotoneOn_consumptionFn (z : Fin 2) : + MonotoneOn (sqrtCES.consumptionFn z) (Icc 0 1) := + sqrtCES.monotoneOn_consumptionFn z + +/-- **A stationary agent distribution exists**, by Krylov-Bogolyubov, with aggregate capital +equal to mean saving. The distribution half of the development needs no porting either. -/ +theorem sqrtCES_exists_isStationary (μ₀ : ProbabilityMeasure sqrtCES.State) : + ∃ μ : ProbabilityMeasure sqrtCES.State, sqrtCES.IsStationary μ := + sqrtCES.exists_isStationary μ₀ + +theorem sqrtCES_aggregateCapital_eq_integral_policy {μ : ProbabilityMeasure sqrtCES.State} + (hμ : sqrtCES.IsStationary μ) : + sqrtCES.aggregateCapital μ + = ∫ s, sqrtCES.policy (sqrtCES.incl s) ∂(μ : Measure sqrtCES.State) := + sqrtCES.aggregateCapital_eq_integral_policy hμ + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/CRRA.lean b/LeanPool/AiyagariEquilibrium/Models/CRRA.lean new file mode 100644 index 000000000..9d8c5c09d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/CRRA.lean @@ -0,0 +1,226 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Analysis.RelativeRiskAversion + +/-! +# CRRA utility, and where it fits + +This file compares two conditions on the CRRA utility family: + +* `Tendsto u (𝓝[>] 0) atBot`, which gives unbounded-below utility at zero consumption; +* Light's relative-risk-aversion bound `RelativeRiskAversionLeOne`. + +For CRRA the first holds exactly when `γ ≥ 1` and the second exactly when `γ ≤ 1`. +`crra_pinch` records that both hold only for log utility, `γ = 1`. For `γ < 1`, +`not_tendsto_atBot_crraUtility` proves utility is bounded below; for `γ > 1`, +`strictConcaveOn_crra_comp_exp` rules out the relative-risk-aversion bound. + +The household structure itself supports both cases. Its utility domain can be `Ioi 0` or +`Ici 0`, with continuity of the extended reward in place of an unconditional divergence +assumption. `IncomeFluctuation.Unbounded` recovers divergence in the open-domain case. +`BoundedIncomeFluctuation` supplies a bounded-utility CES model, and the later iteration +machinery obtains its positive consumption from a marginal Inada condition. Thus the pinch +restricts the unbounded-below CRRA subclass, rather than every household model in the library. + +## The single derivative formula + +`hasDerivAt_crraUtility` gives `u' c = c ^ (-γ)` for every `γ`, log included, since `log' c = c⁻¹` +is the `γ = 1` case. Monotonicity, strict concavity and the Inada condition are all read off it, +so the two branches of the definition are only ever separated once. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +/-- **CRRA period utility.** Power utility for `γ ≠ 1`, with the usual logarithmic convention +at `γ = 1`. For positive `c`, subtracting `1 / (1 - γ)` from the power branch gives the +normalization whose limit as `γ → 1` is `log c`; the raw branch does not have a finite limit. -/ +noncomputable def crraUtility (γ c : ℝ) : ℝ := + if γ = 1 then Real.log c else c ^ (1 - γ) / (1 - γ) + +theorem crraUtility_of_ne {γ : ℝ} (h : γ ≠ 1) (c : ℝ) : + crraUtility γ c = c ^ (1 - γ) / (1 - γ) := by simp [crraUtility, h] + +@[simp] theorem crraUtility_one : crraUtility 1 = Real.log := by + funext c; simp [crraUtility] + +/-- The existing non-vacuity witness `u c = -c⁻¹` is CRRA with `γ = 2`. -/ +theorem crraUtility_two : crraUtility 2 = fun c : ℝ => -c⁻¹ := by + funext c + rw [crraUtility_of_ne (by norm_num), show (1 : ℝ) - 2 = -1 by norm_num, Real.rpow_neg_one] + ring + +/-! ### The derivative, and everything read off it -/ + +/-- **Marginal utility is `c ^ (-γ)`**, log included. -/ +theorem hasDerivAt_crraUtility (γ : ℝ) {c : ℝ} (hc : 0 < c) : + HasDerivAt (crraUtility γ) (c ^ (-γ)) c := by + rcases eq_or_ne γ 1 with rfl | hγ + · rw [crraUtility_one, show -(1 : ℝ) = -1 from rfl, Real.rpow_neg_one] + exact Real.hasDerivAt_log hc.ne' + · have h : HasDerivAt (fun x : ℝ => x ^ (1 - γ)) ((1 - γ) * c ^ (1 - γ - 1)) c := + Real.hasDerivAt_rpow_const (Or.inl hc.ne') + have h2 := h.div_const (1 - γ) + have hne : (1 : ℝ) - γ ≠ 0 := sub_ne_zero.mpr (Ne.symm hγ) + rw [show (1 - γ) * c ^ (1 - γ - 1) / (1 - γ) = c ^ (-γ) by + rw [show (1 : ℝ) - γ - 1 = -γ by ring]; field_simp] at h2 + exact h2.congr_of_eventuallyEq (Filter.Eventually.of_forall fun x => crraUtility_of_ne hγ x) + +theorem deriv_crraUtility (γ : ℝ) {c : ℝ} (hc : 0 < c) : + deriv (crraUtility γ) c = c ^ (-γ) := (hasDerivAt_crraUtility γ hc).deriv + +theorem continuousOn_crraUtility (γ : ℝ) : ContinuousOn (crraUtility γ) (Ioi 0) := + fun _ hc => ((hasDerivAt_crraUtility γ hc).continuousAt).continuousWithinAt + +theorem strictMonoOn_crraUtility (γ : ℝ) : StrictMonoOn (crraUtility γ) (Ioi 0) := by + refine strictMonoOn_of_deriv_pos (convex_Ioi 0) (continuousOn_crraUtility γ) fun c hc => ?_ + rw [interior_Ioi] at hc + rw [deriv_crraUtility γ hc] + exact Real.rpow_pos_of_pos hc _ + +theorem monotoneOn_crraUtility (γ : ℝ) : MonotoneOn (crraUtility γ) (Ioi 0) := + (strictMonoOn_crraUtility γ).monotoneOn + +theorem strictConcaveOn_crraUtility {γ : ℝ} (hγ : 0 < γ) : + StrictConcaveOn ℝ (Ioi 0) (crraUtility γ) := by + refine StrictAntiOn.strictConcaveOn_of_deriv (convex_Ioi 0) (continuousOn_crraUtility γ) + fun x hx y hy hxy => ?_ + rw [interior_Ioi] at hx hy + rw [deriv_crraUtility γ hx, deriv_crraUtility γ hy, Real.rpow_neg hx.le, Real.rpow_neg hy.le] + rw [inv_lt_inv₀ (Real.rpow_pos_of_pos hy _) (Real.rpow_pos_of_pos hx _)] + exact Real.rpow_lt_rpow hx.le hxy hγ + +/-! ### The Inada condition holds exactly for `γ ≥ 1` -/ + +theorem tendsto_atBot_crraUtility {γ : ℝ} (hγ : 1 ≤ γ) : + Tendsto (crraUtility γ) (𝓝[>] 0) atBot := by + rcases eq_or_lt_of_le hγ with rfl | hγ1 + · rw [crraUtility_one]; exact Real.tendsto_log_nhdsGT_zero + · have hpos : (0 : ℝ) < γ - 1 := by linarith + have hcont : Tendsto (fun c : ℝ => c ^ (γ - 1)) (𝓝[>] 0) (𝓝 0) := by + have h : Tendsto (fun x : ℝ => x ^ (γ - 1)) (𝓝[>] 0) (𝓝 ((0 : ℝ) ^ (γ - 1))) := + (Real.continuousAt_rpow_const 0 (γ - 1) (Or.inr hpos.le)).continuousWithinAt + rwa [Real.zero_rpow hpos.ne'] at h + have hwithin : Tendsto (fun c : ℝ => c ^ (γ - 1)) (𝓝[>] 0) (𝓝[>] 0) := + tendsto_nhdsWithin_of_tendsto_nhds_of_eventually_within _ hcont + (eventually_nhdsWithin_of_forall fun c hc => Real.rpow_pos_of_pos hc _) + have hinv : Tendsto (fun c : ℝ => (c ^ (γ - 1))⁻¹) (𝓝[>] 0) atTop := + tendsto_inv_nhdsGT_zero.comp hwithin + have hrw : Tendsto (fun c : ℝ => c ^ (1 - γ)) (𝓝[>] 0) atTop := by + refine hinv.congr' (eventually_nhdsWithin_of_forall fun c hc => ?_) + have hcc : c ^ (1 - γ) = (c ^ (γ - 1))⁻¹ := by + rw [show (1 : ℝ) - γ = -(γ - 1) by ring, Real.rpow_neg (le_of_lt hc)] + exact hcc.symm + have hneg : (1 : ℝ) / (1 - γ) < 0 := by + apply div_neg_of_pos_of_neg one_pos; linarith + refine (hrw.const_mul_atTop_of_neg hneg).congr fun c => ?_ + rw [crraUtility_of_ne (by linarith : γ ≠ 1)] + ring + +/-- **For `γ < 1` the Inada condition fails**, and not for want of an argument: period utility is +then bounded below by zero, so it cannot tend to `-∞` along any filter that is not trivial. -/ +theorem not_tendsto_atBot_crraUtility {γ : ℝ} (hγ : γ < 1) : + ¬ Tendsto (crraUtility γ) (𝓝[>] 0) atBot := by + intro h + have hnn : ∀ᶠ c : ℝ in 𝓝[>] 0, (0 : ℝ) ≤ crraUtility γ c := + eventually_nhdsWithin_of_forall fun c hc => by + rw [crraUtility_of_ne (by linarith : γ ≠ 1)] + exact div_nonneg (Real.rpow_nonneg (le_of_lt hc) _) (by linarith) + have hlt : ∀ᶠ c : ℝ in 𝓝[>] 0, crraUtility γ c < 0 := h.eventually (eventually_lt_atBot 0) + obtain ⟨c, hc1, hc2⟩ := (hnn.and hlt).exists + linarith + +/-! ### The pinch -/ + +/-- **Relative risk aversion at most one holds for CRRA exactly when `γ ≤ 1`.** -/ +theorem relativeRiskAversionLeOne_crraUtility_iff {γ : ℝ} : + RelativeRiskAversionLeOne (crraUtility γ) ↔ γ ≤ 1 := by + rcases lt_trichotomy γ 1 with h | rfl | h + · refine ⟨fun _ => h.le, fun _ => ?_⟩ + have he : crraUtility γ = fun c => c ^ (1 - γ) / (1 - γ) := + funext (crraUtility_of_ne (by linarith)) + rw [he] + exact relativeRiskAversionLeOne_crra h + · simpa using relativeRiskAversionLeOne_log + · refine ⟨fun hcon => absurd hcon ?_, fun hle => absurd h (by linarith)⟩ + have he : crraUtility γ = fun c => c ^ (1 - γ) / (1 - γ) := + funext (crraUtility_of_ne (by linarith)) + rw [he] + exact not_relativeRiskAversionLeOne_crra h + +/-- **The unbounded-domain requirements pinch CRRA to log utility.** Divergence to `-∞` at zero +forces `γ ≥ 1`, while relative risk aversion at most one forces `γ ≤ 1`. This conjunction +does not restrict the separate bounded-domain treatment of `0 < γ < 1`. -/ +theorem crra_pinch {γ : ℝ} : + (Tendsto (crraUtility γ) (𝓝[>] 0) atBot ∧ RelativeRiskAversionLeOne (crraUtility γ)) + ↔ γ = 1 := by + constructor + · rintro ⟨hinada, hrra⟩ + have hge : 1 ≤ γ := by + by_contra hlt + exact not_tendsto_atBot_crraUtility (by linarith) hinada + have hle : γ ≤ 1 := relativeRiskAversionLeOne_crraUtility_iff.mp hrra + linarith + · rintro rfl + exact ⟨tendsto_atBot_crraUtility le_rfl, relativeRiskAversionLeOne_crraUtility_iff.mpr le_rfl⟩ + +/-- The existing witness fails Light's condition, as `γ = 2 > 1` requires. -/ +theorem not_relativeRiskAversionLeOne_neg_inv : + ¬ RelativeRiskAversionLeOne fun c : ℝ => -c⁻¹ := by + rw [← crraUtility_two] + exact fun h => absurd (relativeRiskAversionLeOne_crraUtility_iff.mp h) (by norm_num) + +/-! ### A log-utility economy + +The pinch is not vacuous: `γ = 1` really is an income fluctuation problem. This is +`NonVacuity.impatient` with its `-c⁻¹` — which is `γ = 2` by `crraUtility_two` — replaced by log, +so the two witnesses sit on either side of Light's condition, and every utility axiom is now +discharged by a single lemma about the CRRA family. -/ + +/-- An impatient household with log utility: `β = 1/100`, `r = 0`, income in `{1, 2}`, cap `1`. -/ +noncomputable def logImpatient : IncomeFluctuation (Fin 2) 0 1 where + income z := if z = 0 then 1 else 2 + transitionMatrix _ _ := 1 / 2 + interest := 0 + discount := 1 / 100 + u := crraUtility 1 + minIncome := 1 + maxIncome := 2 + minIncome_pos := by norm_num + minIncome_le z := by fin_cases z <;> norm_num + le_maxIncome z := by fin_cases z <;> norm_num + transitionMatrix_nonneg _ _ := by norm_num + transitionMatrix_sum _ := by simp + interest_gt_neg_one := by norm_num + assetFloor_le_assetCap := by norm_num + assetCap_nonneg := by norm_num + minConsumption_pos := by norm_num + minConsumption_le_floor := le_rfl + discount_lt_one := by norm_num + dom := Ioi 0 + Ioi_subset_dom := subset_rfl + dom_subset_Ici := Ioi_subset_Ici_self + continuousOn_u_dom := continuousOn_crraUtility 1 + monotoneOn_u_dom := monotoneOn_crraUtility 1 + strictConcaveOn_u_dom := strictConcaveOn_crraUtility one_pos + continuousOn_extendDom := + continuousOn_extendDom_Ioi (continuousOn_crraUtility 1) (tendsto_atBot_crraUtility le_rfl) + +@[simp] theorem logImpatient_u : logImpatient.u = Real.log := crraUtility_one + +/-- **The log economy satisfies Light's condition**, and by `crra_pinch` it is the only CRRA +economy that can. -/ +theorem logImpatient_relativeRiskAversionLeOne : + RelativeRiskAversionLeOne logImpatient.u := + relativeRiskAversionLeOne_crraUtility_iff.mpr le_rfl + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/CRRAConstants.lean b/LeanPool/AiyagariEquilibrium/Models/CRRAConstants.lean new file mode 100644 index 000000000..26164b670 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/CRRAConstants.lean @@ -0,0 +1,529 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.BoundedIncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Models.NaturalAssetBound + +/-! +# Quantitative constants for CRRA households + +The equilibrium programme runs on four explicit constants: a lower bound on the secant slope of +`u` (which drives the corner condition and the precautionary gain), an upper bound on it (which +gives the value function its Lipschitz constant), a bound on saving (which gives the decline +condition), and the calibration arithmetic that checks they fit together. All four were computed +for `Real.log`, and `hu : P.u = Real.log` propagated through the whole chain. + +The common derivative formula `hasDerivAt_crraUtility` covers every `γ > 0`, including log, +so the marginal bounds also cover that case. The closed form `crraSlopeBound` is used only +when `γ ≠ 1`; the logarithmic slope bound is evaluated separately as `2 log 2 / m`. + +## The one place the shape changes + +For log, marginal utility is `1/c`, so the slope bound available at resources `R` is `1/R` — it +DEGRADES as resources grow, and the multiplicative bound `c ≥ R / (1 + 2G)` is the right output. +For CES with `γ < 1` marginal utility is `c^{-γ}`, and on a capped problem `c ≤ maxConsumption`, +so there is a single slope `maxConsumption ^ (-γ)` that works everywhere. That turns the same +deviation argument into a UNIFORM bound on saving — `policy s ≤ K` for one constant `K` — which +is a cleaner route to the decline condition than the multiplicative bound, and the natural asset +bound falls straight out of it. + +`policy_le_of_marginal_bound` is stated for any `u` with a uniform slope bound, so log gets the +uniform version too, with `m = 1 / maxConsumption`. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology MeasureTheory + +namespace LeanEconomics + +/-! ### Rational exponents reduce to integer powers + +At `γ = 1 - 1/n` every exponent the CES constants produce is `m/n` for integers `m, n`, and a +comparison of `x ^ (m/n)` against a rational is equivalent to one of `x ^ m` against `c ^ n`. +These two lemmas are the whole bridge, and they are what keeps `γ` near 1 arithmetically +tractable: `norm_num` settles the integer form. -/ + +theorem rpow_le_of_pow_le {x c p : ℝ} {m n : ℕ} (hn : n ≠ 0) (hx : 0 ≤ x) (hc : 0 ≤ c) + (hp : p = (m : ℝ) / n) (h : x ^ m ≤ c ^ n) : x ^ p ≤ c := by + have hn' : (0 : ℝ) < n := Nat.cast_pos.mpr (Nat.pos_of_ne_zero hn) + have h1 : x ^ p = (x ^ m) ^ ((n : ℝ)⁻¹) := by + rw [hp, div_eq_mul_inv, Real.rpow_mul hx, Real.rpow_natCast] + have h2 : c = (c ^ n) ^ ((n : ℝ)⁻¹) := by + rw [← Real.rpow_natCast c n, ← Real.rpow_mul hc, mul_inv_cancel₀ hn'.ne', Real.rpow_one] + calc x ^ p = (x ^ m) ^ ((n : ℝ)⁻¹) := h1 + _ ≤ (c ^ n) ^ ((n : ℝ)⁻¹) := Real.rpow_le_rpow (by positivity) h (by positivity) + _ = c := h2.symm + +/-! ### Negative exponents without a root + +The gain and cost sides of the supply floor are both `x ^ (-γ)`. When the base sits on the right +side of one these need no root at all: a base above one makes the power at most one, and a base +below one makes it at most the reciprocal (for `γ ≤ 1`). That covers the COST side of every +calibration, where the base is `η + income - t` with `t` small. The GAIN side, where the base is +small and the bound has to be large, is where a root is genuinely needed — +`le_rpow_neg_of_rpow_le` hands it back to `rpow_le_of_pow_le`. -/ + +/-- A base of at least one makes the negative power at most one. -/ +theorem rpow_neg_le_one_of_one_le {x γ : ℝ} (hx : 1 ≤ x) (hγ : 0 ≤ γ) : x ^ (-γ) ≤ 1 := + Real.rpow_le_one_of_one_le_of_nonpos hx (by linarith) + +/-- A base of at most one makes the negative power at most the reciprocal, when `γ ≤ 1`. -/ +theorem rpow_neg_le_inv_of_le_one {x γ : ℝ} (hx : 0 < x) (hx1 : x ≤ 1) (hγ : γ ≤ 1) : + x ^ (-γ) ≤ x⁻¹ := by + have h := Real.rpow_le_rpow_of_exponent_ge hx hx1 (show (-1 : ℝ) ≤ -γ by linarith) + rwa [Real.rpow_neg_one] at h + +/-- The gain side: a bound on `x ^ γ` from above is a bound on `x ^ (-γ)` from below. -/ +theorem le_rpow_neg_of_rpow_le {x q γ : ℝ} (hx : 0 < x) (hq : 0 < q) (h : x ^ γ ≤ q⁻¹) : + q ≤ x ^ (-γ) := by + have hpos : (0 : ℝ) < x ^ γ := Real.rpow_pos_of_pos hx _ + rw [Real.rpow_neg hx.le, le_inv_comm₀ hq hpos] + exact h + +theorem le_rpow_of_pow_le {x c p : ℝ} {m n : ℕ} (hn : n ≠ 0) (hx : 0 ≤ x) (hc : 0 ≤ c) + (hp : p = (m : ℝ) / n) (h : c ^ n ≤ x ^ m) : c ≤ x ^ p := by + have hn' : (0 : ℝ) < n := Nat.cast_pos.mpr (Nat.pos_of_ne_zero hn) + have h1 : x ^ p = (x ^ m) ^ ((n : ℝ)⁻¹) := by + rw [hp, div_eq_mul_inv, Real.rpow_mul hx, Real.rpow_natCast] + have h2 : c = (c ^ n) ^ ((n : ℝ)⁻¹) := by + rw [← Real.rpow_natCast c n, ← Real.rpow_mul hc, mul_inv_cancel₀ hn'.ne', Real.rpow_one] + calc c = (c ^ n) ^ ((n : ℝ)⁻¹) := h2 + _ ≤ (x ^ m) ^ ((n : ℝ)⁻¹) := Real.rpow_le_rpow (by positivity) h (by positivity) + _ = x ^ p := h1.symm + +/-! ### Secant slopes of CRRA, bounded below -/ + +/-- **The secant slope of CRRA on `(0, R]` is at least `R ^ (-γ)`.** This is +`log_marginal_bound` for every `γ > 0` at once: at `γ = 1` the bound `R ^ (-1)` is `1 / R`. -/ +theorem crra_marginal_bound {γ R : ℝ} (hγ : 0 < γ) {c d : ℝ} (hd : 0 < d) (hdc : d ≤ c) + (hcR : c ≤ R) : R ^ (-γ) * (c - d) ≤ crraUtility γ c - crraUtility γ d := by + rcases eq_or_lt_of_le hdc with rfl | hlt + · simp + obtain ⟨ξ, hξ, hslope⟩ := exists_hasDerivAt_eq_slope (crraUtility γ) + (fun x => x ^ (-γ)) hlt + ((continuousOn_crraUtility γ).mono fun x hx => lt_of_lt_of_le hd hx.1) + (fun x hx => hasDerivAt_crraUtility γ (hd.trans hx.1)) + have hmono : R ^ (-γ) ≤ ξ ^ (-γ) := + rpow_neg_antitone hγ (hd.trans hξ.1) (hξ.2.le.trans hcR) + rw [hslope, le_div_iff₀ (by linarith)] at hmono + linarith + +/-- **The bound reaches `d = 0` when `γ < 1`.** Utility is finite there, so the statement makes +sense, and the first unit of consumption is worth at least as much as any later one. This is the +case log cannot have. -/ +theorem crra_marginal_bound_zero {γ R : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) {c : ℝ} + (hc : 0 < c) (hcR : c ≤ R) : + R ^ (-γ) * (c - 0) ≤ crraUtility γ c - crraUtility γ 0 := by + have hM : R ^ (-γ) ≤ c ^ (-γ) := rpow_neg_antitone hγ0 hc hcR + have hmul : c ^ (-γ) * c = c ^ (1 - γ) := by + have hadd := Real.rpow_add hc (-γ) 1 + rw [Real.rpow_one] at hadd + rw [← hadd, show -γ + 1 = 1 - γ from by ring] + have h1 : R ^ (-γ) * c ≤ c ^ (1 - γ) := by + rw [← hmul]; exact mul_le_mul_of_nonneg_right hM hc.le + have h2 : (0 : ℝ) ≤ c ^ (1 - γ) := (Real.rpow_pos_of_pos hc _).le + rw [crraUtility_zero hγ1, crraUtility_of_ne (show γ ≠ 1 by linarith), sub_zero, sub_zero, + le_div_iff₀ (show (0 : ℝ) < 1 - γ by linarith)] + nlinarith + +/-- The slope bound on all of `Ici 0`, which is the domain CES with `γ < 1` uses. -/ +theorem crra_marginal_bound_Ici {γ R : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) {c d : ℝ} + (hd : d ∈ Ici (0 : ℝ)) (hdc : d ≤ c) (hcR : c ≤ R) : + R ^ (-γ) * (c - d) ≤ crraUtility γ c - crraUtility γ d := by + rcases (mem_Ici.mp hd).lt_or_eq with hdpos | hd0 + · exact crra_marginal_bound hγ0 hdpos hdc hcR + rcases eq_or_lt_of_le (hd0 ▸ hdc : (0 : ℝ) ≤ c) with hc0 | hcpos + · rw [← hd0, ← hc0]; simp + · rw [← hd0]; exact crra_marginal_bound_zero hγ0 hγ1 hcpos hcR + +/-- **The gain from `d` extra consumption, bounded below.** Taking `R = c + d` in +`crra_marginal_bound` gives the CES form of `log_sub_log_ge`. -/ +theorem crra_sub_ge {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) {c d : ℝ} (hc : 0 ≤ c) (hd : 0 ≤ d) : + (c + d) ^ (-γ) * d ≤ crraUtility γ (c + d) - crraUtility γ c := by + have := crra_marginal_bound_Ici hγ0 hγ1 (mem_Ici.mpr hc) (le_add_of_nonneg_right hd) + (le_refl (c + d)) + simpa using this + +/-- **The secant slope of CRRA on `[d, c]` is at most `d ^ (-γ)`.** The mirror image of +`crra_marginal_bound`, and what caps the COST of saving. -/ +theorem crra_marginal_bound_above {γ : ℝ} (hγ : 0 < γ) {c d : ℝ} (hd : 0 < d) (hdc : d ≤ c) : + crraUtility γ c - crraUtility γ d ≤ d ^ (-γ) * (c - d) := by + rcases eq_or_lt_of_le hdc with rfl | hlt + · simp + obtain ⟨ξ, hξ, hslope⟩ := exists_hasDerivAt_eq_slope (crraUtility γ) + (fun x => x ^ (-γ)) hlt + ((continuousOn_crraUtility γ).mono fun x hx => lt_of_lt_of_le hd hx.1) + (fun x hx => hasDerivAt_crraUtility γ (hd.trans hx.1)) + have hmono : ξ ^ (-γ) ≤ d ^ (-γ) := rpow_neg_antitone hγ hd hξ.1.le + rw [hslope, div_le_iff₀ (by linarith)] at hmono + linarith + +/-- **The cost of saving `h`, capped uniformly in resources.** Resources are at least `m`, so what +is left after saving is at least `m - h`, and marginal utility there is the largest that can +apply. The log form of the same bound is `log (m / (m - h))`. -/ +theorem crra_cost_of_saving {γ : ℝ} (hγ : 0 < γ) {m h R b : ℝ} (hh0 : 0 < h) (hhm : h < m) + (hmR : m ≤ R) (hb0 : 0 ≤ b) (hbh : b ≤ h) : + crraUtility γ (R - b) - crraUtility γ (R - h) ≤ (m - h) ^ (-γ) * h := by + have hRh : 0 < R - h := by linarith + have hstep := crra_marginal_bound_above hγ hRh (show R - h ≤ R - b by linarith) + have hanti : (R - h) ^ (-γ) ≤ (m - h) ^ (-γ) := + rpow_neg_antitone hγ (by linarith) (by linarith) + have hpos : (0 : ℝ) < (R - h) ^ (-γ) := Real.rpow_pos_of_pos hRh _ + nlinarith [hstep, hanti, hb0] + +/-! ### Secant slopes of CRRA, bounded above -/ + +/-- The closed form of `slopeBound (crraUtility γ)` at the income floor for `γ ≠ 1`. +The literal expression below evaluates to zero at `γ = 1` under total real division, so +the logarithmic case uses `log_slopeBoundU` separately. -/ +noncomputable def crraSlopeBound (γ m : ℝ) : ℝ := + 2 * m ^ (-γ) * (1 - ((2 : ℝ) ^ (1 - γ))⁻¹) / (1 - γ) + +theorem slopeBound_crraUtility {γ m : ℝ} (hγ : γ ≠ 1) (hm : 0 < m) : + slopeBound (crraUtility γ) m = crraSlopeBound γ m := by + have h2 : (0 : ℝ) < (2 : ℝ) ^ (1 - γ) := Real.rpow_pos_of_pos (by norm_num) _ + have hhalf : (m / 2) ^ (1 - γ) = m ^ (1 - γ) / (2 : ℝ) ^ (1 - γ) := + Real.div_rpow hm.le (by norm_num : (0 : ℝ) ≤ 2) (1 - γ) + have hexp : m ^ (1 - γ) = m * m ^ (-γ) := by + have h := Real.rpow_add hm 1 (-γ) + rw [Real.rpow_one, show (1 : ℝ) + -γ = 1 - γ from by ring] at h + exact h + have hγ' : (1 : ℝ) - γ ≠ 0 := sub_ne_zero.mpr (Ne.symm hγ) + simp only [slopeBound, crraSlopeBound, crraUtility_of_ne hγ, hhalf, hexp] + field_simp + +theorem crraSlopeBound_nonneg {γ m : ℝ} (hm : 0 ≤ m) : + 0 ≤ crraSlopeBound γ m := by + unfold crraSlopeBound + have hmp : 0 ≤ 2 * m ^ (-γ) := mul_nonneg (by norm_num) (Real.rpow_nonneg hm _) + have hpositive : 0 < (2 : ℝ) ^ (1 - γ) := Real.rpow_pos_of_pos (by norm_num) _ + by_cases hγ : γ ≤ 1 + · have hpower : 1 ≤ (2 : ℝ) ^ (1 - γ) := + Real.one_le_rpow (by norm_num) (sub_nonneg.mpr hγ) + exact div_nonneg + (mul_nonneg hmp (sub_nonneg.mpr ((inv_le_one₀ hpositive).mpr hpower))) + (sub_nonneg.mpr hγ) + · have hγ' : 1 ≤ γ := (lt_of_not_ge hγ).le + have hpower : (2 : ℝ) ^ (1 - γ) ≤ 1 := + Real.rpow_le_one_of_one_le_of_nonpos (by norm_num) (sub_nonpos.mpr hγ') + exact div_nonneg_of_nonpos + (mul_nonpos_of_nonneg_of_nonpos hmp (sub_nonpos.mpr ((one_le_inv₀ hpositive).mpr hpower))) + (sub_nonpos.mpr hγ') + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +-- This layer is the CALIBRATED one: every constant is computed for a household whose borrowing +-- limit is at zero. The general-floor machinery lives upstream; nothing here needs it, and +-- pinning the floor keeps the arithmetic in the primitives. +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-- **A uniform bound on saving from a uniform marginal bound.** Deviate to half the optimal +saving: the extra consumption is worth at least `m` per unit, and concavity of the continuation +caps what the forgone saving could have repaid at `deviationGap`, whatever the state. + +The log development could not use this, because for log the available `m` at resources `R` is +`1 / R`, which is not uniform. It is stated generically anyway — log gets the version with +`m = 1 / maxConsumption`, which is uniform after all, just weaker than what the multiplicative +argument extracts. For CES with `γ < 1` the uniform `m = maxConsumption ^ (-γ)` is all there is, +and it is enough. -/ +theorem policy_le_of_marginal_bound {m : ℝ} (hm : 0 < m) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.maxConsumption → m * (c - d) ≤ P.u c - P.u d) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + P.policy (a, z) ≤ 2 * P.deviationGap / m := by + have h := P.policyOf_le_of_marginal_bound P.concaveOn_valueFunction hm hmarg ha z + rw [P.policyOf_valueFunction, zero_add] at h + convert h using 1 + dsimp [deviationGap] + ring + +/-! ### The oscillation gap + +`deviationGap = 2 β ‖V‖` is not translation-invariant. For CES, subtracting +`1 / (1-γ)` from `c ^ (1-γ) / (1-γ)` gives the normalization that tends to `log c` +as `γ → 1` for positive `c`; it is not an equality with `log c` at a fixed `γ ≠ 1`. +A value-norm bound is sensitive to that additive utility constant. The comparisons here use +DIFFERENCES of continuation values, so an oscillation bound avoids this normalization dependence: +`(u maxConsumption - u minConsumption) / (1 - β)`. -/ + +/-- The spread of the value function, from the two constant plans. -/ +noncomputable def oscGap : ℝ := P.oscSpread + +theorem oscGap_nonneg : 0 ≤ P.oscGap := P.oscSpread_nonneg + +/-- **The continuation value moves by at most the oscillation gap**, whatever the two saving +levels. This is what replaces `2 ‖V‖` in every comparison. -/ +theorem cont_sub_le_oscGap (z : Z) (x y : ℝ) : P.cont z x - P.cont z y ≤ P.oscGap := by + have hsub : P.cont z x - P.cont z y + = ∑ z' : Z, P.transitionMatrix z z' * + (P.toExtended.valueFunction (x, z') - P.toExtended.valueFunction (y, z')) := by + simp only [cont, ← Finset.sum_sub_distrib, ← mul_sub] + rw [hsub] + calc ∑ z' : Z, P.transitionMatrix z z' * + (P.toExtended.valueFunction (x, z') - P.toExtended.valueFunction (y, z')) + ≤ ∑ z' : Z, P.transitionMatrix z z' * P.oscGap := by + refine Finset.sum_le_sum fun z' _ => ?_ + refine mul_le_mul_of_nonneg_left ?_ (P.transitionMatrix_nonneg _ _) + simp only [oscGap, oscSpread, sub_div] + linarith [P.valueFunction_le_oscBound (x, z'), P.oscBound_le_valueFunction (y, z')] + _ = P.oscGap := by rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + +/-- The nonlogarithmic CRRA Lipschitz constant, mirroring `logLipschitz`. -/ +noncomputable def crraLipschitz (γ : ℝ) : ℝ := + crraSlopeBound γ P.minConsumption * (1 + P.interest) / (1 - P.discount * (1 + P.interest)) + +theorem crraLipschitz_nonneg {γ : ℝ} + (hβR : P.discount * (1 + P.interest) < 1) : 0 ≤ P.crraLipschitz γ := by + have hs := crraSlopeBound_nonneg (γ := γ) P.minConsumption_pos.le + have hr := P.interest_gt_neg_one + exact div_nonneg (by positivity) (by linarith) + +/-- **The value function is Lipschitz with the explicit CRRA constant for `γ ≠ 1`.** -/ +theorem crra_valueFunction_lipschitz {γ : ℝ} (hγ : γ ≠ 1) + (hu : P.u = crraUtility γ) (hβR : P.discount * (1 + P.interest) < 1) : + ∀ z : Z, ∀ x ∈ Icc (0 : ℝ) assetCap, ∀ y ∈ Icc (0 : ℝ) assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| + ≤ P.crraLipschitz γ * |x - y| := by + refine P.valueFunction_lipschitz (P.crraLipschitz_nonneg (γ := γ) hβR) (le_of_eq ?_) + have hne : (1 : ℝ) - P.discount * (1 + P.interest) ≠ 0 := by linarith + simp only [crraLipschitz, slopeBoundU, hu, + slopeBound_crraUtility hγ P.minConsumption_pos] + field_simp + ring + +/-- **The borrowing constraint binds where resources are small**, CES version. The threshold is +`resources ^ (-γ)` where the log version had `1 / resources`. -/ +theorem crra_policy_eq_zero_of_resources {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) + (hb : P.Bounded) (hu : P.u = crraUtility γ) + (hβR : P.discount * (1 + P.interest) < 1) {s : ℝ × Z} (hs : s.1 ∈ Icc (0 : ℝ) assetCap) + (hlt : P.discount * P.crraLipschitz γ < P.resources s ^ (-γ)) : + P.policy s = 0 := + P.policy_eq_zero_of_corner_at (P.crra_valueFunction_lipschitz (ne_of_lt hγ1) hu hβR) hs + (fun c d hd hdc hc => by + rw [hu] + rw [sub_zero] at hc + exact crra_marginal_bound_Ici hγ0 hγ1 (by rw [hb] at hd; exact hd) hdc hc) + hlt + +/-- **The CES saving bound in closed form.** Saving never exceeds `2 · deviationGap · +maxConsumption ^ γ`, whatever the state. -/ +theorem crra_policy_le {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) (hb : P.Bounded) + (hu : P.u = crraUtility γ) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) + (z : Z) : P.policy (a, z) ≤ 2 * P.deviationGap * P.maxConsumption ^ γ := by + have hmc := P.maxConsumption_pos + have hm : (0 : ℝ) < P.maxConsumption ^ (-γ) := Real.rpow_pos_of_pos hmc _ + have hkey := P.policy_le_of_marginal_bound hm + (fun c d hd hdc hc => by + rw [hu] + exact crra_marginal_bound_Ici hγ0 hγ1 (by rw [hb] at hd; exact hd) hdc hc) + ha z + have hinv : P.maxConsumption ^ (-γ) = (P.maxConsumption ^ γ)⁻¹ := Real.rpow_neg hmc.le γ + rw [hinv, div_eq_mul_inv, inv_inv] at hkey + exact hkey + +/-- **The natural asset bound for CES.** A uniform cap on saving is a decline condition outright: +above it, assets fall. No linear consumption bound and no `exp` in sight. -/ +theorem crra_exists_decline {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) (hb : P.Bounded) + (hu : P.u = crraUtility γ) (z : Z) : + ∃ ā : ℝ, ∀ a ∈ Icc (0 : ℝ) assetCap, ā < a → P.policy (a, z) < a := + ⟨2 * P.deviationGap * P.maxConsumption ^ γ, fun _ ha hlt => + lt_of_le_of_lt (P.crra_policy_le hγ0 hγ1 hb hu ha _) hlt⟩ + +/-- **The continuation's gain between two saving levels, CES version.** Where the log form carries +`log (1 + R·(y-x) / (income + R·x))`, this carries `(income + R·y) ^ (-γ) · R·(y-x)`: the extra +resources, valued at the marginal utility of the largest consumption they could buy. -/ +theorem crra_cont_sub_ge_gen {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumption) (z z₀ : Z) {x y : ℝ} + (hx : x ∈ Icc (0 : ℝ) assetCap) (hy : y ∈ Icc (0 : ℝ) assetCap) (hxy : x ≤ y) : + P.transitionMatrix z z₀ * ((P.income z₀ + (1 + P.interest) * y) ^ (-γ) + * ((1 + P.interest) * (y - x))) + ≤ P.cont z y - P.cont z x := by + have hRh : 0 ≤ (1 + P.interest) * (y - x) := + mul_nonneg P.interest_gt_neg_one.le (sub_nonneg.mpr hxy) + refine P.cont_sub_ge_of_utility_bound hpc z z₀ hx hy hxy ?_ + intro c hc hcle + simp only [sub_zero] at hcle + rw [hu] + have hR : c + (1 + P.interest) * (y - x) + ≤ P.income z₀ + (1 + P.interest) * y := by nlinarith only [hcle] + simpa using crra_marginal_bound hγ0 hc + (le_add_of_nonneg_right hRh) hR + +/-- **The continuation's gain from zero, CES version.** -/ +theorem crra_cont_sub_ge {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumption) (z z₀ : Z) {h : ℝ} + (hh : h ∈ Icc (0 : ℝ) assetCap) : + P.transitionMatrix z z₀ * ((P.income z₀ + (1 + P.interest) * h) ^ (-γ) + * ((1 + P.interest) * h)) + ≤ P.cont z h - P.cont z 0 := by + have h0 : (0 : ℝ) ∈ Icc (0 : ℝ) assetCap := ⟨le_rfl, P.assetCap_nonneg⟩ + have := P.crra_cont_sub_ge_gen hγ0 hu hpc z z₀ h0 hh hh.1 + simpa using this + +/-! ### The sharp decline constant + +`crra_policy_le` bounds saving by a constant, which is enough for a natural asset bound but far +too weak for the corner condition to meet it: the corner needs the threshold to be comparable to +the LOW income, and an absolute bound does not shrink with it. The log development gets a +PROPORTIONAL bound instead, `c ≥ R / (1 + 2G)`, whose decline threshold is proportional to income. + +The CES analogue deviates to `θ` times the optimal saving rather than to half of it, and prices +the extra consumption at the margin where it lands: `b ≤ (β · oscGap / θ) · (c + (1-θ) b) ^ γ`. +Taking `θ` near 1 makes the right-hand side nearly `(β · oscGap) c ^ γ`, proportional to +consumption, which is the shape the decline condition needs. -/ + +/-- **The proportional CES bound on saving.** -/ +theorem crra_policy_le_mul {γ θ : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) (hθ1 : θ < 1) + (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumption) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + P.policy (a, z) + ≤ (P.discount * P.oscGap / θ) + * (P.consumption (a, z) (P.policy (a, z)) + (1 - θ) * P.policy (a, z)) ^ γ := by + have h := P.policyOf_le_mul_of_power_marginal (γ := γ) (η := 0) hθ0 hθ1 + P.concaveOn_valueFunction P.oscGap_nonneg + P.oscOn_valueFunction + (fun c d _ hcd hpositive => by + simpa only [zero_add, hu] using + crra_marginal_bound hγ0 (by simpa only [zero_add] using hpositive) hcd (le_refl d)) ha z + (by simpa only [zero_add, P.consumptionFnOf_valueFunction] using + P.consumptionFn_pos hpc ha z) + simpa only [P.policyOf_valueFunction, consumptionFnOf, zero_add, sub_zero] using h + +/-- **Assets fall above an explicit threshold**, in the income state that matters. The threshold +is `(β · oscGap / θ) · (income z + (1 + r - θ) · assetCap) ^ γ`. It depends on the +income of the selected state and the residual gross return `1 + r - θ`; its strict comparison +with current assets is an explicit hypothesis. -/ +theorem crra_policy_lt_self {γ θ : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) (hθ1 : θ < 1) + (hθr : θ ≤ 1 + P.interest) (hu : P.u = crraUtility γ) + (hpc : P.PositiveConsumption) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) + (hlt : (P.discount * P.oscGap / θ) + * (P.income z + (1 + P.interest - θ) * assetCap) ^ γ < a) : + P.policy (a, z) < a := by + by_contra hcon + rw [not_lt] at hcon + have hosc := P.oscGap_nonneg + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + set b : ℝ := P.policy (a, z) with hbdef + set c : ℝ := P.consumption (a, z) b with hcdef + have hkey := P.crra_policy_le_mul hγ0 hθ0 hθ1 hu hpc ha z + rw [← hbdef, ← hcdef] at hkey + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + have hsum : c + (1 - θ) * b = P.income z + (1 + P.interest) * a - θ * b := by + simp only [hcdef, consumption, hres]; ring + have hle : c + (1 - θ) * b ≤ P.income z + (1 + P.interest - θ) * assetCap := by + rw [hsum] + have h1 : θ * a ≤ θ * b := mul_le_mul_of_nonneg_left hcon hθ0.le + have h2 : (1 + P.interest - θ) * a ≤ (1 + P.interest - θ) * assetCap := + mul_le_mul_of_nonneg_left ha.2 (by linarith) + nlinarith [h1, h2] + have hnn : (0 : ℝ) ≤ c + (1 - θ) * b := by + have hc0 : 0 < c := by rw [hcdef, hbdef]; exact P.consumption_policy_pos hpc ha + have : 0 ≤ (1 - θ) * b := mul_nonneg (by linarith) (P.policy_mem_region (a, z)).1 + linarith + have hmono : (c + (1 - θ) * b) ^ γ + ≤ (P.income z + (1 + P.interest - θ) * assetCap) ^ γ := + Real.rpow_le_rpow hnn hle hγ0.le + have hscale : (P.discount * P.oscGap / θ) * (c + (1 - θ) * b) ^ γ + ≤ (P.discount * P.oscGap / θ) + * (P.income z + (1 + P.interest - θ) * assetCap) ^ γ := + mul_le_mul_of_nonneg_left hmono (by positivity) + linarith + +/-! ### The asset cap never binds + +`not_concaveOn_consumptionFn_of_cap_binds` makes Carroll and Kimball's conclusion FALSE wherever +saving reaches the cap, so the cap is the obstruction to Light's uniqueness argument, not the +utility class. It can be removed by calibration rather than by weakening the theory: run +`crra_policy_le_mul` by contradiction. If saving reached the cap then consumption would be at +most `maxIncome + (1 + r - θ) · assetCap`. The displayed inequality makes the saving upper +bound at that quantity strictly smaller than the cap. + +The criterion evaluates the proportional bound at the consumption quantity implied by cap +binding. The theorem retains the displayed numerical inequality as an explicit calibration +hypothesis. + +Related literature: Marcet, Obiols-Homs and Weil (2007), *Incomplete Markets, Labor Supply +and Capital Accumulation*. The sufficient criterion below is specific to this capped household +model: it uses the displayed quantitative inequality and positive optimal +consumption. Its fixed-point and iterative versions share the same comparison argument. -/ + +/-- **Saving never reaches the asset cap.** -/ +theorem crra_policy_lt_assetCap {γ θ : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) + (hθ1 : θ < 1) (hu : P.u = crraUtility γ) + (hpc : P.PositiveConsumption) + (hlt : (P.discount * P.oscGap / θ) + * (P.maxIncome + (1 + P.interest - θ) * assetCap) ^ γ < assetCap) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + P.policy (a, z) < assetCap := by + have h := P.policyOf_lt_cap_of_power_bound (η := 0) hγ0.le hθ0.le hθ1.le + (show 0 ≤ (P.discount : ℝ) * P.oscGap / θ by + exact div_nonneg (mul_nonneg P.discount.coe_nonneg P.oscGap_nonneg) hθ0.le) + (v := P.toExtended.valueFunction) ha z + (by simpa only [zero_add, P.consumptionFnOf_valueFunction] using + (P.consumptionFn_pos hpc ha z).le) + (by simpa only [P.policyOf_valueFunction, consumptionFnOf, zero_add, sub_zero] using + P.crra_policy_le_mul hγ0 hθ0 hθ1 hu hpc ha z) + (by simpa only [zero_add, mul_zero, sub_zero] using hlt) + simpa only [P.policyOf_valueFunction] using h + +/-- **The disproof of Carroll and Kimball is disarmed.** Where saving never reaches the cap, the +hypothesis of `not_concaveOn_consumptionFn_of_cap_binds` -- a flat stretch of the policy at the +top of the asset region -- cannot occur, so nothing in this development contradicts concavity of +the consumption function. That is not a proof of concavity; it is the removal of the obstruction +that made one impossible. -/ +theorem no_flat_at_cap {γ θ : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) (hθ1 : θ < 1) + (hu : P.u = crraUtility γ) + (hpc : P.PositiveConsumption) + (hlt : (P.discount * P.oscGap / θ) + * (P.maxIncome + (1 + P.interest - θ) * assetCap) ^ γ < assetCap) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + P.policy (a, z) ≠ assetCap := + ne_of_lt (P.crra_policy_lt_assetCap hγ0 hθ0 hθ1 hu hpc hlt ha z) + +/-- **A quantitative CES saving floor**, the form the sign-change argument consumes. -/ +theorem crra_policy_ge_of_gain {γ : ℝ} (hγ : 0 < γ) (hu : P.u = crraUtility γ) + (hpc : P.PositiveConsumption) (z₁ : Z) {h : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) + (hhinc : h < P.income z₁) + (hgain : (P.income z₁ - h) ^ (-γ) * h < P.discount * (P.cont z₁ h - P.cont z₁ (h / 2))) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + h / 2 ≤ P.policy (a, z₁) := by + refine P.policy_ge_of_cost rfl hpc z₁ hh0 hhcap hhinc (fun R b hm hb0 hbh => ?_) hgain ha + rw [hu] + exact crra_cost_of_saving hγ hh0 hhinc hm hb0 hbh + +section Measure + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- **Positive aggregate capital for CES, from primitives alone.** The log condition +`log (m / (m - h)) < β · P z₁ z₀ · log (1 + R h / income z₀)` becomes +`(m - h) ^ (-γ) · h < β · P z₁ z₀ · (income z₀ + R h) ^ (-γ) · R h`: the saved unit priced at +the margin where consumption is lowest, against its return in the bad state. -/ +theorem crra_aggregateCapital_pos_of_primitives {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumption) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) {z₁ z₀ : Z} {p₀ : ℝ} (hp0 : 0 < p₀) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + {h : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hgain : (P.income z₁ - h) ^ (-γ) * h + < P.discount * (P.transitionMatrix z₁ z₀ + * ((P.income z₀ + (1 + P.interest) * h) ^ (-γ) * ((1 + P.interest) * h)))) : + 0 < P.aggregateCapital μ := by + refine P.aggregateCapital_pos_of_cost rfl hμ hp0 hp hh0 hhcap hhinc (fun R hm => ?_) + (lt_of_lt_of_le hgain ?_) + · rw [hu] + simpa using crra_cost_of_saving hγ0 hh0 hhinc hm (le_refl (0 : ℝ)) hh0.le + · exact mul_le_mul_of_nonneg_left (P.crra_cont_sub_ge hγ0 hu hpc z₁ z₀ ⟨hh0.le, hhcap⟩) + P.discount.coe_nonneg + +end Measure + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/ConsumptionFloor.lean b/LeanPool/AiyagariEquilibrium/Models/ConsumptionFloor.lean new file mode 100644 index 000000000..68f376c37 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/ConsumptionFloor.lean @@ -0,0 +1,436 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationConsumption +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationBounds +public import LeanPool.AiyagariEquilibrium.Models.CRRA + +/-! +# A consumption floor from the margin, not from the level + +`crra_pinch` traced the restriction `γ = 1` to `tendsto_atBot_u`, the requirement that utility +fall to `-∞` at zero consumption. This file replaces the use of that condition in the one place +where it does economic work — the uniform lower bound on consumption at the optimum — by a +condition on the MARGIN, which CRRA satisfies for every `γ > 0`, `γ < 1` included. + +## Minimum income is necessary and not sufficient + +A positive lower bound on income does not by itself keep consumption away from zero. It bounds +RESOURCES below, not consumption: `maxSaving = min assetCap resources`, so whenever the cap is +slack the household may save everything and consume nothing. Nothing about the income process +says it will not. + +What minimum income does is make the CONTINUATION Lipschitz where the argument needs it. The +deviation argument compares the optimum with saving `h` less: + + `u (c + h) - u c ≤ β * (cont a - cont (a - h))`, + +and the right-hand side has to be bounded by a multiple of `h`. The continuation is concave and +bounded, so its slope from `assetFloor` to `x` is at most `2‖V‖ / (x - assetFloor)`. The +positive sustainability bound `minConsumption` keeps the deviation away from that endpoint: +small consumption forces saving at least `assetFloor + 3 * minConsumption / 4`. The resulting +state-independent slope constant is `4‖V‖ / minConsumption`, as used by `cont_sub_le`. + +With a state-independent constant `L`, any consumption level whose marginal value beats `β L` +cannot be optimal, and `MarginalInada` supplies one. The floor is explicit: +`min δ (minConsumption / 4)`. + +## How the current bounded-domain model uses this result + +Divergence at zero has two distinct roles in the unbounded-domain model: it makes +`extendBot u` continuous into `EReal` and excludes zero consumption at an optimum. The marginal +argument here replaces the latter role. + +The current `IncomeFluctuation` structure has a utility-domain field. +`BoundedIncomeFluctuation` takes `dom = Ici 0`, where `extendDom` uses the finite value `u 0` +rather than `⊥`, and proves the needed reward continuity. Its marginal Inada argument then +establishes positive consumption separately. The floor results here fit that shared structure; +they do not require a new household type or a divergence assumption. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +/-- **Marginal Inada.** Consumption near zero has unboundedly large marginal value, measured by +secant slopes so that no derivative is needed. CRRA satisfies this for every `γ > 0`, including +the `γ < 1` range where utility itself stays bounded. -/ +def MarginalInadaOn (D : Set ℝ) (u : ℝ → ℝ) : Prop := + ∀ M : ℝ, ∃ δ > 0, ∀ c c' : ℝ, c ∈ D → c < c' → c' ≤ δ → M * (c' - c) ≤ u c' - u c + +/-- The classical form, on the positives. -/ +abbrev MarginalInada (u : ℝ → ℝ) : Prop := MarginalInadaOn (Ioi 0) u + +/-- **A marginal lower bound near zero.** `MarginalInadaOn` is this for every `M`; a utility +with finite marginal value at zero -- CARA, or a shifted CRRA -- satisfies it for the `M` its +slope near zero allows, and the consumption floor then has to be earned by an inequality. -/ +def MarginalBoundOn (D : Set ℝ) (u : ℝ → ℝ) (M : ℝ) : Prop := + ∃ δ > 0, ∀ c c' : ℝ, c ∈ D → c < c' → c' ≤ δ → M * (c' - c) ≤ u c' - u c + +theorem marginalBoundOn_of_inada {D : Set ℝ} {u : ℝ → ℝ} (h : MarginalInadaOn D u) (M : ℝ) : + MarginalBoundOn D u M := h M + +/-- Marginal utility explodes at zero. -/ +theorem tendsto_rpow_neg_atTop {γ : ℝ} (hγ : 0 < γ) : + Tendsto (fun c : ℝ => c ^ (-γ)) (𝓝[>] 0) atTop := by + have hcont : Tendsto (fun c : ℝ => c ^ γ) (𝓝[>] 0) (𝓝 0) := by + have h : Tendsto (fun x : ℝ => x ^ γ) (𝓝[>] 0) (𝓝 ((0 : ℝ) ^ γ)) := + (Real.continuousAt_rpow_const 0 γ (Or.inr hγ.le)).continuousWithinAt + rwa [Real.zero_rpow hγ.ne'] at h + have hwithin : Tendsto (fun c : ℝ => c ^ γ) (𝓝[>] 0) (𝓝[>] 0) := + tendsto_nhdsWithin_of_tendsto_nhds_of_eventually_within _ hcont + (eventually_nhdsWithin_of_forall fun c hc => Real.rpow_pos_of_pos hc _) + refine (tendsto_inv_nhdsGT_zero.comp hwithin).congr' + (eventually_nhdsWithin_of_forall fun c hc => ?_) + exact (Real.rpow_neg (le_of_lt hc) γ).symm + +theorem rpow_neg_antitone {γ : ℝ} (hγ : 0 < γ) {x y : ℝ} (hx : 0 < x) (hxy : x ≤ y) : + y ^ (-γ) ≤ x ^ (-γ) := by + rw [Real.rpow_neg hx.le, Real.rpow_neg (hx.trans_le hxy).le] + rw [inv_le_inv₀ (Real.rpow_pos_of_pos (hx.trans_le hxy) _) (Real.rpow_pos_of_pos hx _)] + exact Real.rpow_le_rpow hx.le hxy hγ.le + +/-- **CRRA has unbounded marginal value at zero for every `γ > 0`** — the `γ < 1` case included, +where the LEVEL condition `tendsto_atBot_u` fails. This is the whole point. -/ +theorem marginalInada_crraUtility {γ : ℝ} (hγ : 0 < γ) : MarginalInada (crraUtility γ) := by + intro M + obtain ⟨δ, hδM, hδ0⟩ := + (((tendsto_rpow_neg_atTop hγ).eventually_ge_atTop M).and self_mem_nhdsWithin).exists + refine ⟨δ, hδ0, fun c c' hc hcc' hc'δ => ?_⟩ + have hc' : 0 < c' := hc.trans hcc' + obtain ⟨ξ, hξ, hslope⟩ := exists_hasDerivAt_eq_slope (crraUtility γ) + (fun x => x ^ (-γ)) hcc' + ((continuousOn_crraUtility γ).mono fun x hx => lt_of_lt_of_le hc hx.1) + (fun x hx => hasDerivAt_crraUtility γ (hc.trans hx.1)) + have hmono : M ≤ ξ ^ (-γ) := + hδM.trans (rpow_neg_antitone hγ (hc.trans hξ.1) (le_trans hξ.2.le hc'δ)) + rw [hslope, le_div_iff₀ (by linarith)] at hmono + linarith + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The slope constant an oscillation bound `G` gives. -/ +noncomputable def oscSlopeConst (G : ℝ) : ℝ := 2 * G / P.minConsumption + +theorem oscSlopeConst_nonneg {G : ℝ} (hG : 0 ≤ G) : 0 ≤ P.oscSlopeConst G := + div_nonneg (by linarith) P.minConsumption_pos.le + +/-- **The continuation's slope, priced by its oscillation**, above +`assetFloor + minConsumption / 2`. -/ +theorem contOf_sub_le_of_osc {v : (ℝ × Z) →ᵇ ℝ} {G : ℝ} (hG : 0 ≤ G) + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) + (hosc : P.OscOn v G) (z : Z) {x y : ℝ} + (hx : assetFloor + P.minConsumption / 2 ≤ x) (hxy : x < y) + (hy : y ∈ Icc assetFloor assetCap) : + P.contOf v z y - P.contOf v z x ≤ P.oscSlopeConst G * (y - x) := by + have hmin := P.minConsumption_pos + have hx0 : assetFloor < x := by linarith + have hxmem : x ∈ Icc assetFloor assetCap := ⟨hx0.le, by linarith [hy.2]⟩ + have hslope := (P.concaveOn_contOf hv z).slope_anti_adjacent + (mem_Icc.mpr ⟨le_rfl, P.assetFloor_le_assetCap⟩) hy hx0 hxy + have hgap := P.contOf_sub_le_osc hosc hxmem + (show assetFloor ∈ Icc assetFloor assetCap from ⟨le_rfl, P.assetFloor_le_assetCap⟩) z z + have hhalf : P.oscSlopeConst G * (P.minConsumption / 2) = G := by + rw [oscSlopeConst]; field_simp + have hbig : P.contOf v z x - P.contOf v z assetFloor + ≤ P.oscSlopeConst G * (x - assetFloor) := by + have hstep : P.oscSlopeConst G * (P.minConsumption / 2) + ≤ P.oscSlopeConst G * (x - assetFloor) := + mul_le_mul_of_nonneg_left (by linarith) (P.oscSlopeConst_nonneg hG) + rw [hhalf] at hstep + linarith + rw [div_le_div_iff₀ (by linarith) (by linarith)] at hslope + have hprod : (P.contOf v z y - P.contOf v z x) * (x - assetFloor) + ≤ (P.oscSlopeConst G * (x - assetFloor)) * (y - x) := by + nlinarith [hslope, hbig, (show (0 : ℝ) < y - x by linarith)] + nlinarith [hprod, hx0] + +/-- **The consumption floor, from a finite marginal bound.** -/ +theorem exists_consumptionFnOf_floor_of_marginal {M G : ℝ} + (hM : MarginalBoundOn P.dom P.u M) (hG : 0 ≤ G) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) + (hosc : P.OscOn v G) + (hlt : (P.discount : ℝ) * P.oscSlopeConst G < M) : + ∃ δ > 0, ∀ (z : Z), ∀ a ∈ Icc assetFloor assetCap, δ ≤ P.consumptionFnOf v z a := by + have hmin := P.minConsumption_pos + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + obtain ⟨δ₀, hδ₀, hδ⟩ := hM + refine ⟨min δ₀ (P.minConsumption / 4), lt_min hδ₀ (by linarith), fun z a ha => ?_⟩ + by_contra hlt' + rw [not_le] at hlt' + set d : ℝ := min δ₀ (P.minConsumption / 4) with hd + set b : ℝ := P.policyOf v (a, z) with hbdef + set c : ℝ := P.consumption (a, z) b with hcdef + have hltc : c < d := hlt' + have hdδ : d ≤ δ₀ := min_le_left _ _ + have hd4 : d ≤ P.minConsumption / 4 := min_le_right _ _ + have hcmem : c ∈ P.dom := P.consumption_policyOf_mem_dom v ha + have hc0 : 0 ≤ c := P.nonneg_of_mem_dom hcmem + have hres : P.minConsumption ≤ P.resources (a, z) - assetFloor := + P.minConsumption_le_consumption_floor (a, z) + have hcb : c = P.resources (a, z) - b := rfl + have hbig : assetFloor + 3 * P.minConsumption / 4 ≤ b := by rw [hcb] at hltc; linarith + set hgap : ℝ := d - c with hhdef + have hh0 : 0 < hgap := by rw [hhdef]; linarith + have hhle : hgap ≤ P.minConsumption / 4 := by rw [hhdef]; linarith + have hlow : assetFloor + P.minConsumption / 2 ≤ b - hgap := by linarith + have hbmem : b ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem v (a, z)) + have hfeas : b - hgap ∈ P.toExtended.feasible (a, z) := by + rw [P.feasible_eq] + exact ⟨by linarith [hbmem.1], by linarith [(P.policyOf_mem v (a, z)).2]⟩ + have hcd : P.consumption (a, z) (b - hgap) = d := by + simp only [consumption] at hcb ⊢; linarith + have hdpos : 0 < P.consumption (a, z) (b - hgap) := by rw [hcd]; linarith + have hopt := P.objROf_le_of_mem v ha hfeas (P.mem_dom_of_pos hdpos) + simp only [objROf, hcd] at hopt + have hslope := P.contOf_sub_le_of_osc hG hv hosc z hlow (by linarith) hbmem + rw [show b - (b - hgap) = hgap from by ring] at hslope + have hscaled := mul_le_mul_of_nonneg_left hslope hβ + have hutil : P.u d - P.u c ≤ ((P.discount : ℝ) * P.oscSlopeConst G) * (d - c) := by + have e : (P.discount : ℝ) * (P.oscSlopeConst G * hgap) + = ((P.discount : ℝ) * P.oscSlopeConst G) * (d - c) := by rw [hhdef]; ring + linarith [hopt, hscaled, e.le, e.ge] + have hmarg := hδ c d hcmem (by linarith) hdδ + rw [hhdef] at hh0 + nlinarith only [hmarg, hutil, hh0, hlt] + +/-- The state-independent bound on the continuation's slope, read against `v`. -/ +noncomputable def contSlopeConstOf (v : (ℝ × Z) →ᵇ ℝ) : ℝ := 4 * ‖v‖ / P.minConsumption + +theorem contSlopeConstOf_nonneg (v : (ℝ × Z) →ᵇ ℝ) : 0 ≤ P.contSlopeConstOf v := + div_nonneg (by positivity) P.minConsumption_pos.le + +/-- **The continuation's slope is bounded by a state-independent constant**, above +`assetFloor + minConsumption / 2`, whatever the continuation. -/ +theorem contOf_sub_le {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) (z : Z) + {x y : ℝ} (hx : assetFloor + P.minConsumption / 2 ≤ x) (hxy : x < y) + (hy : y ∈ Icc assetFloor assetCap) : + P.contOf v z y - P.contOf v z x ≤ P.contSlopeConstOf v * (y - x) := by + have h := P.contOf_sub_le_of_osc (by positivity) hv (P.oscOn_two_norm v) z hx hxy hy + convert h using 1 + dsimp [oscSlopeConst, contSlopeConstOf] + ring + +/-- The state-independent bound on the continuation's slope, which is where the positive income +floor does its work. -/ +noncomputable def contSlopeConst : ℝ := 4 * ‖P.toExtended.valueFunction‖ / P.minConsumption + +theorem contSlopeConst_nonneg : 0 ≤ P.contSlopeConst := + div_nonneg (by positivity) P.minConsumption_pos.le + +theorem abs_cont_le (z : Z) (x : ℝ) : |P.cont z x| ≤ ‖P.toExtended.valueFunction‖ := + P.abs_expectation_le x z + +/-- **The continuation's slope is bounded by a state-independent constant**, provided we stay +above `assetFloor + minConsumption / 2` in savings — which is exactly where the deviation +argument lives. -/ +theorem cont_sub_le (z : Z) {x y : ℝ} (hx : assetFloor + P.minConsumption / 2 ≤ x) (hxy : x < y) + (hy : y ∈ Icc assetFloor assetCap) : + P.cont z y - P.cont z x ≤ P.contSlopeConst * (y - x) := by + simpa only [cont, contOf, contSlopeConst, contSlopeConstOf] using + P.contOf_sub_le P.concaveOn_valueFunction z hx hxy hy + +/-- **A consumption floor from the margin, against an arbitrary continuation.** -/ +theorem exists_consumptionFnOf_floor_of_marginalInada (hu : MarginalInadaOn P.dom P.u) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) : + ∃ δ > 0, ∀ (z : Z), ∀ a ∈ Icc assetFloor assetCap, δ ≤ P.consumptionFnOf v z a := by + refine P.exists_consumptionFnOf_floor_of_marginal + (hu ((P.discount : ℝ) * P.oscSlopeConst (2 * ‖v‖) + 1)) + (by positivity) hv (P.oscOn_two_norm v) ?_ + linarith + +/-- **A consumption floor from the margin.** No use is made of `tendsto_atBot_u`: the argument +runs on concavity and boundedness of the continuation, a positive income floor, and unbounded +marginal value at zero consumption. -/ +theorem exists_consumption_floor_of_marginalInada (hu : MarginalInadaOn P.dom P.u) : + ∃ δ > 0, ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → δ ≤ P.consumption s (P.policy s) := by + obtain ⟨δ, hδ, hspec⟩ := + P.exists_consumptionFnOf_floor_of_marginalInada hu P.concaveOn_valueFunction + exact ⟨δ, hδ, fun s hs => hspec s.2 s.1 hs⟩ + +/-- **A uniform bound on saving from a uniform marginal bound**, against any continuation. -/ +theorem policyOf_le_of_marginal_bound {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) {m : ℝ} (hm : 0 < m) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.maxConsumption → m * (c - d) ≤ P.u c - P.u d) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + P.policyOf v (a, z) ≤ assetFloor + 4 * (P.discount : ℝ) * ‖v‖ / m := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hnv : (0 : ℝ) ≤ ‖v‖ := norm_nonneg _ + have hbound : (0 : ℝ) ≤ 4 * (P.discount : ℝ) * ‖v‖ / m := by positivity + set b : ℝ := P.policyOf v (a, z) with hbdef + have hbreg : b ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policyOf_mem v _) + rcases eq_or_lt_of_le hbreg.1 with hzero | hbpos + · rw [← hzero]; linarith + set c : ℝ := P.consumptionFnOf v z a with hcdef + have hcdom : c ∈ P.dom := P.consumption_policyOf_mem_dom v ha + have hcpos : 0 ≤ c := P.nonneg_of_mem_dom hcdom + have hres : P.resources (a, z) = c + b := by + simp only [hcdef, consumptionFnOf, consumption]; ring + set w : ℝ := (b + assetFloor) / 2 with hwdef + have hw0 : assetFloor < w := by rw [hwdef]; linarith + have hwb : w < b := by rw [hwdef]; linarith + have heq : b - w = w - assetFloor := by rw [hwdef]; ring + have hfeas : w ∈ P.toExtended.feasible (a, z) := by + rw [P.feasible_eq] + exact ⟨hw0.le, by linarith [(P.policyOf_mem v (a, z)).2]⟩ + have hcons : P.consumption (a, z) w = c + (b - assetFloor) / 2 := by + simp only [consumption, hres, hwdef]; ring + have hdev : 0 < P.consumption (a, z) w := by rw [hcons]; linarith + have hopt := P.objROf_le_of_mem v ha hfeas (P.mem_dom_of_pos hdev) + simp only [objROf, hcons] at hopt + rw [show P.consumption (a, z) (P.policyOf v (a, z)) = c from rfl, + show P.policyOf v (a, z) = b from rfl] at hopt + -- concavity of the continuation caps what the forgone saving could have repaid + have hfloormem : assetFloor ∈ Icc assetFloor assetCap := ⟨le_rfl, P.assetFloor_le_assetCap⟩ + have hslope := (P.concaveOn_contOf hv z).slope_anti_adjacent hfloormem hbreg hw0 hwb + have hb1 := abs_le.mp (P.abs_contOf_le v z w) + have hb0 := abs_le.mp (P.abs_contOf_le v z assetFloor) + have hloss : P.contOf v z b - P.contOf v z w ≤ 2 * ‖v‖ := by + rw [div_le_div_iff₀ (by linarith) (by linarith), ← heq] at hslope + have := le_of_mul_le_mul_right hslope (by linarith : (0 : ℝ) < b - w) + linarith [hb1.2, hb0.1] + have hcmax : c + (b - assetFloor) / 2 ≤ P.maxConsumption := by + have hcm := P.consumption_le_maxConsumption (s := (a, z)) ha hw0.le + rwa [hcons] at hcm + have hmg := hmarg (c + (b - assetFloor) / 2) c hcdom (by linarith) hcmax + rw [show c + (b - assetFloor) / 2 - c = (b - assetFloor) / 2 from by ring] at hmg + rw [← sub_le_iff_le_add', le_div_iff₀ hm] + nlinarith only [hopt, hmg, mul_le_mul_of_nonneg_left hloss hβ] + +/-- **The second route to positive consumption.** When utility is unbounded below the reward is +`⊥` at zero consumption and positivity is free; when it is bounded — CES with `γ < 1` — it has to +be earned, and this is where. The condition is on MARGINS, which is why it survives the loss of +the level condition. -/ +theorem positiveConsumption_of_marginalInada (hu : MarginalInadaOn P.dom P.u) : + P.PositiveConsumption := by + obtain ⟨δ, hδ, hspec⟩ := P.exists_consumption_floor_of_marginalInada hu + exact fun s hs => lt_of_lt_of_le hδ (hspec s hs) + +/-- A power marginal bound prices the same proportional saving deviation for every utility. -/ +theorem policyOf_le_mul_of_power_marginal {γ η θ G : ℝ} (hθ0 : 0 < θ) (hθ1 : θ < 1) + {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) + (hG : 0 ≤ G) (hosc : P.OscOn v G) + (hmarg : ∀ c d : ℝ, c ∈ P.dom → c ≤ d → 0 < η + c → + (η + d) ^ (-γ) * (d - c) ≤ P.u d - P.u c) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) + (hcpos : 0 < η + P.consumptionFnOf v z a) : + P.policyOf v (a, z) - assetFloor + ≤ ((P.discount : ℝ) * G / θ) + * (η + P.consumptionFnOf v z a + + (1 - θ) * (P.policyOf v (a, z) - assetFloor)) ^ γ := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + set b : ℝ := P.policyOf v (a, z) with hbdef + set c : ℝ := P.consumptionFnOf v z a with hcdef + have hbreg : b ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem v (a, z)) + have hbase : (0 : ℝ) < η + c + (1 - θ) * (b - assetFloor) := by + have := mul_nonneg (sub_pos.mpr hθ1).le (sub_nonneg.mpr hbreg.1) + linarith + have hrpow : (0 : ℝ) < (η + c + (1 - θ) * (b - assetFloor)) ^ γ := + Real.rpow_pos_of_pos hbase _ + rcases eq_or_lt_of_le hbreg.1 with hzero | hbpos + · rw [← hzero, sub_self] + positivity + have hbf : (0 : ℝ) < b - assetFloor := sub_pos.mpr hbpos + set w : ℝ := assetFloor + θ * (b - assetFloor) with hwdef + have hw0 : assetFloor < w := by rw [hwdef]; nlinarith only [hθ0, hbf] + have hwb : w < b := by rw [hwdef]; nlinarith only [hθ1, hbf] + have hwmem : w ∈ Icc assetFloor assetCap := ⟨hw0.le, hwb.le.trans hbreg.2⟩ + have hfeas : w ∈ P.toExtended.feasible (a, z) := by + rw [P.feasible_eq] + exact ⟨hw0.le, hwb.le.trans (P.policyOf_mem v (a, z)).2⟩ + have hcb : c = P.resources (a, z) - b := rfl + have hcons : P.consumption (a, z) w = c + (1 - θ) * (b - assetFloor) := by + simp only [consumption, hwdef]; rw [hcb]; ring + have hdev : 0 < P.consumption (a, z) w := by + rw [hcons] + have hc : 0 ≤ c := P.nonneg_of_mem_dom (P.consumption_policyOf_mem_dom v ha) + nlinarith only [hc, hθ1, hbf] + have hopt := P.objROf_le_of_mem v ha hfeas (P.mem_dom_of_pos hdev) + simp only [objROf, hcons] at hopt + rw [show P.consumption (a, z) (P.policyOf v (a, z)) = c from rfl, + show P.policyOf v (a, z) = b from rfl] at hopt + have hslope := (P.concaveOn_contOf hv z).slope_anti_adjacent + (mem_Icc.mpr ⟨le_rfl, P.assetFloor_le_assetCap⟩) hbreg hw0 hwb + have hgap := P.contOf_sub_le_osc hosc hwmem + (show assetFloor ∈ Icc assetFloor assetCap from ⟨le_rfl, P.assetFloor_le_assetCap⟩) z z + have hbw : b - w = (1 - θ) * (b - assetFloor) := by rw [hwdef]; ring + have hwf : w - assetFloor = θ * (b - assetFloor) := by rw [hwdef]; ring + have hloss : P.contOf v z b - P.contOf v z w ≤ (1 - θ) / θ * G := by + rw [div_le_div_iff₀ (sub_pos.mpr hwb) (sub_pos.mpr hw0), hbw, hwf] at hslope + have h2 := hslope.trans (mul_le_mul_of_nonneg_right hgap + (mul_nonneg (sub_pos.mpr hθ1).le hbf.le)) + rw [div_mul_eq_mul_div, le_div_iff₀ hθ0] + refine le_of_mul_le_mul_right (a := b - assetFloor) ?_ hbf + convert h2 using 1 <;> ring + have hmg : (η + c + (1 - θ) * (b - assetFloor)) ^ (-γ) * ((1 - θ) * (b - assetFloor)) + ≤ P.u (c + (1 - θ) * (b - assetFloor)) - P.u c := by + have := hmarg c (c + (1 - θ) * (b - assetFloor)) + (P.consumption_policyOf_mem_dom v ha) + (le_add_of_nonneg_right (mul_nonneg (sub_pos.mpr hθ1).le hbf.le)) hcpos + simpa only [add_sub_cancel_left, add_assoc] using this + have hmid : P.u (c + (1 - θ) * (b - assetFloor)) - P.u c + ≤ (P.discount : ℝ) * (P.contOf v z b - P.contOf v z w) := by + have hd : (P.discount : ℝ) * (P.contOf v z b - P.contOf v z w) + = (P.discount : ℝ) * P.contOf v z b - (P.discount : ℝ) * P.contOf v z w := by ring + linarith only [hopt, hd] + have hkey := le_trans hmg (le_trans hmid (mul_le_mul_of_nonneg_left hloss hβ)) + rw [Real.rpow_neg hbase.le γ] at hkey + have hexp : ((η + c + (1 - θ) * (b - assetFloor)) ^ γ)⁻¹ * ((1 - θ) * (b - assetFloor)) + = (1 - θ) * ((b - assetFloor) / (η + c + (1 - θ) * (b - assetFloor)) ^ γ) := by + field_simp + rw [hexp, show (P.discount : ℝ) * ((1 - θ) / θ * G) + = (1 - θ) * ((P.discount : ℝ) * G / θ) from by ring] at hkey + have hdiv := le_of_mul_le_mul_left hkey (sub_pos.mpr hθ1) + rw [div_le_iff₀ hrpow] at hdiv + exact hdiv + +/-- A proportional power bound keeps saving below the cap under one primitive inequality. -/ +theorem policyOf_lt_cap_of_power_bound {γ η θ K : ℝ} (hγ : 0 ≤ γ) (hθ0 : 0 ≤ θ) + (hθ1 : θ ≤ 1) (hK : 0 ≤ K) {v : (ℝ × Z) →ᵇ ℝ} + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) + (hc : 0 ≤ η + P.consumptionFnOf v z a) + (hbound : P.policyOf v (a, z) - assetFloor ≤ K * + (η + P.consumptionFnOf v z a + (1 - θ) * (P.policyOf v (a, z) - assetFloor)) ^ γ) + (hcap : K * (η + P.maxIncome + (1 + P.interest - θ) * assetCap + - (1 - θ) * assetFloor) ^ γ < assetCap - assetFloor) : + P.policyOf v (a, z) < assetCap := by + by_contra hcon + rw [not_lt] at hcon + set b := P.policyOf v (a, z) with hbdef + set c := P.consumptionFnOf v z a with hcdef + have hbreg : b ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem v (a, z)) + have hres : P.resources (a, z) - assetFloor ≤ P.maxConsumption := + P.consumption_le_maxConsumption (s := (a, z)) ha le_rfl + have hsum : η + c + (1 - θ) * (b - assetFloor) + = η + (P.resources (a, z) - assetFloor) - θ * (b - assetFloor) := by + simp only [hcdef, consumptionFnOf, consumption, ← hbdef]; ring + have hle : η + c + (1 - θ) * (b - assetFloor) + ≤ η + P.maxIncome + (1 + P.interest - θ) * assetCap - (1 - θ) * assetFloor := by + rw [hsum] + have hθb : θ * (assetCap - assetFloor) ≤ θ * (b - assetFloor) := + mul_le_mul_of_nonneg_left (by linarith only [hcon]) hθ0 + simp only [maxConsumption] at hres + nlinarith only [hres, hθb] + have hnn : 0 ≤ η + c + (1 - θ) * (b - assetFloor) := + add_nonneg hc (mul_nonneg (sub_nonneg.mpr hθ1) (sub_nonneg.mpr hbreg.1)) + have hmono := mul_le_mul_of_nonneg_left (Real.rpow_le_rpow hnn hle hγ) hK + exact (not_lt_of_ge hcon) (by linarith only [hbound, hmono, hcap]) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/ConsumptionSavingsUnbounded.lean b/LeanPool/AiyagariEquilibrium/Models/ConsumptionSavingsUnbounded.lean new file mode 100644 index 000000000..d87ceaa71 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/ConsumptionSavingsUnbounded.lean @@ -0,0 +1,540 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.Extended +public import LeanPool.AiyagariEquilibrium.Topology.IccCorrespondence +public import Mathlib.Analysis.SpecialFunctions.Pow.Continuity +public import Mathlib.Analysis.Convex.SpecificFunctions.Basic + +/-! +# Consumption and savings with period utility unbounded below + +Period utility may fall to `-∞` at zero consumption, which covers `σ ≥ 1` in the CES family +and log utility. Assets are capped. + +## This file used to use a floor + +It replaced `u` by `max floor u` to keep the reward real-valued and bounded, and then had +to prove the floor never binds: bounds on the value function above and below, then +`floor_lt_rewardFn_optimal`, then `cFloor_le_consumption_optimal`. Four structure fields +(`floor`, `cFloor`, `u_cFloor_le_floor`, `floor_lt`) and about a hundred and fifty lines of +proof existed only to establish that a device introduced for technical reasons was +invisible in the answer. + +With `LeanEconomics.DynamicProgramming.Extended` the reward is simply `-∞` there. All of +that machinery is gone. What replaces it is a single hypothesis, `tendsto_atBot_u`, saying +utility falls to `-∞` at zero consumption -- which is not an extra assumption at all, since +it is precisely the situation that forced the floor. + +**Positive consumption at the optimum now comes for free.** It used to be the conclusion of +the floor argument. Now it follows from the value function being real: if the optimal +action left zero consumption the reward would be `-∞`, so the value would be `-∞`, and it +is not. That is `exists_optimal_saving`, which returns `0 < consumption` as part of its +conclusion rather than requiring a separate development. + +## What is unchanged + +Consumption is still clamped *above*, and assets are still capped. Neither has anything to +do with `-∞`; both are the ordinary requirement that the reward be bounded above, which the +weighted theory addresses separately. + +Cash on hand is now computed from `max 0 a` rather than `a`. With a floor, a state with +negative cash on hand received the floor value; without one it would receive `-∞`, which +the framework does not allow. Reading assets as nonnegative keeps the value finite +everywhere, and changes nothing on `[0, assetCap]`, which is where the results are stated. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +/-- `c ↦ -1/c`, that is CES utility at `σ = 2`, is strictly concave on the positives. +Mathlib has no convexity lemma for `x⁻¹`, and its `rpow` convexity results cover only +exponents `≥ 1`, so this is proved by hand: the difference of the two sides is +`a * b * (x - y)² / (x * y * (a * x + b * y))`. -/ +theorem strictConcaveOn_neg_inv : StrictConcaveOn ℝ (Ioi 0) (fun c : ℝ => -c⁻¹) := by + refine ⟨convex_Ioi 0, fun x hx y hy hxy a b ha hb hab => ?_⟩ + have hx' : (0 : ℝ) < x := hx + have hy' : (0 : ℝ) < y := hy + have hs : (0 : ℝ) < a * x + b * y := by positivity + have hne : x - y ≠ 0 := sub_ne_zero.mpr hxy + have hpos : 0 < a * b * (x - y) ^ 2 := by positivity + have key : (a * x + b * y)⁻¹ < a * x⁻¹ + b * y⁻¹ := by + rw [← sub_pos] + have hb' : b = 1 - a := by linarith + subst hb' + have heq : a * x⁻¹ + (1 - a) * y⁻¹ - (a * x + (1 - a) * y)⁻¹ + = (a * (1 - a) * (x - y) ^ 2) / (x * y * (a * x + (1 - a) * y)) := by + field_simp + ring + rw [heq] + positivity + simp only [smul_eq_mul] + linarith + +/-- A consumption-savings problem whose period utility is unbounded below. -/ +structure ConsumptionSavingsUnbounded where + /-- Constant labour income. -/ + income : ℝ + /-- The interest rate on assets. -/ + interest : ℝ + /-- The upper bound imposed on asset holdings. -/ + assetCap : ℝ + /-- The discount factor. -/ + discount : ℝ≥0 + /-- Period utility, required to behave only on positive consumption. -/ + u : ℝ → ℝ + income_pos : 0 < income + interest_gt_neg_one : 0 < 1 + interest + assetCap_nonneg : 0 ≤ assetCap + discount_lt_one : discount < 1 + continuousOn_u : ContinuousOn u (Ioi 0) + monotoneOn_u : MonotoneOn u (Ioi 0) + /-- Utility falls to `-∞` as consumption vanishes. This single hypothesis replaces the + four fields the floored version needed. -/ + tendsto_atBot_u : Tendsto u (𝓝[>] 0) atBot + /-- Diminishing marginal utility. Needed for a *unique* optimal policy, hence for an + agent distribution to be a well-defined object. -/ + strictConcaveOn_u : StrictConcaveOn ℝ (Ioi 0) u + +namespace ConsumptionSavingsUnbounded + +variable (P : ConsumptionSavingsUnbounded) + +/-- Cash on hand, with assets read as nonnegative. -/ +noncomputable def resources (a : ℝ) : ℝ := P.income + (1 + P.interest) * max 0 a + +/-- The largest consumption the capped problem allows. -/ +noncomputable def maxConsumption : ℝ := P.income + (1 + P.interest) * P.assetCap + +/-- The largest asset holding that can be carried forward. -/ +noncomputable def maxSaving (a : ℝ) : ℝ := max 0 (min P.assetCap (P.resources a)) + +/-- Consumption on the budget line. -/ +noncomputable def consumption (a a' : ℝ) : ℝ := P.resources a - a' + +/-- The reward: utility of consumption clamped above, and `-∞` where consumption vanishes. +No floor. -/ +noncomputable def rewardFn (p : ℝ × ℝ) : EReal := + extendBot P.u (min P.maxConsumption (P.consumption p.1 p.2)) + +theorem income_le_resources (a : ℝ) : P.income ≤ P.resources a := by + have : 0 ≤ (1 + P.interest) * max 0 a := + mul_nonneg P.interest_gt_neg_one.le (le_max_left _ _) + simp only [resources]; linarith + +theorem resources_pos (a : ℝ) : 0 < P.resources a := + lt_of_lt_of_le P.income_pos (P.income_le_resources a) + +theorem income_le_maxConsumption : P.income ≤ P.maxConsumption := by + have : 0 ≤ (1 + P.interest) * P.assetCap := + mul_nonneg P.interest_gt_neg_one.le P.assetCap_nonneg + simp only [maxConsumption]; linarith + +theorem maxConsumption_pos : 0 < P.maxConsumption := + lt_of_lt_of_le P.income_pos P.income_le_maxConsumption + +theorem continuous_resources : Continuous P.resources := + continuous_const.add (continuous_const.mul (continuous_const.max continuous_id)) + +theorem continuous_maxSaving : Continuous P.maxSaving := + continuous_const.max (continuous_const.min P.continuous_resources) + +theorem continuous_rewardFn : Continuous P.rewardFn := by + refine (continuous_extendBot P.continuousOn_u P.tendsto_atBot_u).comp + (continuous_const.min ?_) + exact (P.continuous_resources.comp continuous_fst).sub continuous_snd + +/-- The problem as a dynamic program with an extended-real reward. -/ +noncomputable def toExtended : ExtendedProgram ℝ ℝ where + feasible a := Icc 0 (P.maxSaving a) + isCompact_feasible _ := isCompact_Icc + feasible_nonempty _ := nonempty_Icc.mpr (le_max_left _ _) + upperHemicontinuous_feasible := upperHemicontinuous_Icc continuous_const P.continuous_maxSaving + lowerHemicontinuous_feasible := + lowerHemicontinuous_Icc continuous_const P.continuous_maxSaving fun _ => le_max_left _ _ + reward := ⟨P.rewardFn, P.continuous_rewardFn⟩ + rewardMax := P.u P.maxConsumption + reward_le := by + intro s a _ + rcases le_or_gt (min P.maxConsumption (P.consumption s a)) 0 with h | h + · simp only [ContinuousMap.coe_mk, rewardFn] + rw [extendBot_of_nonpos h] + exact bot_le + · simp only [ContinuousMap.coe_mk, rewardFn] + rw [extendBot_of_pos h, EReal.coe_le_coe_iff] + exact P.monotoneOn_u h P.maxConsumption_pos (min_le_left _ _) + select := ⟨fun _ => 0, continuous_const⟩ + select_mem := fun _ => ⟨le_rfl, le_max_left _ _⟩ + rewardMin := P.u P.income + le_reward_select := by + intro s + have h : P.income ≤ min P.maxConsumption (P.consumption s 0) := by + refine le_min P.income_le_maxConsumption ?_ + simp only [consumption, sub_zero] + exact P.income_le_resources s + simp only [ContinuousMap.coe_mk, rewardFn] + rw [extendBot_of_pos (lt_of_lt_of_le P.income_pos h), EReal.coe_le_coe_iff] + exact P.monotoneOn_u P.income_pos (lt_of_lt_of_le P.income_pos h) h + transition := ⟨fun p => p.2, continuous_snd⟩ + discount := P.discount + discount_lt_one := P.discount_lt_one + +@[simp] +theorem feasible_eq (a : ℝ) : P.toExtended.feasible a = Icc 0 (P.maxSaving a) := rfl + +theorem consumption_le_maxConsumption {a a' : ℝ} (ha : a ∈ Icc 0 P.assetCap) (ha' : 0 ≤ a') : + P.consumption a a' ≤ P.maxConsumption := by + have hmax : max 0 a = a := max_eq_right ha.1 + have : (1 + P.interest) * a ≤ (1 + P.interest) * P.assetCap := + mul_le_mul_of_nonneg_left ha.2 P.interest_gt_neg_one.le + simp only [consumption, resources, maxConsumption, hmax] + linarith + +/-- **The Bellman equation, with honest utility and positive consumption.** That +consumption is positive at the optimum is not assumed and not separately proved: a zero +would make the reward `-∞` and hence the value `-∞`, and the value is real. -/ +theorem exists_optimal_saving {a : ℝ} (ha : a ∈ Icc 0 P.assetCap) : + ∃ a' ∈ Icc 0 (P.maxSaving a), 0 < P.consumption a a' ∧ + P.toExtended.valueFunction a + = P.u (P.consumption a a') + P.discount * P.toExtended.valueFunction a' := by + obtain ⟨a', ha', heq⟩ := P.toExtended.exists_optimal_policy a + -- the reward at the optimum cannot be `⊥`, or the value would not be real + have hne : P.rewardFn (a, a') ≠ ⊥ := by + intro hb + rw [show P.toExtended.reward (a, a') = P.rewardFn (a, a') from rfl, hb, + EReal.bot_add _] at heq + exact EReal.coe_ne_bot _ heq + have hpos : 0 < min P.maxConsumption (P.consumption a a') := by + by_contra hle + push Not at hle + exact hne (extendBot_of_nonpos hle) + have hc : 0 < P.consumption a a' := lt_of_lt_of_le hpos (min_le_right _ _) + have hclamp : min P.maxConsumption (P.consumption a a') = P.consumption a a' := + min_eq_right (P.consumption_le_maxConsumption ha ha'.1) + refine ⟨a', ha', hc, ?_⟩ + have hrw : P.toExtended.reward (a, a') = ((P.u (P.consumption a a') : ℝ) : EReal) := + calc P.toExtended.reward (a, a') + = extendBot P.u (min P.maxConsumption (P.consumption a a')) := rfl + _ = extendBot P.u (P.consumption a a') := by rw [hclamp] + _ = _ := extendBot_of_pos hc + rw [hrw, ← EReal.coe_add, EReal.coe_eq_coe_iff] at heq + exact heq + +/-! ### Concavity and the optimal policy + +The `σ < 1` model gets concavity from the general criterion in +`LeanEconomics.DynamicProgramming.Bellman`. That criterion is stated for a real-valued +reward and does not apply here, because `ConcaveOn` over `EReal` would need an ordered +module structure that does not sit well. The way round is that the `-∞` never appears where +it matters: at an optimum consumption is strictly positive, so every value the argument +touches is finite, and the reasoning can be carried out in the reals. -/ + +theorem resources_eq_affine {x : ℝ} (hx : 0 ≤ x) : + P.resources x = P.income + (1 + P.interest) * x := by + simp only [resources, max_eq_right hx] + +theorem resources_affine_comb {x y θ φ : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) (hθ : 0 ≤ θ) + (hφ : 0 ≤ φ) (hθφ : θ + φ = 1) : + P.resources (θ * x + φ * y) = θ * P.resources x + φ * P.resources y := by + have hmix : 0 ≤ θ * x + φ * y := by positivity + rw [P.resources_eq_affine hmix, P.resources_eq_affine hx, P.resources_eq_affine hy] + linear_combination (-P.income) * hθφ + +theorem maxSaving_le_assetCap (a : ℝ) : P.maxSaving a ≤ P.assetCap := + max_le P.assetCap_nonneg (min_le_left _ _) + +theorem feasible_subset_region {x a : ℝ} (ha : a ∈ P.toExtended.feasible x) : + a ∈ Icc 0 P.assetCap := + ⟨ha.1, ha.2.trans (P.maxSaving_le_assetCap x)⟩ + +theorem maxSaving_eq (x : ℝ) : P.maxSaving x = min P.assetCap (P.resources x) := + max_eq_right (le_min P.assetCap_nonneg (P.resources_pos x).le) + +theorem feasible_convex {x y : ℝ} (hx : x ∈ Icc 0 P.assetCap) (hy : y ∈ Icc 0 P.assetCap) + {ax ay θ φ : ℝ} (hax : ax ∈ P.toExtended.feasible x) (hay : ay ∈ P.toExtended.feasible y) + (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) (hθφ : θ + φ = 1) : + θ * ax + φ * ay ∈ P.toExtended.feasible (θ * x + φ * y) := by + obtain ⟨hax0, haxm⟩ := hax + obtain ⟨hay0, haym⟩ := hay + rw [P.maxSaving_eq x] at haxm + rw [P.maxSaving_eq y] at haym + have hmix : (0 : ℝ) ≤ θ * x + φ * y := + add_nonneg (mul_nonneg hθ hx.1) (mul_nonneg hφ hy.1) + refine ⟨add_nonneg (mul_nonneg hθ hax0) (mul_nonneg hφ hay0), ?_⟩ + rw [P.maxSaving_eq (θ * x + φ * y)] + refine le_min ?_ ?_ + · have h1 : ax ≤ P.assetCap := haxm.trans (min_le_left _ _) + have h2 : ay ≤ P.assetCap := haym.trans (min_le_left _ _) + nlinarith + · have h1 : ax ≤ P.resources x := haxm.trans (min_le_right _ _) + have h2 : ay ≤ P.resources y := haym.trans (min_le_right _ _) + rw [P.resources_affine_comb hx.1 hy.1 hθ hφ hθφ] + nlinarith + +/-- A finite reward means strictly positive consumption. -/ +theorem consumption_pos_of_ne_bot {x a : ℝ} (h : P.toExtended.reward (x, a) ≠ ⊥) : + 0 < P.consumption x a := by + by_contra hle + push Not at hle + refine h ?_ + calc P.toExtended.reward (x, a) + = extendBot P.u (min P.maxConsumption (P.consumption x a)) := rfl + _ = ⊥ := extendBot_of_nonpos ((min_le_right _ _).trans hle) + +/-- Where consumption is positive the reward is an ordinary real utility. -/ +theorem reward_eq_coe {x a : ℝ} (hx : x ∈ Icc 0 P.assetCap) (ha : a ∈ P.toExtended.feasible x) + (hc : 0 < P.consumption x a) : + P.toExtended.reward (x, a) = ((P.u (P.consumption x a) : ℝ) : EReal) := + calc P.toExtended.reward (x, a) + = extendBot P.u (min P.maxConsumption (P.consumption x a)) := rfl + _ = extendBot P.u (P.consumption x a) := by + rw [min_eq_right (P.consumption_le_maxConsumption hx ha.1)] + _ = _ := extendBot_of_pos hc + +/-- At an optimum consumption is positive and the value is an ordinary real expression. -/ +theorem bellmanFn_eq_of_optimal {v : ℝ →ᵇ ℝ} {x a : ℝ} (hx : x ∈ Icc 0 P.assetCap) + (ha : a ∈ P.toExtended.feasible x) + (heq : P.toExtended.objectiveE v x a = ((P.toExtended.bellmanFn v x : ℝ) : EReal)) : + 0 < P.consumption x a ∧ + P.toExtended.bellmanFn v x = P.u (P.consumption x a) + P.discount * v a := by + have hne : P.toExtended.reward (x, a) ≠ ⊥ := by + intro hb + rw [ExtendedProgram.objectiveE, hb, EReal.bot_add _] at heq + exact EReal.coe_ne_bot _ heq.symm + have hc := P.consumption_pos_of_ne_bot hne + refine ⟨hc, ?_⟩ + rw [ExtendedProgram.objectiveE, P.reward_eq_coe hx ha hc, ← EReal.coe_add, + EReal.coe_eq_coe_iff] at heq + exact heq.symm + +/-- **The Bellman operator preserves concavity on the region.** -/ +theorem concaveOn_bellman (v : ℝ →ᵇ ℝ) (hv : ConcaveOn ℝ (Icc 0 P.assetCap) ⇑v) : + ConcaveOn ℝ (Icc 0 P.assetCap) ⇑(P.toExtended.bellman v) := by + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + obtain ⟨ax, hax, heqx⟩ := P.toExtended.exists_optimal_action v x + obtain ⟨ay, hay, heqy⟩ := P.toExtended.exists_optimal_action v y + obtain ⟨hcx, hbx⟩ := P.bellmanFn_eq_of_optimal hx hax heqx + obtain ⟨hcy, hby⟩ := P.bellmanFn_eq_of_optimal hy hay heqy + have hxy : θ * x + φ * y ∈ Icc 0 P.assetCap := by + simpa using convex_Icc (0 : ℝ) P.assetCap hx hy hθ hφ hθφ + have hmix := P.feasible_convex hx hy hax hay hθ hφ hθφ + have hcmix : P.consumption (θ * x + φ * y) (θ * ax + φ * ay) + = θ * P.consumption x ax + φ * P.consumption y ay := by + simp only [consumption] + rw [P.resources_affine_comb hx.1 hy.1 hθ hφ hθφ] + ring + have hcm : 0 < P.consumption (θ * x + φ * y) (θ * ax + φ * ay) := by + rw [hcmix] + rcases lt_or_eq_of_le hθ with h | h + · exact add_pos_of_pos_of_nonneg (mul_pos h hcx) (mul_nonneg hφ hcy.le) + · have hφ1 : φ = 1 := by linarith + rw [← h, hφ1] + simpa using hcy + -- the mixed action is available, so it bounds the mixed value from below + have hle := P.toExtended.le_bellmanFn v hmix + have hobj : P.toExtended.objectiveE v (θ * x + φ * y) (θ * ax + φ * ay) + = ((P.u (P.consumption (θ * x + φ * y) (θ * ax + φ * ay)) + + P.discount * v (θ * ax + φ * ay) : ℝ) : EReal) := by + rw [ExtendedProgram.objectiveE, P.reward_eq_coe hxy hmix hcm, ← EReal.coe_add] + rfl + rw [hobj] at hle + have hle' : P.u (P.consumption (θ * x + φ * y) (θ * ax + φ * ay)) + + P.discount * v (θ * ax + φ * ay) ≤ P.toExtended.bellmanFn v (θ * x + φ * y) := by + exact_mod_cast hle + -- concavity of utility and of the continuation value + have hu := P.strictConcaveOn_u.concaveOn.2 hcx hcy hθ hφ hθφ + have hvv := hv.2 (P.feasible_subset_region hax) (P.feasible_subset_region hay) hθ hφ hθφ + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hscaled := mul_le_mul_of_nonneg_left hvv hβ + simp only [smul_eq_mul] at hu hvv hscaled + rw [hcmix] at hle' + simp only [smul_eq_mul, ExtendedProgram.bellman_apply] + rw [hbx, hby] + linarith + +/-- **The value function is concave on `[0, assetCap]`**, for utility unbounded below. -/ +theorem concaveOn_valueFunction : + ConcaveOn ℝ (Icc 0 P.assetCap) ⇑P.toExtended.valueFunction := + Blackwell.concaveOn_valueFunction (convex_Icc _ _) P.toExtended.blackwell + P.toExtended.discount_lt_one fun v hv => P.concaveOn_bellman v hv + +/-- **The optimal action is unique.** Two distinct optimal actions would be beaten by their +midpoint, since utility is strictly concave and the continuation value concave. -/ +theorem optimal_action_unique {x : ℝ} (hx : x ∈ Icc 0 P.assetCap) {a₀ a₁ : ℝ} + (h₀ : a₀ ∈ P.toExtended.feasible x) (h₁ : a₁ ∈ P.toExtended.feasible x) + (hm₀ : P.toExtended.objectiveE P.toExtended.valueFunction x a₀ + = ((P.toExtended.bellmanFn P.toExtended.valueFunction x : ℝ) : EReal)) + (hm₁ : P.toExtended.objectiveE P.toExtended.valueFunction x a₁ + = ((P.toExtended.bellmanFn P.toExtended.valueFunction x : ℝ) : EReal)) : + a₀ = a₁ := by + by_contra hne + obtain ⟨hc₀, hb₀⟩ := P.bellmanFn_eq_of_optimal hx h₀ hm₀ + obtain ⟨hc₁, hb₁⟩ := P.bellmanFn_eq_of_optimal hx h₁ hm₁ + have hhalf : (0 : ℝ) < 1 / 2 := by norm_num + have hsum : (1 : ℝ) / 2 + 1 / 2 = 1 := by norm_num + have hmem : (1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁ ∈ P.toExtended.feasible x := by + simpa using convex_Icc (0 : ℝ) (P.maxSaving x) h₀ h₁ hhalf.le hhalf.le hsum + have hcmid : P.consumption x ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + = (1 / 2 : ℝ) * P.consumption x a₀ + (1 / 2 : ℝ) * P.consumption x a₁ := by + simp only [consumption]; ring + have hcm : 0 < P.consumption x ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) := by + rw [hcmid]; linarith + have hcne : P.consumption x a₀ ≠ P.consumption x a₁ := by + simp only [consumption] + intro h + exact hne (by linarith) + -- strict gain at the midpoint + have hu := P.strictConcaveOn_u.2 hc₀ hc₁ hcne hhalf hhalf hsum + have hvv := P.concaveOn_valueFunction.2 (P.feasible_subset_region h₀) + (P.feasible_subset_region h₁) hhalf.le hhalf.le hsum + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hscaled := mul_le_mul_of_nonneg_left hvv hβ + simp only [smul_eq_mul] at hu hvv hscaled + have hle := P.toExtended.le_bellmanFn P.toExtended.valueFunction hmem + have hobj : P.toExtended.objectiveE P.toExtended.valueFunction x + ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + = ((P.u (P.consumption x ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁)) + + P.discount * P.toExtended.valueFunction ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + : ℝ) : EReal) := by + rw [ExtendedProgram.objectiveE, P.reward_eq_coe hx hmem hcm, ← EReal.coe_add] + rfl + rw [hobj] at hle + have hle' : P.u (P.consumption x ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁)) + + P.discount * P.toExtended.valueFunction ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + ≤ P.toExtended.bellmanFn P.toExtended.valueFunction x := by exact_mod_cast hle + rw [hcmid] at hle' + linarith + +/-- The optimal saving choice. By `optimal_action_unique` this is *the* optimal choice on +the region, not merely *an* optimal choice. -/ +noncomputable def policy (x : ℝ) : ℝ := + Classical.choose (P.toExtended.exists_optimal_action P.toExtended.valueFunction x) + +theorem policy_mem (x : ℝ) : P.policy x ∈ P.toExtended.feasible x := + (Classical.choose_spec + (P.toExtended.exists_optimal_action P.toExtended.valueFunction x)).1 + +theorem policy_optimal (x : ℝ) : + P.toExtended.objectiveE P.toExtended.valueFunction x (P.policy x) + = ((P.toExtended.bellmanFn P.toExtended.valueFunction x : ℝ) : EReal) := + (Classical.choose_spec + (P.toExtended.exists_optimal_action P.toExtended.valueFunction x)).2 + +theorem policy_mem_region (x : ℝ) : P.policy x ∈ Icc 0 P.assetCap := + P.feasible_subset_region (P.policy_mem x) + +/-- Consumption under the optimal policy is strictly positive: the household never starves +itself. With a floor this had to be proved; here it follows from the value being real. -/ +theorem consumption_policy_pos {x : ℝ} (hx : x ∈ Icc 0 P.assetCap) : + 0 < P.consumption x (P.policy x) := + (P.bellmanFn_eq_of_optimal hx (P.policy_mem x) (P.policy_optimal x)).1 + +/-- **Any optimal action is the policy.** -/ +theorem eq_policy_of_optimal {x : ℝ} (hx : x ∈ Icc 0 P.assetCap) {a : ℝ} + (ha : a ∈ P.toExtended.feasible x) + (hopt : P.toExtended.objectiveE P.toExtended.valueFunction x a + = ((P.toExtended.bellmanFn P.toExtended.valueFunction x : ℝ) : EReal)) : + a = P.policy x := + P.optimal_action_unique hx ha (P.policy_mem x) hopt (P.policy_optimal x) + +/-- **The Bellman equation at the policy**, with honest utility and positive consumption. -/ +theorem valueFunction_eq_policy {x : ℝ} (hx : x ∈ Icc 0 P.assetCap) : + P.toExtended.valueFunction x + = P.u (P.consumption x (P.policy x)) + + P.discount * P.toExtended.valueFunction (P.policy x) := by + have h := (P.bellmanFn_eq_of_optimal hx (P.policy_mem x) (P.policy_optimal x)).2 + have hfix : P.toExtended.bellmanFn P.toExtended.valueFunction x + = P.toExtended.valueFunction x := by + conv_rhs => rw [← P.toExtended.bellman_valueFunction] + rfl + rw [← hfix, h] + +/-- **The maximiser set is exactly the policy.** -/ +theorem argmax_eq_singleton {x : ℝ} (hx : x ∈ Icc 0 P.assetCap) : + argmax (P.toExtended.objectiveE P.toExtended.valueFunction) P.toExtended.feasible x + = {P.policy x} := by + ext a + simp only [argmax, Set.mem_ofPred_eq, Set.mem_singleton_iff] + constructor + · rintro ⟨ha, hmax⟩ + refine P.eq_policy_of_optimal hx ha (le_antisymm (P.toExtended.le_bellmanFn _ ha) ?_) + rw [← P.policy_optimal x] + exact isMaxOn_iff.mp hmax _ (P.policy_mem x) + · rintro rfl + refine ⟨P.policy_mem x, isMaxOn_iff.mpr fun b hb => ?_⟩ + rw [P.policy_optimal x] + exact P.toExtended.le_bellmanFn _ hb + +/-- **The optimal policy is continuous on the region**, for utility unbounded below. -/ +theorem continuousOn_policy : ContinuousOn P.policy (Icc 0 P.assetCap) := + continuousOn_of_upperHemicontinuous_singleton + (P.toExtended.upperHemicontinuous_argmax P.toExtended.valueFunction) + fun _ hx => P.argmax_eq_singleton hx + +/-! ### Utilities that qualify -/ + +/-- CES period utility. -/ +noncomputable def crra (σ c : ℝ) : ℝ := c ^ (1 - σ) / (1 - σ) + +theorem continuousOn_crra {σ : ℝ} : ContinuousOn (crra σ) (Ioi 0) := fun c hc => + (((Real.continuousAt_rpow_const c (1 - σ) (Or.inl (ne_of_gt hc))).div_const _)).continuousWithinAt + +theorem monotoneOn_crra {σ : ℝ} (hσ : 1 < σ) : MonotoneOn (crra σ) (Ioi 0) := by + intro x hx y _ hxy + have hneg : (1 : ℝ) - σ < 0 := by linarith + simp only [crra] + rw [div_le_div_right_of_neg hneg] + exact Real.rpow_le_rpow_of_nonpos hx hxy hneg.le + +theorem continuousOn_log : ContinuousOn Real.log (Ioi 0) := + Real.continuousOn_log.mono fun _ hx => ne_of_gt hx + +theorem monotoneOn_log : MonotoneOn Real.log (Ioi 0) := Real.strictMonoOn_log.monotoneOn + +/-- A calibration with `σ = 2`, where CES utility is `-1 / c`. -/ +noncomputable def calibrated : ConsumptionSavingsUnbounded where + income := 1 + interest := 1 / 20 + assetCap := 10 + discount := 24 / 25 + u := fun c => -c⁻¹ + income_pos := by norm_num + interest_gt_neg_one := by norm_num + assetCap_nonneg := by norm_num + discount_lt_one := by norm_num + continuousOn_u := (continuousOn_id.inv₀ fun x hx => ne_of_gt hx).neg + monotoneOn_u := by + intro x hx y _ hxy + have : (0 : ℝ) < x := hx + simp only [neg_le_neg_iff] + gcongr + tendsto_atBot_u := tendsto_neg_atTop_atBot.comp tendsto_inv_nhdsGT_zero + strictConcaveOn_u := strictConcaveOn_neg_inv + +/-- A log calibration (`σ = 1`). -/ +noncomputable def calibratedLog : ConsumptionSavingsUnbounded where + income := 1 + interest := 1 / 20 + assetCap := 10 + discount := 24 / 25 + u := Real.log + income_pos := by norm_num + interest_gt_neg_one := by norm_num + assetCap_nonneg := by norm_num + discount_lt_one := by norm_num + continuousOn_u := continuousOn_log + monotoneOn_u := monotoneOn_log + tendsto_atBot_u := Real.tendsto_log_nhdsGT_zero + strictConcaveOn_u := strictConcaveOn_log_Ioi + +end ConsumptionSavingsUnbounded + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/EulerCorner.lean b/LeanPool/AiyagariEquilibrium/Models/EulerCorner.lean new file mode 100644 index 000000000..5e8ff4c94 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/EulerCorner.lean @@ -0,0 +1,179 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.ImpatientDecline + +/-! +# The corner condition from the Euler inequality + +`policy_eq_zero_of_corner_at` gets the corner from a LIPSCHITZ bound on the value function, +`β L < m`. The constant `L` is `slopeBound / (1 - βR)`, and at Aiyagari's `β = 0.96` that is +several hundred times the true slope, so the test fails by orders of magnitude. This file gets the +corner from the Euler inequality instead, and the only thing it asks is `βR < 1`. + +## The argument + +Two steps, both at the fixed point. + +**Nobody at the borrowing limit consumes less than the worst income.** Take the income state at +which consumption at the limit is smallest. If that household were saving, its Euler inequality +(`euler_ge`) would read `u'(c) ≤ βR E[u'(c')]`; but tomorrow's consumption is at least today's +minimum (consumption rises with assets, and assets can only have risen), so `u'(c') ≤ u'(c)` and +`u'(c) ≤ βR u'(c)`, impossible with `βR < 1`. So the household at the minimum is at the corner, +consumes its whole income, and the minimum is at least `minIncome`. + +**The corner, state by state.** At any state where `βR · u'(minIncome) < u'(cash on hand)`, +saving is not optimal: if it were, the Euler inequality and step one would give +`u'(cash on hand) ≤ u'(c) ≤ βR u'(minIncome)`. This is the contrapositive of `euler_ge`. + +For log utility the test at the worst income state is `a < minIncome (1 - βR) / (βR²)`: a small +but positive corner region, at every `β < 1/R`, without assuming a value for `u'(0)`. +Related literature: Clarida (1987), *Consumption, Liquidity Constraints and Asset Accumulation +in the Presence of Random Income Fluctuations*. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **At the borrowing limit, nobody consumes less than the worst income.** The household at the +minimum is at the corner — otherwise its own Euler inequality would push it up by `1/βR`. -/ +theorem minIncome_le_consumptionFn_floor + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + (z : Z) : P.minIncome + P.interest * assetFloor ≤ P.consumptionFn z assetFloor := by + have hfl : assetFloor ∈ Icc assetFloor assetCap := ⟨le_rfl, P.assetFloor_le_assetCap⟩ + -- the state at which consumption at the limit is smallest + obtain ⟨z₀, -, hz₀⟩ := Finset.exists_min_image Finset.univ + (fun z' => P.consumptionFn z' assetFloor) Finset.univ_nonempty + have hmin : ∀ z', P.consumptionFn z₀ assetFloor ≤ P.consumptionFn z' assetFloor := + fun z' => hz₀ z' (Finset.mem_univ _) + -- it is at the corner + have hcorner : P.policy (assetFloor, z₀) = assetFloor := by + by_contra hne + set A : ℝ := P.policy (assetFloor, z₀) with hA + have hAmem : A ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policy_mem _) + have hA0 : assetFloor < A := lt_of_le_of_ne hAmem.1 (Ne.symm hne) + have hbv := P.toExtended.bellman_valueFunction + have hc : 0 < P.consumptionFn z₀ assetFloor := hpos z₀ _ hfl + have hE := P.euler_ge (v := P.toExtended.valueFunction) (z := z₀) (a := assetFloor) (A := A) + (du := du (P.consumptionFn z₀ assetFloor)) (du' := fun z' => du (P.consumptionFn z' A)) + hfl (by rw [hbv, hA]; exact congrFun P.policyOf_valueFunction (assetFloor, z₀)) hA0 + (fun z' => hslack z' A hAmem) (by rw [hbv]; exact hc) + (by rw [hbv]; exact hderiv _ hc) (fun z' => hpos z' A hAmem) + (fun z' => hderiv _ (hpos z' A hAmem)) + -- tomorrow's consumption is at least today's minimum, so marginal utility is at most today's + have hbound : ∀ z', du (P.consumptionFn z' A) ≤ du (P.consumptionFn z₀ assetFloor) := + fun z' => hanti (mem_Ioi.mpr hc) (mem_Ioi.mpr (hpos z' A hAmem)) + (le_trans (hmin z') (P.consumptionFn_mono hfl hAmem hAmem.1)) + have hsum : ∑ z' : Z, P.transitionMatrix z₀ z' * du (P.consumptionFn z' A) + ≤ du (P.consumptionFn z₀ assetFloor) := by + refine le_trans (Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (hbound z') (P.transitionMatrix_nonneg z₀ z')) ?_ + rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + have hdu : 0 < du (P.consumptionFn z₀ assetFloor) := hdupos _ hc + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hcoef : (0 : ℝ) ≤ (P.discount : ℝ) * (1 + P.interest) := mul_nonneg hβ hR.le + have h2 := mul_le_mul_of_nonneg_left hsum hcoef + nlinarith [hE, h2, hdu, hβR] + -- so it consumes its whole income + have hres : P.resources (assetFloor, z₀) = P.income z₀ + (1 + P.interest) * assetFloor := by + simp only [resources, max_self] + have hc₀ : P.consumptionFn z₀ assetFloor = P.income z₀ + P.interest * assetFloor := by + simp only [consumptionFn, consumption, hcorner, hres]; ring + have := P.minIncome_le z₀ + linarith [hmin z, hc₀] + +/-- **The corner from the Euler inequality.** Wherever `βR · u'(minIncome + r·floor)` is below the +marginal utility of cash on hand, the household saves nothing. The contrapositive of `euler_ge`, +with `minIncome_le_consumptionFn_floor` bounding tomorrow's marginal utility. + +Nothing here depends on how large `β` is beyond `βR < 1`; that is what makes it usable at +`β = 0.96`, where the Lipschitz corner `policy_eq_zero_of_corner_at` is hopeless. -/ +theorem policy_eq_floor_of_euler + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) + (hcond : (P.discount : ℝ) * (1 + P.interest) * du (P.minIncome + P.interest * assetFloor) + < du (P.resources (a, z) - assetFloor)) : + P.policy (a, z) = assetFloor := by + by_contra hne + set A : ℝ := P.policy (a, z) with hA + have hAmem : A ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policy_mem _) + have hA0 : assetFloor < A := lt_of_le_of_ne hAmem.1 (Ne.symm hne) + have hbv := P.toExtended.bellman_valueFunction + have hc : 0 < P.consumptionFn z a := hpos z a ha + have hE := P.euler_ge (v := P.toExtended.valueFunction) (z := z) (a := a) (A := A) + (du := du (P.consumptionFn z a)) (du' := fun z' => du (P.consumptionFn z' A)) + ha (by rw [hbv, hA]; exact congrFun P.policyOf_valueFunction (a, z)) hA0 + (fun z' => hslack z' A hAmem) (by rw [hbv]; exact hc) + (by rw [hbv]; exact hderiv _ hc) (fun z' => hpos z' A hAmem) + (fun z' => hderiv _ (hpos z' A hAmem)) + -- the worst income bounds tomorrow's marginal utility + have hy : 0 < P.minIncome + P.interest * assetFloor := by + have h1 := P.minConsumption_le_floor + have h2 := P.minConsumption_pos + linarith + have hfl : assetFloor ∈ Icc assetFloor assetCap := ⟨le_rfl, P.assetFloor_le_assetCap⟩ + have hbound : ∀ z', du (P.consumptionFn z' A) ≤ du (P.minIncome + P.interest * assetFloor) := + fun z' => hanti (mem_Ioi.mpr hy) (mem_Ioi.mpr (hpos z' A hAmem)) + (le_trans (P.minIncome_le_consumptionFn_floor hβR hderiv hanti hdupos hpos hslack z') + (P.consumptionFn_mono hfl hAmem hAmem.1)) + have hsum : ∑ z' : Z, P.transitionMatrix z z' * du (P.consumptionFn z' A) + ≤ du (P.minIncome + P.interest * assetFloor) := by + refine le_trans (Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (hbound z') (P.transitionMatrix_nonneg z z')) ?_ + rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + -- and cash on hand bounds today's from below + have hcle : P.consumptionFn z a ≤ P.resources (a, z) - assetFloor := by + simp only [consumptionFn, consumption]; linarith [hAmem.1] + have hres0 : 0 < P.resources (a, z) - assetFloor := lt_of_lt_of_le hc hcle + have htoday : du (P.resources (a, z) - assetFloor) ≤ du (P.consumptionFn z a) := + hanti (mem_Ioi.mpr hc) (mem_Ioi.mpr hres0) hcle + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hcoef : (0 : ℝ) ≤ (P.discount : ℝ) * (1 + P.interest) := mul_nonneg hβ hR.le + have h2 := mul_le_mul_of_nonneg_left hsum hcoef + linarith [hE, h2, htoday, hcond] + +/-! ### CRRA and log -/ + +/-- **The Euler corner for CRRA**, `u' c = c ^ (-γ)`, at a zero borrowing limit. The test at +state `z` is `βR · minIncome^(-γ) < resources^(-γ)`, i.e. +`resources (a, z) < minIncome · (βR)^(-1/γ)`. -/ +theorem crra_policy_eq_zero_of_euler {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + (hpos : ∀ (z : Z), ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc (0 : ℝ) assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) + (hcond : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < P.resources (a, z) ^ (-γ)) : + P.policy (a, z) = 0 := by + subst hfl + refine P.policy_eq_floor_of_euler hβR (du := fun c => c ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_crraUtility γ hc) + (fun _ hx _ _ hxy => Real.rpow_le_rpow_of_nonpos hx hxy (by linarith)) + (fun c hc => Real.rpow_pos_of_pos hc _) hpos hslack ha z ?_ + simpa using hcond + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/ImpatientDecline.lean b/LeanPool/AiyagariEquilibrium/Models/ImpatientDecline.lean new file mode 100644 index 000000000..3c9a5616f --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/ImpatientDecline.lean @@ -0,0 +1,205 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationIterate +public import LeanPool.AiyagariEquilibrium.Distribution.Uniqueness + +/-! +# Worst-state asset decline from impatience + +`NaturalAssetBound` gets assets to decline from a LINEAR lower bound on consumption, which it +can supply only for log utility, and with the very lossy constant `exp (-2 β ‖V‖)`. Its own +docstring says what the sharp argument would need: "Getting it needs the Euler equation, hence +the envelope condition, hence the machinery that `IncomeFluctuationEnvelope` supplies only under +side conditions." That machinery now exists (`hasDerivAt_bellman`, `euler_ge`), and with it the +decline condition is not a calibration at all — it is exactly impatience. + +## The argument, in one line + +Let `z` be the income state at which consumption is LOWEST, and suppose the household does not +decumulate there: `A = g(a, z) ≥ a`. The Euler inequality from saving less is + + `u'(c(a, z)) ≤ β R · Σ_{z'} π_{z z'} u'(c(A, z'))`. + +Consumption rises with assets and `A ≥ a`, so `c(A, z') ≥ c(a, z') ≥ c(a, z)`, and marginal +utility falls, so every term on the right is at most `u'(c(a, z))`. The weights sum to one. +Hence `u'(c(a,z)) ≤ β R · u'(c(a,z))`, and since marginal utility is positive, `1 ≤ β R`. + +So `β R < 1` forces decline. No limits, no asymptotic marginal propensity to consume, no +calibration — and nothing is asked of the utility beyond a positive, decreasing derivative. + +## What it costs + +Only the Euler inequality's own side conditions: consumption positive at every optimum and the +saving strictly below its cap (`hslack`), which is `policyOf_lt_maxSaving` once the asset cap is +slack. Note the direction — `euler_ge` comes from the deviation "save LESS", so it needs room +BELOW, which is exactly what `A ≥ a > assetFloor` provides. + +## Source and scope + +Related source: Açıkgöz, April 2016 author draft, Proposition 3, printed page 8 +(https://mpra.ub.uni-muenchen.de/71066/1/MPRA_paper_71066.pdf#page=9). +That proposition concerns worst-state decline; Proposition 4 on the same page concerns a +finite asset bound. The capped variants here have their own explicit positivity, cap-slack +and iid or monotone-transition hypotheses; they do not restate the uncapped proposition. + + +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **Impatience forces decumulation at a state where consumption is lowest.** -/ +theorem policy_lt_self_of_impatient + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (ha0 : assetFloor < a) {z : Z} + (hmin : ∀ z' : Z, P.consumptionFn z a ≤ P.consumptionFn z' a) : + P.policy (a, z) < a := by + by_contra hcon + rw [not_lt] at hcon + set A : ℝ := P.policy (a, z) with hA + have hAmem : A ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policy_mem _) + have hA0 : assetFloor < A := lt_of_lt_of_le ha0 hcon + have hbv := P.toExtended.bellman_valueFunction + have hc : 0 < P.consumptionFn z a := hpos z a ha + -- the Euler inequality from saving less + have hE := P.euler_ge (v := P.toExtended.valueFunction) (z := z) (a := a) (A := A) + (du := du (P.consumptionFn z a)) (du' := fun z' => du (P.consumptionFn z' A)) + ha (by rw [hbv, hA]; exact congrFun P.policyOf_valueFunction (a, z)) hA0 + (fun z' => hslack z' A hAmem) (by rw [hbv]; exact hc) + (by rw [hbv]; exact hderiv _ hc) (fun z' => hpos z' A hAmem) + (fun z' => hderiv _ (hpos z' A hAmem)) + -- every next-period marginal utility is at most today's + have hbound : ∀ z' : Z, du (P.consumptionFn z' A) ≤ du (P.consumptionFn z a) := fun z' => + hanti (mem_Ioi.mpr hc) (mem_Ioi.mpr (hpos z' A hAmem)) + (le_trans (hmin z') (P.consumptionFn_mono ha hAmem hcon)) + have hsum : ∑ z' : Z, P.transitionMatrix z z' * du (P.consumptionFn z' A) + ≤ du (P.consumptionFn z a) := by + refine le_trans (Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (hbound z') (P.transitionMatrix_nonneg z z')) ?_ + rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + have hdu : 0 < du (P.consumptionFn z a) := hdupos _ hc + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hcoef : (0 : ℝ) ≤ (P.discount : ℝ) * (1 + P.interest) := mul_nonneg hβ hR.le + have h2 : ((P.discount : ℝ) * (1 + P.interest)) + * (∑ z' : Z, P.transitionMatrix z z' * du (P.consumptionFn z' A)) + ≤ ((P.discount : ℝ) * (1 + P.interest)) * du (P.consumptionFn z a) := + mul_le_mul_of_nonneg_left hsum hcoef + nlinarith [hE, h2, hdu, hβR] + +/-! ### Which income state consumes least + +The Euler argument above runs at the state where consumption is lowest, and the Doeblin +construction wants a FIXED state to repeat. The two agree when income is iid: the continuation +is then the same function of saving whatever today's state, so the problem depends on the state +only through cash on hand, and `consumptionFn_mono` generalises from two asset levels at one +income state to two states with ordered resources. -/ + +/-- **Consumption rises with resources**, across income states as well as asset levels, whenever +the two states share a continuation. This is `consumptionFn_mono` with the state allowed to +move: its proof never used `z` for anything but `cont z`. -/ +theorem consumptionFn_mono_of_cont_eq {a a' : ℝ} {z₁ z₂ : Z} (hcont : P.cont z₁ = P.cont z₂) + (ha : a ∈ Icc assetFloor assetCap) (ha' : a' ∈ Icc assetFloor assetCap) + (hres : P.resources (a, z₁) ≤ P.resources (a', z₂)) : + P.consumptionFn z₁ a ≤ P.consumptionFn z₂ a' := by + simpa only [P.consumptionFnOf_valueFunction] using + P.consumptionFnOf_le_across_states P.concaveOn_valueFunction + (z₁ := z₁) (z₂ := z₂) (fun x y _ _ _ => by + change P.cont z₂ y - P.cont z₂ x ≤ P.cont z₁ y - P.cont z₁ x + rw [← hcont]) ha ha' hres + +/-- Income is iid: the transition probabilities do not depend on today's state. -/ +def IidIncome : Prop := ∀ z₁ z₂ z' : Z, P.transitionMatrix z₁ z' = P.transitionMatrix z₂ z' + +theorem cont_eq_of_iid (hiid : P.IidIncome) (z₁ z₂ : Z) : P.cont z₁ = P.cont z₂ := by + funext x + exact Finset.sum_congr rfl fun z' _ => by rw [hiid z₁ z₂ z'] + +/-- **Consumption is lowest where income is lowest**, for an iid income process. -/ +theorem consumptionFn_le_of_income_le (hiid : P.IidIncome) {z₁ z₂ : Z} + (hinc : P.income z₁ ≤ P.income z₂) {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + P.consumptionFn z₁ a ≤ P.consumptionFn z₂ a := + P.consumptionFn_mono_of_cont_eq (P.cont_eq_of_iid hiid z₁ z₂) ha ha + (by simp only [resources]; linarith) + +/-! ### The decline condition, from impatience + +Putting the two together: with iid income the lowest-income state is the lowest-consumption +state, so the Euler argument runs there, and assets decline at it from EVERY asset level above +the borrowing constraint. That is the hypothesis `hdecl` of `exists_exhaust_of_decline`, which +every witness has been discharging by a numerical calibration. It is impatience. + +What is NOT replaced is the corner condition `hzero`: decline alone gives a strictly falling +sequence, and the Doeblin argument needs an ATOM — the constraint has to be reached exactly, not +approached. That is a statement about the bottom of the state space, where the Euler equation +does not hold, and it stays a calibration. -/ + +/-- **Worst-state decline in the capped iid model.** With iid income and `β(1+r) < 1`, +assets strictly decline +at the lowest income state, at every asset level above the borrowing constraint. -/ +theorem policy_lt_self_of_impatient_iid + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (ha0 : assetFloor < a) : + P.policy (a, z₀) < a := + P.policy_lt_self_of_impatient hβR hderiv hanti hdupos hpos hslack ha ha0 + (fun z' => P.consumptionFn_le_of_income_le hiid (hz₀ z') ha) + +/-- **The Doeblin exhaustion data, with the decline half free.** Only the corner condition is +left as a calibration. -/ +theorem exists_exhaust_of_impatient + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a₀ : ℝ} (ha₀ : assetFloor < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc assetFloor a₀, P.policy (a, z₀) = assetFloor) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := + P.exists_exhaust_of_decline ha₀ hle hzero fun _a ha => + P.policy_lt_self_of_impatient_iid hβR hiid hderiv hanti hdupos hpos hslack hz₀ + ⟨le_trans ha₀.le ha.1, ha.2⟩ (lt_of_lt_of_le ha₀ ha.1) + +/-! ### CRRA, with the marginal-utility hypotheses discharged -/ + +/-- **Worst-state decline for capped CRRA households.** Marginal utility `c ^ (-γ)` is positive +and decreasing +for every `γ > 0`, so impatience is the whole hypothesis. -/ +theorem crra_exists_exhaust_of_impatient {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a₀ : ℝ} (ha₀ : assetFloor < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc assetFloor a₀, P.policy (a, z₀) = assetFloor) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := + P.exists_exhaust_of_impatient hβR hiid (du := fun c => c ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_crraUtility γ hc) + (fun _ hx _ _ hxy => Real.rpow_le_rpow_of_nonpos hx hxy (by linarith)) + (fun c hc => Real.rpow_pos_of_pos hc _) hpos hslack hz₀ ha₀ hle hzero + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuation.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuation.lean new file mode 100644 index 000000000..3465411ee --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuation.lean @@ -0,0 +1,765 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.DynamicProgramming.ExtendedStochastic +public import LeanPool.AiyagariEquilibrium.Topology.IccCorrespondence +public import LeanPool.AiyagariEquilibrium.Models.ConsumptionSavingsUnbounded + +/-! +# The income fluctuation problem + +The canonical incomplete-markets household: income follows a finite Markov chain, assets +earn interest, borrowing is ruled out, and the household chooses savings. The state is the +pair `(a, z)` of assets and income state, and + + `V (a, z) = max { u c + β * ∑ z', P z z' * V (a', z') }`. + +Period utility may fall to `-∞` at zero consumption, so this covers log and CRRA with +`σ ≥ 1` -- and, since the utility DOMAIN is a field, CES with `γ < 1` as well. + +## The domain field + +`u` is required to behave on a set `dom` with `Ioi 0 ⊆ dom ⊆ Ici 0`, so there are exactly two +choices: `Ioi 0`, where utility is unbounded below, and `Ici 0`, where it is bounded there. The +old `tendsto_atBot_u` is replaced by `continuousOn_extendDom`, which says that `u` extended by +`⊥` off `dom` is continuous where consumption lives. That is what Berge actually consumes, and +it is exactly `tendsto_atBot_u` when `dom = Ioi 0` (see `continuousOn_extendDom_Ioi`) and merely +continuity of `u` on `Ici 0` in the other case, where the `⊥` branch is unreachable. + +Nothing is lost by the swap: `tendsto_atBot_u` comes BACK as a theorem from `Unbounded`. + +The one real consequence is that positive consumption at the optimum is no longer free. With +`dom = Ioi 0` the reward is `⊥` there and the value being real settles it; with `dom = Ici 0` +it has to be earned from a MARGINAL Inada condition instead — see `ConsumptionFloor`. Results +that need it take `PositiveConsumption` as a hypothesis, which both routes supply. Results that +only need `u` to behave take membership in `dom`, and carry over untouched. + +## Retrofitted onto extended-real rewards + +This file used a floor, with the same apparatus as the deterministic models: `floor`, +`cFloor`, and a chain of results establishing that the floor never binds. All of it is +gone. The reward is `-∞` where consumption vanishes, and positive consumption at the +optimum follows from the value function being real rather than from an argument. + +The stochastic case needed `LeanEconomics.DynamicProgramming.ExtendedStochastic`, since the +continuation here is an expectation over shocks rather than `v` at a single point. That +turned out to be routine: the expectation is a sum of reals, so the objective is again an +extended real plus a finite coercion, and the reward's `-∞` never meets the expectation's +arithmetic. + +## Unchanged + +Discreteness of the income state is still what makes every function of `z` continuous, so +the budget correspondence is continuous in the pair. Consumption is still clamped above and +assets still capped -- the ordinary boundedness requirement, unrelated to the floor. Cash on +hand is computed from `max 0 a`, as in the deterministic retrofits, so that the value stays +finite at every state. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +/-- Utility extended by `⊥` outside the domain on which it is required to behave. With +`D = Ioi 0` this is `extendBot`; with `D = Ici 0` the `⊥` branch is unreachable on `Ici 0`. -/ +noncomputable def extendDom (D : Set ℝ) (u : ℝ → ℝ) (c : ℝ) : EReal := + open Classical in if c ∈ D then ((u c : ℝ) : EReal) else ⊥ + +theorem extendDom_of_mem {D : Set ℝ} {u : ℝ → ℝ} {c : ℝ} (h : c ∈ D) : + extendDom D u c = ((u c : ℝ) : EReal) := by + simp only [extendDom, h, reduceIte] + +theorem extendDom_of_not_mem {D : Set ℝ} {u : ℝ → ℝ} {c : ℝ} (h : c ∉ D) : + extendDom D u c = ⊥ := by + simp only [extendDom, h, reduceIte] + +theorem extendDom_ne_bot_iff {D : Set ℝ} {u : ℝ → ℝ} {c : ℝ} : + extendDom D u c ≠ ⊥ ↔ c ∈ D := by + by_cases h : c ∈ D + · simp [extendDom_of_mem h, h] + · simp [extendDom_of_not_mem h, h] + +/-- With `D = Ioi 0` the extension is `extendBot`, so the divergence condition supplies the +continuity requirement. -/ +theorem extendDom_Ioi (u : ℝ → ℝ) : extendDom (Ioi 0) u = extendBot u := by + funext c + rcases le_or_gt c 0 with h | h + · rw [extendDom_of_not_mem (by simpa using h), extendBot_of_nonpos h] + · rw [extendDom_of_mem (mem_Ioi.mpr h), extendBot_of_pos h] + +/-- The unbounded case: divergence at zero supplies the continuity requirement. -/ +theorem continuousOn_extendDom_Ioi {u : ℝ → ℝ} (hc : ContinuousOn u (Ioi 0)) + (ht : Tendsto u (𝓝[>] 0) atBot) : ContinuousOn (extendDom (Ioi 0) u) (Ici 0) := by + rw [extendDom_Ioi] + exact (continuous_extendBot hc ht).continuousOn + +/-- The income fluctuation problem, with a utility domain supporting bounded and unbounded +below period utility. -/ +structure IncomeFluctuation (Z : Type*) [Fintype Z] [Nonempty Z] [TopologicalSpace Z] + [DiscreteTopology Z] (assetFloor assetCap : ℝ) where + /-- Income in each state. -/ + income : Z → ℝ + /-- The Markov transition matrix on income states. -/ + transitionMatrix : Z → Z → ℝ + /-- The interest rate on assets. -/ + interest : ℝ + /-- The discount factor. -/ + discount : ℝ≥0 + /-- Period utility, required to behave only on positive consumption. -/ + u : ℝ → ℝ + /-- A lower bound on income. -/ + minIncome : ℝ + /-- An upper bound on income. -/ + maxIncome : ℝ + minIncome_pos : 0 < minIncome + minIncome_le : ∀ z, minIncome ≤ income z + le_maxIncome : ∀ z, income z ≤ maxIncome + transitionMatrix_nonneg : ∀ z z', 0 ≤ transitionMatrix z z' + transitionMatrix_sum : ∀ z, ∑ z', transitionMatrix z z' = 1 + interest_gt_neg_one : 0 < 1 + interest + /-- The borrowing limit is below the asset cap, so the household always has somewhere to be. -/ + assetFloor_le_assetCap : assetFloor ≤ assetCap + /-- The asset cap is a large positive number; nothing in the development needs it to be small, + but several bounds need it to have a sign. -/ + assetCap_nonneg : 0 ≤ assetCap + /-- **A declared floor on consumption**, at most what a household pinned at the borrowing limit + can afford. Carrying it as data rather than computing it from the rate is what lets the same + household problem be re-run at a different interest rate without moving the reward's lower + bound: `minConsumption` is the rate-free part of the natural-borrowing-limit condition. -/ + minConsumption : ℝ := minIncome + interest * assetFloor + /-- **The borrowing limit is sustainable**: at the limit, income covers the interest on the debt + with something left to eat. With `assetFloor = 0` this is `0 < minIncome`; with a negative limit + it is Aiyagari's natural-borrowing-limit condition, and it is what replaces positivity of cash + on hand -- resources can be negative when the household is in debt, while consumption cannot. -/ + minConsumption_pos : 0 < minConsumption + minConsumption_le_floor : minConsumption ≤ minIncome + interest * assetFloor + discount_lt_one : discount < 1 + /-- The consumption levels at which utility is required to behave. `Ioi 0` when utility is + unbounded below, `Ici 0` when it is bounded there. -/ + dom : Set ℝ + Ioi_subset_dom : Ioi 0 ⊆ dom + dom_subset_Ici : dom ⊆ Ici 0 + continuousOn_u_dom : ContinuousOn u dom + monotoneOn_u_dom : MonotoneOn u dom + /-- Diminishing marginal utility, which makes the optimal policy unique. -/ + strictConcaveOn_u_dom : StrictConcaveOn ℝ dom u + /-- **What replaces the Inada condition.** Utility, extended by `⊥` off its domain, must be + continuous where consumption lives. For `dom = Ioi 0` this is exactly `Tendsto u (𝓝[>] 0) atBot` + via `continuous_extendBot`; for `dom = Ici 0` the `⊥` branch is never taken and it is just + continuity of `u` on `Ici 0`. -/ + continuousOn_extendDom : ContinuousOn (extendDom dom u) (Ici 0) + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- Cash on hand, with assets read as nonnegative. -/ +noncomputable def resources (s : ℝ × Z) : ℝ := + P.income s.2 + (1 + P.interest) * max assetFloor s.1 + +/-- The largest consumption the capped problem allows. -/ +noncomputable def maxConsumption : ℝ := + P.maxIncome + (1 + P.interest) * assetCap - assetFloor + +/-- The largest asset holding that can be carried forward. -/ +noncomputable def maxSaving (s : ℝ × Z) : ℝ := max assetFloor (min assetCap (P.resources s)) + +/-- Consumption on the budget line. -/ +noncomputable def consumption (s : ℝ × Z) (a' : ℝ) : ℝ := P.resources s - a' + +/-- Consumption clamped into `[0, maxConsumption]`. On the feasible set the lower clamp is +inactive, so this is consumption itself; off it, it keeps the reward's argument in `Ici 0`, which +is where `continuousOn_extendDom` applies. -/ +noncomputable def clampedConsumption (p : (ℝ × Z) × ℝ) : ℝ := + min P.maxConsumption (max 0 (P.consumption p.1 p.2)) + +/-- The reward: utility of consumption clamped above, and `⊥` where consumption falls outside +the domain on which utility behaves. No floor. -/ +noncomputable def rewardFn (p : (ℝ × Z) × ℝ) : EReal := + extendDom P.dom P.u (P.clampedConsumption p) + +/-- **The rates at which the borrowing limit is sustainable.** Raising the interest rate raises +the debt service on a negative floor, so a floor that supports positive consumption at one rate +need not support it at another: sustainability is part of what it means for a rate to be +admissible, not a consequence of `-1 < r`. With `assetFloor = 0` the second conjunct is free. -/ +def RateOK (P : IncomeFluctuation Z assetFloor assetCap) (r : ℝ) : Prop := + 0 < 1 + r ∧ P.minConsumption ≤ P.minIncome + r * assetFloor + +theorem RateOK.one_add_pos {P : IncomeFluctuation Z assetFloor assetCap} {r : ℝ} + (h : P.RateOK r) : 0 < 1 + r := h.1 + +theorem RateOK.sustainable {P : IncomeFluctuation Z assetFloor assetCap} {r : ℝ} + (h : P.RateOK r) : P.minConsumption ≤ P.minIncome + r * assetFloor := h.2 + +/-- At a zero borrowing limit every admissible rate is sustainable. -/ +theorem rateOK_of_floor_zero {P : IncomeFluctuation Z 0 assetCap} {r : ℝ} (hr : 0 < 1 + r) : + P.RateOK r := ⟨hr, by simpa using P.minConsumption_le_floor⟩ + +/-- Sustainability is linear in the rate, so it is inherited from the endpoints of an interval. -/ +theorem rateOK_of_mem_Icc {rlo rhi r : ℝ} (hlo : P.RateOK rlo) (hhi : P.RateOK rhi) + (hr : r ∈ Set.Icc rlo rhi) : P.RateOK r := by + refine ⟨by linarith [hr.1, hlo.1], ?_⟩ + rcases le_total (0 : ℝ) assetFloor with hf | hf + · nlinarith [hlo.2, hr.1, hr.2] + · nlinarith [hhi.2, hr.1, hr.2] + +theorem rateOK_self : P.RateOK P.interest := ⟨P.interest_gt_neg_one, P.minConsumption_le_floor⟩ + +theorem minConsumption_le_consumption_floor (s : ℝ × Z) : + P.minConsumption ≤ P.resources s - assetFloor := by + have h1 : assetFloor ≤ max assetFloor s.1 := le_max_left _ _ + have h2 : (1 + P.interest) * assetFloor ≤ (1 + P.interest) * max assetFloor s.1 := + mul_le_mul_of_nonneg_left h1 P.interest_gt_neg_one.le + have := P.minIncome_le s.2 + have := P.minConsumption_le_floor + simp only [resources]; linarith + +/-- **Cash on hand always exceeds the borrowing limit**, so something can always be eaten. This +replaces positivity of cash on hand: in debt, resources can be negative while consumption cannot. +-/ +theorem assetFloor_lt_resources (s : ℝ × Z) : assetFloor < P.resources s := by + have h1 := P.minConsumption_le_consumption_floor s + have h2 := P.minConsumption_pos + linarith + +theorem minIncome_le_maxIncome : P.minIncome ≤ P.maxIncome := + (P.minIncome_le Classical.ofNonempty).trans (P.le_maxIncome _) + +theorem minConsumption_le_maxConsumption : P.minConsumption ≤ P.maxConsumption := by + have h : (1 + P.interest) * assetFloor ≤ (1 + P.interest) * assetCap := + mul_le_mul_of_nonneg_left P.assetFloor_le_assetCap P.interest_gt_neg_one.le + simp only [maxConsumption] + linarith [P.minIncome_le_maxIncome, P.minConsumption_le_floor] + +theorem maxConsumption_pos : 0 < P.maxConsumption := + lt_of_lt_of_le P.minConsumption_pos P.minConsumption_le_maxConsumption + +theorem mem_dom_of_pos {c : ℝ} (hc : 0 < c) : c ∈ P.dom := P.Ioi_subset_dom hc + +theorem nonneg_of_mem_dom {c : ℝ} (hc : c ∈ P.dom) : 0 ≤ c := P.dom_subset_Ici hc + +theorem maxConsumption_mem_dom : P.maxConsumption ∈ P.dom := P.mem_dom_of_pos P.maxConsumption_pos + +theorem convex_dom : Convex ℝ P.dom := P.strictConcaveOn_u_dom.1 + +/-- The domain is an up-set: if utility behaves at `c` it behaves at anything larger. Both +`Ioi 0` and `Ici 0` are, and it follows from the two bracketing inclusions alone. -/ +theorem dom_upward {c c' : ℝ} (hc : c ∈ P.dom) (h : c ≤ c') : c' ∈ P.dom := by + rcases lt_or_ge 0 c' with h' | h' + · exact P.mem_dom_of_pos h' + · rwa [le_antisymm h (h'.trans (P.nonneg_of_mem_dom hc))] at hc + +/-- Utility behaves on the positives whatever the domain is, so every existing argument that +supplies `0 < c` keeps working. -/ +theorem continuousOn_u : ContinuousOn P.u (Ioi 0) := P.continuousOn_u_dom.mono P.Ioi_subset_dom + +theorem monotoneOn_u : MonotoneOn P.u (Ioi 0) := P.monotoneOn_u_dom.mono P.Ioi_subset_dom + +theorem strictConcaveOn_u : StrictConcaveOn ℝ (Ioi 0) P.u := + P.strictConcaveOn_u_dom.subset P.Ioi_subset_dom (convex_Ioi 0) + +theorem clampedConsumption_nonneg (p : (ℝ × Z) × ℝ) : 0 ≤ P.clampedConsumption p := + le_min P.maxConsumption_pos.le (le_max_left _ _) + +theorem clampedConsumption_le (p : (ℝ × Z) × ℝ) : + P.clampedConsumption p ≤ P.maxConsumption := min_le_left _ _ + +/-- Discreteness of `Z` is what makes this continuous. -/ +theorem continuous_resources : Continuous P.resources := + (continuous_of_discreteTopology.comp continuous_snd).add + (continuous_const.mul (continuous_const.max continuous_fst)) + +theorem continuous_maxSaving : Continuous P.maxSaving := + continuous_const.max (continuous_const.min P.continuous_resources) + +theorem continuous_clampedConsumption : Continuous P.clampedConsumption := by + refine continuous_const.min (continuous_const.max ?_) + exact (P.continuous_resources.comp continuous_fst).sub continuous_snd + +theorem continuous_rewardFn : Continuous P.rewardFn := + P.continuousOn_extendDom.comp_continuous P.continuous_clampedConsumption + fun p => mem_Ici.mpr (P.clampedConsumption_nonneg p) + +/-- The problem as a stochastic dynamic program with an extended-real reward. -/ +noncomputable def toExtended : ExtendedStochasticProgram (ℝ × Z) ℝ Z where + feasible s := Icc assetFloor (P.maxSaving s) + isCompact_feasible _ := isCompact_Icc + feasible_nonempty _ := nonempty_Icc.mpr (le_max_left _ _) + upperHemicontinuous_feasible := upperHemicontinuous_Icc continuous_const P.continuous_maxSaving + lowerHemicontinuous_feasible := + lowerHemicontinuous_Icc continuous_const P.continuous_maxSaving fun _ => le_max_left _ _ + reward := ⟨P.rewardFn, P.continuous_rewardFn⟩ + rewardMax := P.u P.maxConsumption + reward_le := by + intro s a _ + simp only [ContinuousMap.coe_mk, rewardFn] + by_cases h : P.clampedConsumption (s, a) ∈ P.dom + · rw [extendDom_of_mem h, EReal.coe_le_coe_iff] + exact P.monotoneOn_u_dom h P.maxConsumption_mem_dom (P.clampedConsumption_le _) + · rw [extendDom_of_not_mem h] + exact bot_le + select := ⟨fun _ => assetFloor, continuous_const⟩ + select_mem := fun _ => ⟨le_rfl, le_max_left _ _⟩ + rewardMin := P.u P.minConsumption + le_reward_select := by + intro s + simp only [ContinuousMap.coe_mk, rewardFn] + have h : P.minConsumption ≤ P.clampedConsumption (s, assetFloor) := by + refine le_min P.minConsumption_le_maxConsumption (le_max_of_le_right ?_) + simp only [consumption] + exact P.minConsumption_le_consumption_floor s + have hpos : 0 < P.clampedConsumption (s, assetFloor) := + lt_of_lt_of_le P.minConsumption_pos h + rw [extendDom_of_mem (P.mem_dom_of_pos hpos), EReal.coe_le_coe_iff] + exact P.monotoneOn_u_dom (P.mem_dom_of_pos P.minConsumption_pos) + (P.mem_dom_of_pos hpos) h + transition z' := ⟨fun p => (p.2, z'), continuous_snd.prodMk continuous_const⟩ + prob z' := ⟨fun p => P.transitionMatrix p.1.2 z', + (continuous_of_discreteTopology (f := fun z => P.transitionMatrix z z')).comp + (continuous_snd.comp continuous_fst)⟩ + prob_nonneg _ _ := P.transitionMatrix_nonneg _ _ + prob_sum p := P.transitionMatrix_sum _ + discount := P.discount + discount_lt_one := P.discount_lt_one + +@[simp] +theorem feasible_eq (s : ℝ × Z) : P.toExtended.feasible s = Icc assetFloor (P.maxSaving s) := rfl + +theorem maxSaving_le_resources (s : ℝ × Z) : P.maxSaving s ≤ P.resources s := + max_le (P.assetFloor_lt_resources s).le (min_le_right _ _) + +theorem consumption_nonneg {s : ℝ × Z} {a : ℝ} (ha : a ∈ P.toExtended.feasible s) : + 0 ≤ P.consumption s a := by + simp only [consumption] + linarith [le_trans ha.2 (P.maxSaving_le_resources s)] + +theorem consumption_le_maxConsumption {s : ℝ × Z} {a' : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (ha' : assetFloor ≤ a') : P.consumption s a' ≤ P.maxConsumption := by + have hmax : max assetFloor s.1 = s.1 := max_eq_right hs.1 + have : (1 + P.interest) * s.1 ≤ (1 + P.interest) * assetCap := + mul_le_mul_of_nonneg_left hs.2 P.interest_gt_neg_one.le + simp only [consumption, resources, maxConsumption, hmax] + linarith [P.le_maxIncome s.2] + +/-- **Utility is unbounded below**: the domain is the open half-line, so the reward is `⊥` at +zero consumption. This is the hypothesis under which positive consumption comes free. -/ +def Unbounded : Prop := P.dom = Ioi 0 + +theorem clampedConsumption_mem_dom_of_ne_bot {s : ℝ × Z} {a : ℝ} + (h : P.toExtended.reward (s, a) ≠ ⊥) : P.clampedConsumption (s, a) ∈ P.dom := + extendDom_ne_bot_iff.mp h + +theorem consumption_pos_of_ne_bot (hd : P.Unbounded) {s : ℝ × Z} {a : ℝ} + (h : P.toExtended.reward (s, a) ≠ ⊥) : 0 < P.consumption s a := by + have hmem := P.clampedConsumption_mem_dom_of_ne_bot h + rw [hd] at hmem + have : 0 < max 0 (P.consumption s a) := lt_of_lt_of_le hmem (min_le_right _ _) + rcases max_cases 0 (P.consumption s a) with ⟨he, _⟩ | ⟨he, _⟩ + · rw [he] at this; exact absurd this (lt_irrefl 0) + · rwa [he] at this + +/-- On the feasible set the clamps are both inactive, so the reward's argument is consumption +itself. -/ +theorem clampedConsumption_eq {s : ℝ × Z} {a : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (ha : a ∈ P.toExtended.feasible s) : P.clampedConsumption (s, a) = P.consumption s a := by + have h0 : 0 ≤ P.consumption s a := P.consumption_nonneg ha + simp only [clampedConsumption, max_eq_right h0, + min_eq_right (P.consumption_le_maxConsumption hs ha.1)] + +theorem consumption_mem_dom_of_ne_bot {s : ℝ × Z} {a : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (ha : a ∈ P.toExtended.feasible s) (h : P.toExtended.reward (s, a) ≠ ⊥) : + P.consumption s a ∈ P.dom := by + have := P.clampedConsumption_mem_dom_of_ne_bot h + rwa [P.clampedConsumption_eq hs ha] at this + +/-- **The reward is the utility of consumption**, wherever consumption lies in the domain on +which utility is required to behave. -/ +theorem reward_eq_coe_dom {s : ℝ × Z} {a : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (ha : a ∈ P.toExtended.feasible s) (hc : P.consumption s a ∈ P.dom) : + P.toExtended.reward (s, a) = ((P.u (P.consumption s a) : ℝ) : EReal) := + calc P.toExtended.reward (s, a) + = extendDom P.dom P.u (P.clampedConsumption (s, a)) := rfl + _ = extendDom P.dom P.u (P.consumption s a) := by rw [P.clampedConsumption_eq hs ha] + _ = _ := extendDom_of_mem hc + +theorem reward_eq_coe {s : ℝ × Z} {a : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (ha : a ∈ P.toExtended.feasible s) (hc : 0 < P.consumption s a) : + P.toExtended.reward (s, a) = ((P.u (P.consumption s a) : ℝ) : EReal) := + P.reward_eq_coe_dom hs ha (P.mem_dom_of_pos hc) + +/-- **The stochastic Bellman equation, with honest utility and positive consumption.** That +consumption is positive at the optimum is a consequence of the value being real, not a +separate development. -/ +theorem exists_optimal_saving {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + ∃ a' ∈ Icc assetFloor (P.maxSaving s), P.consumption s a' ∈ P.dom ∧ + P.toExtended.valueFunction s + = P.u (P.consumption s a') + + P.discount * ∑ z', P.transitionMatrix s.2 z' + * P.toExtended.valueFunction (a', z') := by + obtain ⟨a', ha', heq⟩ := P.toExtended.exists_optimal_policy s + have hne : P.rewardFn (s, a') ≠ ⊥ := by + intro hb + rw [show P.toExtended.reward (s, a') = P.rewardFn (s, a') from rfl, hb, + EReal.bot_add _] at heq + exact EReal.coe_ne_bot _ heq + have hc : P.consumption s a' ∈ P.dom := P.consumption_mem_dom_of_ne_bot hs ha' hne + refine ⟨a', ha', hc, ?_⟩ + rw [P.reward_eq_coe_dom hs ha' hc, ← EReal.coe_add, EReal.coe_eq_coe_iff] at heq + exact heq + +/-! ### Concavity and the optimal policy + +The state is a pair `(assets, income state)`, which is not a module over `ℝ`, so concavity +is stated along the asset slice for each income state -- see +`LeanEconomics.Blackwell.forall_concaveOn_valueFunction`. Everything else follows the +deterministic argument, with the continuation value now an expectation: it is concave in +next period's assets because each `V (·, z')` is, and the transition probabilities are +nonnegative. -/ + +/-- A convex combination stays above a common lower bound. Used wherever the borrowing limit +has to survive an averaging argument, which with a floor of zero was `add_nonneg`. -/ +theorem le_convex_comb {c x y θ φ : ℝ} (hx : c ≤ x) (hy : c ≤ y) (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) + (hθφ : θ + φ = 1) : c ≤ θ * x + φ * y := by + have key : θ * x + φ * y - c = θ * (x - c) + φ * (y - c) := by linear_combination c * hθφ + linarith [key, mul_nonneg hθ (sub_nonneg.mpr hx), mul_nonneg hφ (sub_nonneg.mpr hy)] + +theorem convex_comb_le {c x y θ φ : ℝ} (hx : x ≤ c) (hy : y ≤ c) (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) + (hθφ : θ + φ = 1) : θ * x + φ * y ≤ c := by + have key : c - (θ * x + φ * y) = θ * (c - x) + φ * (c - y) := by + linear_combination (-c) * hθφ + linarith [key, mul_nonneg hθ (sub_nonneg.mpr hx), mul_nonneg hφ (sub_nonneg.mpr hy)] + +theorem resources_eq_affine {s : ℝ × Z} (hs : assetFloor ≤ s.1) : + P.resources s = P.income s.2 + (1 + P.interest) * s.1 := by + simp only [resources, max_eq_right hs] + +theorem resources_affine_comb {x y θ φ : ℝ} {z : Z} (hx : assetFloor ≤ x) + (hy : assetFloor ≤ y) + (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) (hθφ : θ + φ = 1) : + P.resources (θ * x + φ * y, z) = θ * P.resources (x, z) + φ * P.resources (y, z) := by + have hmix : assetFloor ≤ θ * x + φ * y := le_convex_comb hx hy hθ hφ hθφ + rw [P.resources_eq_affine hmix, P.resources_eq_affine hx, P.resources_eq_affine hy] + linear_combination (-(P.income z)) * hθφ + +theorem maxSaving_le_assetCap (s : ℝ × Z) : P.maxSaving s ≤ assetCap := + max_le P.assetFloor_le_assetCap (min_le_left _ _) + +theorem feasible_subset_region {s : ℝ × Z} {a : ℝ} (ha : a ∈ P.toExtended.feasible s) : + a ∈ Icc assetFloor assetCap := + ⟨ha.1, ha.2.trans (P.maxSaving_le_assetCap s)⟩ + +theorem maxSaving_eq (s : ℝ × Z) : P.maxSaving s = min assetCap (P.resources s) := + max_eq_right (le_min P.assetFloor_le_assetCap (P.assetFloor_lt_resources s).le) + +theorem feasible_convex {x y : ℝ} {z : Z} (hx : x ∈ Icc assetFloor assetCap) + (hy : y ∈ Icc assetFloor assetCap) {ax ay θ φ : ℝ} + (hax : ax ∈ P.toExtended.feasible (x, z)) (hay : ay ∈ P.toExtended.feasible (y, z)) + (hθ : 0 ≤ θ) (hφ : 0 ≤ φ) (hθφ : θ + φ = 1) : + θ * ax + φ * ay ∈ P.toExtended.feasible (θ * x + φ * y, z) := by + obtain ⟨hax0, haxm⟩ := hax + obtain ⟨hay0, haym⟩ := hay + rw [P.maxSaving_eq (x, z)] at haxm + rw [P.maxSaving_eq (y, z)] at haym + refine ⟨le_convex_comb hax0 hay0 hθ hφ hθφ, ?_⟩ + rw [P.maxSaving_eq (θ * x + φ * y, z)] + refine le_min ?_ ?_ + · have h1 : ax ≤ assetCap := haxm.trans (min_le_left _ _) + have h2 : ay ≤ assetCap := haym.trans (min_le_left _ _) + exact convex_comb_le h1 h2 hθ hφ hθφ + · have h1 : ax ≤ P.resources (x, z) := haxm.trans (min_le_right _ _) + have h2 : ay ≤ P.resources (y, z) := haym.trans (min_le_right _ _) + rw [P.resources_affine_comb hx.1 hy.1 hθ hφ hθφ] + exact add_le_add (mul_le_mul_of_nonneg_left h1 hθ) (mul_le_mul_of_nonneg_left h2 hφ) + +theorem bellmanFn_eq_of_optimal {v : (ℝ × Z) →ᵇ ℝ} {s : ℝ × Z} {a : ℝ} + (hs : s.1 ∈ Icc assetFloor assetCap) (ha : a ∈ P.toExtended.feasible s) + (heq : P.toExtended.objectiveE v s a + = ((P.toExtended.bellmanFn v s : ℝ) : EReal)) : + P.consumption s a ∈ P.dom ∧ + P.toExtended.bellmanFn v s + = P.u (P.consumption s a) + P.discount * ∑ z', P.transitionMatrix s.2 z' * v (a, z') := by + have hne : P.toExtended.reward (s, a) ≠ ⊥ := by + intro hb + rw [ExtendedStochasticProgram.objectiveE, hb, EReal.bot_add _] at heq + exact EReal.coe_ne_bot _ heq.symm + have hc := P.consumption_mem_dom_of_ne_bot hs ha hne + refine ⟨hc, ?_⟩ + rw [ExtendedStochasticProgram.objectiveE, P.reward_eq_coe_dom hs ha hc, ← EReal.coe_add, + EReal.coe_eq_coe_iff] at heq + exact heq.symm + +/-- **The Bellman operator preserves concavity along each asset slice.** -/ +theorem concaveOn_bellman (v : (ℝ × Z) →ᵇ ℝ) + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) (z : Z) : + ConcaveOn ℝ (Icc assetFloor assetCap) fun a => (P.toExtended.bellman v) (a, z) := by + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + obtain ⟨ax, hax, heqx⟩ := P.toExtended.exists_optimal_action v (x, z) + obtain ⟨ay, hay, heqy⟩ := P.toExtended.exists_optimal_action v (y, z) + obtain ⟨hcx, hbx⟩ := P.bellmanFn_eq_of_optimal hx hax heqx + obtain ⟨hcy, hby⟩ := P.bellmanFn_eq_of_optimal hy hay heqy + have hxy : θ * x + φ * y ∈ Icc assetFloor assetCap := by + simpa using convex_Icc assetFloor assetCap hx hy hθ hφ hθφ + have hmix := P.feasible_convex hx hy hax hay hθ hφ hθφ + have hcmix : P.consumption (θ * x + φ * y, z) (θ * ax + φ * ay) + = θ * P.consumption (x, z) ax + φ * P.consumption (y, z) ay := by + simp only [consumption] + rw [P.resources_affine_comb hx.1 hy.1 hθ hφ hθφ] + ring + have hcm : P.consumption (θ * x + φ * y, z) (θ * ax + φ * ay) ∈ P.dom := by + rw [hcmix] + simpa only [smul_eq_mul] using P.convex_dom hcx hcy hθ hφ hθφ + have hle := P.toExtended.le_bellmanFn v hmix + have hobj : P.toExtended.objectiveE v (θ * x + φ * y, z) (θ * ax + φ * ay) + = ((P.u (P.consumption (θ * x + φ * y, z) (θ * ax + φ * ay)) + + P.discount * ∑ z', P.transitionMatrix z z' * v (θ * ax + φ * ay, z') : ℝ) : EReal) := by + rw [ExtendedStochasticProgram.objectiveE, P.reward_eq_coe_dom hxy hmix hcm, ← EReal.coe_add] + rfl + rw [hobj] at hle + have hle' : P.u (P.consumption (θ * x + φ * y, z) (θ * ax + φ * ay)) + + P.discount * ∑ z', P.transitionMatrix z z' * v (θ * ax + φ * ay, z') + ≤ P.toExtended.bellmanFn v (θ * x + φ * y, z) := by exact_mod_cast hle + -- utility is concave, and so is the expectation, termwise + have hu := P.strictConcaveOn_u_dom.concaveOn.2 hcx hcy hθ hφ hθφ + have hexp : θ * (∑ z', P.transitionMatrix z z' * v (ax, z')) + + φ * (∑ z', P.transitionMatrix z z' * v (ay, z')) + ≤ ∑ z', P.transitionMatrix z z' * v (θ * ax + φ * ay, z') := by + rw [Finset.mul_sum, Finset.mul_sum, ← Finset.sum_add_distrib] + refine Finset.sum_le_sum fun z' _ => ?_ + have hvz := (hv z').2 (P.feasible_subset_region hax) (P.feasible_subset_region hay) hθ hφ hθφ + simp only [smul_eq_mul] at hvz + nlinarith [P.transitionMatrix_nonneg z z'] + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hscaled := mul_le_mul_of_nonneg_left hexp hβ + simp only [smul_eq_mul] at hu + simp only [smul_eq_mul, ExtendedStochasticProgram.bellman_apply] + rw [hbx, hby] + rw [hcmix] at hle' + linarith + +/-- **The value function is concave in assets, for each income state.** -/ +theorem concaveOn_valueFunction (z : Z) : + ConcaveOn ℝ (Icc assetFloor assetCap) fun a => P.toExtended.valueFunction (a, z) := + Blackwell.forall_concaveOn_valueFunction (convex_Icc _ _) (fun (z : Z) (a : ℝ) => (a, z)) + P.toExtended.blackwell P.toExtended.discount_lt_one + (fun v hv => P.concaveOn_bellman v hv) z + +/-- **The optimal action is unique** for any continuation with concave slices. -/ +theorem optimal_action_unique_of_concave {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) + {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) {a₀ a₁ : ℝ} + (h₀ : a₀ ∈ P.toExtended.feasible s) (h₁ : a₁ ∈ P.toExtended.feasible s) + (hm₀ : P.toExtended.objectiveE v s a₀ = ((P.toExtended.bellmanFn v s : ℝ) : EReal)) + (hm₁ : P.toExtended.objectiveE v s a₁ = ((P.toExtended.bellmanFn v s : ℝ) : EReal)) : + a₀ = a₁ := by + by_contra hne + obtain ⟨hc₀, hb₀⟩ := P.bellmanFn_eq_of_optimal hs h₀ hm₀ + obtain ⟨hc₁, hb₁⟩ := P.bellmanFn_eq_of_optimal hs h₁ hm₁ + have hhalf : (0 : ℝ) < 1 / 2 := by norm_num + have hsum : (1 : ℝ) / 2 + 1 / 2 = 1 := by norm_num + have hmem : (1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁ ∈ P.toExtended.feasible s := by + rw [P.feasible_eq] at h₀ h₁ ⊢ + exact convex_Icc assetFloor (P.maxSaving s) h₀ h₁ hhalf.le hhalf.le hsum + have hcmid : P.consumption s ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + = (1 / 2 : ℝ) * P.consumption s a₀ + (1 / 2 : ℝ) * P.consumption s a₁ := by + simp only [consumption]; ring + have hcm : P.consumption s ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) ∈ P.dom := by + rw [hcmid] + simpa only [smul_eq_mul] using P.convex_dom hc₀ hc₁ hhalf.le hhalf.le hsum + have hcne : P.consumption s a₀ ≠ P.consumption s a₁ := by + simp only [consumption] + intro h + exact hne (by linarith) + have hu := P.strictConcaveOn_u_dom.2 hc₀ hc₁ hcne hhalf hhalf hsum + have hexp : (1 / 2 : ℝ) * (∑ z', P.transitionMatrix s.2 z' * v (a₀, z')) + + (1 / 2 : ℝ) * (∑ z', P.transitionMatrix s.2 z' * v (a₁, z')) + ≤ ∑ z', P.transitionMatrix s.2 z' * v ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁, z') := by + rw [Finset.mul_sum, Finset.mul_sum, ← Finset.sum_add_distrib] + refine Finset.sum_le_sum fun z' _ => ?_ + have hvz := (hv z').2 (P.feasible_subset_region h₀) (P.feasible_subset_region h₁) + hhalf.le hhalf.le hsum + simp only [smul_eq_mul] at hvz + nlinarith [P.transitionMatrix_nonneg s.2 z'] + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hscaled := mul_le_mul_of_nonneg_left hexp hβ + simp only [smul_eq_mul] at hu + have hle := P.toExtended.le_bellmanFn v hmem + have hobj : P.toExtended.objectiveE v s ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁) + = ((P.u (P.consumption s ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁)) + + P.discount * ∑ z', P.transitionMatrix s.2 z' + * v ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁, z') : ℝ) : EReal) := by + rw [ExtendedStochasticProgram.objectiveE, P.reward_eq_coe_dom hs hmem hcm, ← EReal.coe_add] + rfl + rw [hobj] at hle + have hle' : P.u (P.consumption s ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁)) + + P.discount * ∑ z', P.transitionMatrix s.2 z' + * v ((1 / 2 : ℝ) * a₀ + (1 / 2 : ℝ) * a₁, z') + ≤ P.toExtended.bellmanFn v s := by exact_mod_cast hle + rw [hcmid] at hle' + linarith + +/-- **The optimal action is unique.** -/ +theorem optimal_action_unique {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) {a₀ a₁ : ℝ} + (h₀ : a₀ ∈ P.toExtended.feasible s) (h₁ : a₁ ∈ P.toExtended.feasible s) + (hm₀ : P.toExtended.objectiveE P.toExtended.valueFunction s a₀ + = ((P.toExtended.bellmanFn P.toExtended.valueFunction s : ℝ) : EReal)) + (hm₁ : P.toExtended.objectiveE P.toExtended.valueFunction s a₁ + = ((P.toExtended.bellmanFn P.toExtended.valueFunction s : ℝ) : EReal)) : + a₀ = a₁ := by + exact P.optimal_action_unique_of_concave P.concaveOn_valueFunction hs h₀ h₁ hm₀ hm₁ + +/-- The optimal saving choice. -/ +noncomputable def policy (s : ℝ × Z) : ℝ := + Classical.choose (P.toExtended.exists_optimal_action P.toExtended.valueFunction s) + +theorem policy_mem (s : ℝ × Z) : P.policy s ∈ P.toExtended.feasible s := + (Classical.choose_spec + (P.toExtended.exists_optimal_action P.toExtended.valueFunction s)).1 + +theorem policy_optimal (s : ℝ × Z) : + P.toExtended.objectiveE P.toExtended.valueFunction s (P.policy s) + = ((P.toExtended.bellmanFn P.toExtended.valueFunction s : ℝ) : EReal) := + (Classical.choose_spec + (P.toExtended.exists_optimal_action P.toExtended.valueFunction s)).2 + +/-- The optimal saving choice under an arbitrary continuation. At the value function this is +definitionally `policy`. -/ +noncomputable def policyOf (v : (ℝ × Z) →ᵇ ℝ) (s : ℝ × Z) : ℝ := + Classical.choose (P.toExtended.exists_optimal_action v s) + +theorem policyOf_mem (v : (ℝ × Z) →ᵇ ℝ) (s : ℝ × Z) : + P.policyOf v s ∈ P.toExtended.feasible s := + (Classical.choose_spec (P.toExtended.exists_optimal_action v s)).1 + +theorem policyOf_optimal (v : (ℝ × Z) →ᵇ ℝ) (s : ℝ × Z) : + P.toExtended.objectiveE v s (P.policyOf v s) + = ((P.toExtended.bellmanFn v s : ℝ) : EReal) := + (Classical.choose_spec (P.toExtended.exists_optimal_action v s)).2 + +theorem policyOf_valueFunction : P.policyOf P.toExtended.valueFunction = P.policy := rfl + +theorem policy_mem_region (s : ℝ × Z) : P.policy s ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policy_mem s) + +theorem consumption_policy_mem_dom {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + P.consumption s (P.policy s) ∈ P.dom := + (P.bellmanFn_eq_of_optimal hs (P.policy_mem s) (P.policy_optimal s)).1 + +/-- **Consumption never vanishes at the optimum.** Automatic when utility is unbounded below +(`positiveConsumption_of_unbounded`); when it is bounded, it has to be earned from a marginal +Inada condition, which is what `ConsumptionFloor` does. -/ +def PositiveConsumption : Prop := + ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → 0 < P.consumption s (P.policy s) + +/-- **Utility really does diverge when the domain is the open half-line.** The structure no +longer carries `tendsto_atBot_u`, but it has not lost it: `continuousOn_extendDom` says the +extension is continuous at zero consumption, and with `dom = Ioi 0` the value there is `⊥`. -/ +theorem tendsto_atBot_u (hd : P.Unbounded) : Tendsto P.u (𝓝[>] 0) atBot := by + have hcont : ContinuousWithinAt (extendDom P.dom P.u) (Ici 0) 0 := + P.continuousOn_extendDom 0 (mem_Ici.mpr le_rfl) + have hbot : extendDom P.dom P.u 0 = ⊥ := extendDom_of_not_mem (by rw [hd]; simp) + rw [ContinuousWithinAt, hbot] at hcont + have h2 : Tendsto (extendDom P.dom P.u) (𝓝[>] (0 : ℝ)) (𝓝 ⊥) := + hcont.mono_left (nhdsWithin_mono _ Ioi_subset_Ici_self) + refine tendsto_atBot.2 fun R => ?_ + filter_upwards [h2 (Iio_mem_nhds (EReal.bot_lt_coe R)), self_mem_nhdsWithin] with c hc hcpos + simp only [mem_preimage, mem_Iio, extendDom_of_mem (P.mem_dom_of_pos hcpos), + EReal.coe_lt_coe_iff] at hc + exact hc.le + +theorem positiveConsumption_of_unbounded (hd : P.Unbounded) : P.PositiveConsumption := by + intro s hs + have := P.consumption_policy_mem_dom hs + rwa [hd] at this + +theorem consumption_policy_pos (hpc : P.PositiveConsumption) {s : ℝ × Z} + (hs : s.1 ∈ Icc assetFloor assetCap) : 0 < P.consumption s (P.policy s) := hpc s hs + +theorem eq_policy_of_optimal {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) {a : ℝ} + (ha : a ∈ P.toExtended.feasible s) + (hopt : P.toExtended.objectiveE P.toExtended.valueFunction s a + = ((P.toExtended.bellmanFn P.toExtended.valueFunction s : ℝ) : EReal)) : + a = P.policy s := + P.optimal_action_unique hs ha (P.policy_mem s) hopt (P.policy_optimal s) + +theorem argmax_eq_singleton {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + argmax (P.toExtended.objectiveE P.toExtended.valueFunction) P.toExtended.feasible s + = {P.policy s} := by + ext a + simp only [argmax, Set.mem_ofPred_eq, Set.mem_singleton_iff] + constructor + · rintro ⟨ha, hmax⟩ + refine P.eq_policy_of_optimal hs ha (le_antisymm (P.toExtended.le_bellmanFn _ ha) ?_) + rw [← P.policy_optimal s] + exact isMaxOn_iff.mp hmax _ (P.policy_mem s) + · rintro rfl + refine ⟨P.policy_mem s, isMaxOn_iff.mpr fun b hb => ?_⟩ + rw [P.policy_optimal s] + exact P.toExtended.le_bellmanFn _ hb + +/-- **The optimal policy is continuous** on the region of states with assets in +`[0, assetCap]`. -/ +theorem continuousOn_policy : + ContinuousOn P.policy {s : ℝ × Z | s.1 ∈ Icc assetFloor assetCap} := + continuousOn_of_upperHemicontinuous_singleton + (P.toExtended.upperHemicontinuous_argmax P.toExtended.valueFunction) + fun _ hs => P.argmax_eq_singleton hs + +/-- **The Bellman equation at the policy.** -/ +theorem valueFunction_eq_policy {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + P.toExtended.valueFunction s + = P.u (P.consumption s (P.policy s)) + + P.discount * ∑ z', P.transitionMatrix s.2 z' + * P.toExtended.valueFunction (P.policy s, z') := by + have h := (P.bellmanFn_eq_of_optimal hs (P.policy_mem s) (P.policy_optimal s)).2 + have hfix : P.toExtended.bellmanFn P.toExtended.valueFunction s + = P.toExtended.valueFunction s := by + conv_rhs => rw [← P.toExtended.bellman_valueFunction] + rfl + rw [← hfix, h] + +/-- A two-state calibration with `σ = 2`, where CES utility is `-1 / c`. -/ +noncomputable def calibrated : IncomeFluctuation (Fin 2) 0 10 where + income z := if z = 0 then 1 / 2 else 3 / 2 + transitionMatrix _ _ := 1 / 2 + interest := 1 / 20 + discount := 24 / 25 + u := fun c => -c⁻¹ + minIncome := 1 / 2 + maxIncome := 3 / 2 + minIncome_pos := by norm_num + minIncome_le z := by fin_cases z <;> norm_num + le_maxIncome z := by fin_cases z <;> norm_num + transitionMatrix_nonneg _ _ := by norm_num + transitionMatrix_sum _ := by simp + interest_gt_neg_one := by norm_num + assetFloor_le_assetCap := by norm_num + assetCap_nonneg := by norm_num + minConsumption_pos := by norm_num + minConsumption_le_floor := le_rfl + discount_lt_one := by norm_num + dom := Ioi 0 + Ioi_subset_dom := subset_rfl + dom_subset_Ici := Ioi_subset_Ici_self + continuousOn_u_dom := (continuousOn_id.inv₀ fun x hx => ne_of_gt hx).neg + monotoneOn_u_dom := by + intro x hx y _ hxy + have : (0 : ℝ) < x := hx + simp only [neg_le_neg_iff] + gcongr + strictConcaveOn_u_dom := strictConcaveOn_neg_inv + continuousOn_extendDom := + continuousOn_extendDom_Ioi ((continuousOn_id.inv₀ fun x hx => ne_of_gt hx).neg) + (tendsto_neg_atTop_atBot.comp tendsto_inv_nhdsGT_zero) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationAugmented.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationAugmented.lean new file mode 100644 index 000000000..78ae0b66c --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationAugmented.lean @@ -0,0 +1,596 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRate +public import LeanPool.AiyagariEquilibrium.Distribution.Stationary + +/-! +# The interest rate as a state variable + +Comparative statics in the interest rate can be done by a parametric argument -- prove the +value function continuous in the parameter, then the policy, then the distribution -- or by +putting the rate INTO the state and letting the existing machinery do the work. This file +takes the second route. + +The rate never changes, so the operator is block diagonal in it: the transition carries the +rate forward untouched. The `r`-slice of the augmented value function is therefore a fixed +point of the `r`-programme's own Bellman operator, hence equal to its value function; and +Berge, already proved in this library, gives continuity of the augmented policy in the whole +state, which includes the rate. + +Two design points. + +The rate coordinate is CLAMPED to `[rlo, rhi]`. Without it the reward has no bound above, +since maximum consumption grows with the rate. This is the same clamp-and-prove-inactive +device the model already uses for consumption, and `clampRate_eq` is the "inactive" half. + +The clamp inside the reward is STATE-DEPENDENT rather than a constant, even though a constant +would also bound it. That is what makes the slice at a rate in range *equal* to the +`r`-programme rather than merely close to it: every piece of data matches on the nose, so the +two Bellman operators are literally the same and uniqueness of the fixed point finishes. With +a constant clamp the two would differ off the region and the slice lemma would need the +region bridge instead of an equality. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction MeasureTheory + +namespace LeanEconomics + +/-- The rate, clamped to the interval under consideration. -/ +noncomputable def clampRate (rlo rhi r : ℝ) : ℝ := min rhi (max rlo r) + +theorem continuous_clampRate (rlo rhi : ℝ) : Continuous (clampRate rlo rhi) := + continuous_const.min (continuous_const.max continuous_id) + +theorem clampRate_mem {rlo rhi : ℝ} (hle : rlo ≤ rhi) (r : ℝ) : + clampRate rlo rhi r ∈ Icc rlo rhi := + ⟨le_min hle (le_max_left _ _), min_le_left _ _⟩ + +/-- On the interval, the clamp is inactive. -/ +theorem clampRate_eq {rlo rhi r : ℝ} (hr : r ∈ Icc rlo rhi) : clampRate rlo rhi r = r := by + rw [clampRate, max_eq_right hr.1, min_eq_right hr.2] + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +-- The augmented state space is built for a household whose borrowing limit is at zero: the +-- clamped rate moves the debt service, so a negative limit would have to be re-checked at every +-- rate in the interval. The general-floor machinery is upstream. +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) (rlo rhi : ℝ) + +/-- The augmented state: assets, the interest rate, and the income state. -/ +abbrev AugState (_P : IncomeFluctuation Z (0 : ℝ) assetCap) : Type _ := (ℝ × ℝ) × Z + +/-- Cash on hand when the interest rate is carried in the state and clamped to the interval. -/ +noncomputable def augResources (s : P.AugState) : ℝ := + P.income s.2 + (1 + clampRate rlo rhi s.1.2) * max 0 s.1.1 + +/-- The consumption ceiling for the rate carried by an augmented state. -/ +noncomputable def augMaxConsumption (s : P.AugState) : ℝ := + P.maxIncome + (1 + clampRate rlo rhi s.1.2) * assetCap + +/-- The largest feasible saving, respecting the zero borrowing limit and asset cap. -/ +noncomputable def augMaxSaving (s : P.AugState) : ℝ := + max 0 (min assetCap (P.augResources rlo rhi s)) + +/-- Consumption restricted to the domain on which the extended reward is defined. -/ +noncomputable def augClampedConsumption (p : P.AugState × ℝ) : ℝ := + min (P.augMaxConsumption rlo rhi p.1) (max 0 (P.augResources rlo rhi p.1 - p.2)) + +/-- The extended period utility used by the augmented Bellman operator. -/ +noncomputable def augRewardFn (p : P.AugState × ℝ) : EReal := + extendDom P.dom P.u (P.augClampedConsumption rlo rhi p) + +variable {rlo rhi} + +theorem one_add_clampRate_pos (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (r : ℝ) : + 0 < 1 + clampRate rlo rhi r := by + have := (clampRate_mem hle r).1 + linarith + +theorem minIncome_le_augResources (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (s : P.AugState) : + P.minIncome ≤ P.augResources rlo rhi s := by + have h1 : 0 ≤ (1 + clampRate rlo rhi s.1.2) * max 0 s.1.1 := + mul_nonneg (one_add_clampRate_pos hrlo hle _).le (le_max_left _ _) + have := P.minIncome_le s.2 + simp only [augResources]; linarith + +theorem augResources_pos (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (s : P.AugState) : + 0 < P.augResources rlo rhi s := + lt_of_lt_of_le P.minIncome_pos (P.minIncome_le_augResources hrlo hle s) + +theorem minIncome_le_augMaxConsumption (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (s : P.AugState) : + P.minIncome ≤ P.augMaxConsumption rlo rhi s := by + have h1 : 0 ≤ (1 + clampRate rlo rhi s.1.2) * assetCap := + mul_nonneg (one_add_clampRate_pos hrlo hle _).le P.assetCap_nonneg + simp only [augMaxConsumption]; linarith [P.minIncome_le_maxIncome] + +theorem augMaxConsumption_pos (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (s : P.AugState) : + 0 < P.augMaxConsumption rlo rhi s := + lt_of_lt_of_le P.minIncome_pos (P.minIncome_le_augMaxConsumption hrlo hle s) + +/-- The rate clamp is what bounds the reward above. -/ +theorem augMaxConsumption_le (hle : rlo ≤ rhi) (s : P.AugState) : + P.augMaxConsumption rlo rhi s ≤ P.maxIncome + (1 + rhi) * assetCap := by + have h1 : clampRate rlo rhi s.1.2 ≤ rhi := (clampRate_mem hle _).2 + have h2 := P.assetCap_nonneg + simp only [augMaxConsumption] + nlinarith + +variable (rlo rhi) + +theorem continuous_augResources : Continuous (P.augResources rlo rhi) := + (continuous_of_discreteTopology.comp continuous_snd).add + (((continuous_const.add + ((continuous_clampRate rlo rhi).comp (continuous_snd.comp continuous_fst)))).mul + (continuous_const.max (continuous_fst.comp continuous_fst))) + +theorem continuous_augMaxConsumption : Continuous (P.augMaxConsumption rlo rhi) := + continuous_const.add + (((continuous_const.add + ((continuous_clampRate rlo rhi).comp (continuous_snd.comp continuous_fst)))).mul + continuous_const) + +theorem continuous_augMaxSaving : Continuous (P.augMaxSaving rlo rhi) := + continuous_const.max (continuous_const.min (P.continuous_augResources rlo rhi)) + +theorem augClampedConsumption_nonneg (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) + (p : P.AugState × ℝ) : 0 ≤ P.augClampedConsumption rlo rhi p := + le_min (P.augMaxConsumption_pos hrlo hle p.1).le (le_max_left _ _) + +theorem augClampedConsumption_le (p : P.AugState × ℝ) : + P.augClampedConsumption rlo rhi p ≤ P.augMaxConsumption rlo rhi p.1 := min_le_left _ _ + +theorem continuous_augClampedConsumption : Continuous (P.augClampedConsumption rlo rhi) := + ((P.continuous_augMaxConsumption rlo rhi).comp continuous_fst).min + (continuous_const.max + (((P.continuous_augResources rlo rhi).comp continuous_fst).sub continuous_snd)) + +theorem continuous_augRewardFn (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) : + Continuous (P.augRewardFn rlo rhi) := + P.continuousOn_extendDom.comp_continuous (P.continuous_augClampedConsumption rlo rhi) + fun p => mem_Ici.mpr (P.augClampedConsumption_nonneg rlo rhi hrlo hle p) + +variable {rlo rhi} + +/-- **The augmented programme**: the interest rate carried along as a state that never +changes. -/ +noncomputable def toAugmented (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) : + ExtendedStochasticProgram P.AugState ℝ Z where + feasible s := Icc 0 (P.augMaxSaving rlo rhi s) + isCompact_feasible _ := isCompact_Icc + feasible_nonempty _ := nonempty_Icc.mpr (le_max_left _ _) + upperHemicontinuous_feasible := + upperHemicontinuous_Icc continuous_const (P.continuous_augMaxSaving rlo rhi) + lowerHemicontinuous_feasible := + lowerHemicontinuous_Icc continuous_const (P.continuous_augMaxSaving rlo rhi) + fun _ => le_max_left _ _ + reward := ⟨P.augRewardFn rlo rhi, P.continuous_augRewardFn rlo rhi hrlo hle⟩ + rewardMax := P.u (P.maxIncome + (1 + rhi) * assetCap) + reward_le := by + intro s a _ + simp only [ContinuousMap.coe_mk, augRewardFn] + have hcap : 0 < P.maxIncome + (1 + rhi) * assetCap := + lt_of_lt_of_le (P.augMaxConsumption_pos hrlo hle s) (P.augMaxConsumption_le hle s) + by_cases h : P.augClampedConsumption rlo rhi (s, a) ∈ P.dom + · rw [extendDom_of_mem h, EReal.coe_le_coe_iff] + exact P.monotoneOn_u_dom h (P.mem_dom_of_pos hcap) + (le_trans (P.augClampedConsumption_le rlo rhi (s, a)) (P.augMaxConsumption_le hle s)) + · rw [extendDom_of_not_mem h]; exact bot_le + select := ⟨fun _ => 0, continuous_const⟩ + select_mem := fun _ => ⟨le_rfl, le_max_left _ _⟩ + rewardMin := P.u P.minIncome + le_reward_select := by + intro s + simp only [ContinuousMap.coe_mk, augRewardFn] + have h : P.minIncome ≤ P.augClampedConsumption rlo rhi (s, 0) := by + refine le_min (P.minIncome_le_augMaxConsumption hrlo hle s) (le_max_of_le_right ?_) + rw [sub_zero]; exact P.minIncome_le_augResources hrlo hle s + have hpos : 0 < P.augClampedConsumption rlo rhi (s, 0) := + lt_of_lt_of_le P.minIncome_pos h + rw [extendDom_of_mem (P.mem_dom_of_pos hpos), EReal.coe_le_coe_iff] + exact P.monotoneOn_u_dom (P.mem_dom_of_pos P.minIncome_pos) (P.mem_dom_of_pos hpos) h + transition z' := ⟨fun p => ((p.2, p.1.1.2), z'), + (continuous_snd.prodMk ((continuous_snd.comp continuous_fst).comp continuous_fst)).prodMk + continuous_const⟩ + prob z' := ⟨fun p => P.transitionMatrix p.1.2 z', + (continuous_of_discreteTopology (f := fun z => P.transitionMatrix z z')).comp + (continuous_snd.comp continuous_fst)⟩ + prob_nonneg _ _ := P.transitionMatrix_nonneg _ _ + prob_sum p := P.transitionMatrix_sum _ + discount := P.discount + discount_lt_one := P.discount_lt_one + +/-! ### The slice + +At a rate inside the interval the clamp is inactive, so every piece of the augmented +programme's data agrees on the nose with the `r`-programme's. The two Bellman operators are +then literally equal on the slice, and uniqueness of the fixed point does the rest. -/ + +variable {r : ℝ} + +theorem augResources_slice (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) (t : ℝ × Z) : + P.augResources rlo rhi ((t.1, r), t.2) = (P.withRate r hrr).resources t := by + simp only [augResources, resources, clampRate_eq hr] + rfl + +theorem augMaxConsumption_slice (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) (t : ℝ × Z) : + P.augMaxConsumption rlo rhi ((t.1, r), t.2) = (P.withRate r hrr).maxConsumption := by + simp only [augMaxConsumption, maxConsumption, clampRate_eq hr] + norm_num + +theorem augMaxSaving_slice (hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) (t : ℝ × Z) : + P.augMaxSaving rlo rhi ((t.1, r), t.2) = (P.withRate r hrr).maxSaving t := by + simp only [augMaxSaving, maxSaving, P.augResources_slice hr hrr t] + +/-- The slice of a bounded continuous function on the augmented state. -/ +noncomputable def sliceAt (w : P.AugState →ᵇ ℝ) (r : ℝ) : (ℝ × Z) →ᵇ ℝ := + w.compContinuous ⟨fun t => ((t.1, r), t.2), + (continuous_fst.prodMk continuous_const).prodMk continuous_snd⟩ + +@[simp] +theorem sliceAt_apply (w : P.AugState →ᵇ ℝ) (r : ℝ) (t : ℝ × Z) : + P.sliceAt w r t = w ((t.1, r), t.2) := rfl + +/-- The feasible sets agree on the slice. -/ +theorem feasible_aug_eq (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) (t : ℝ × Z) : + (P.withRate r hrr).toExtended.feasible t + = (P.toAugmented hrlo hle).feasible ((t.1, r), t.2) := by + change Icc 0 ((P.withRate r hrr).maxSaving t) = Icc 0 (P.augMaxSaving rlo rhi ((t.1, r), t.2)) + rw [P.augMaxSaving_slice hr hrr t] + +/-- The objectives agree on the slice, for ANY continuation value. -/ +theorem objectiveE_aug_eq (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) (W : P.AugState →ᵇ ℝ) (t : ℝ × Z) (a : ℝ) : + (P.withRate r hrr).toExtended.objectiveE (P.sliceAt W r) t a + = (P.toAugmented hrlo hle).objectiveE W ((t.1, r), t.2) a := by + have hrew : (P.withRate r hrr).toExtended.reward (t, a) + = (P.toAugmented hrlo hle).reward (((t.1, r), t.2), a) := by + change extendDom (P.withRate r hrr).dom P.u + (min ((P.withRate r hrr).maxConsumption) + (max 0 ((P.withRate r hrr).resources t - a))) + = extendDom P.dom P.u (min (P.augMaxConsumption rlo rhi ((t.1, r), t.2)) + (max 0 (P.augResources rlo rhi ((t.1, r), t.2) - a))) + rw [P.augMaxConsumption_slice hr hrr t, P.augResources_slice hr hrr t] + rfl + have hexp : (P.withRate r hrr).toExtended.expect (P.sliceAt W r) (t, a) + = (P.toAugmented hrlo hle).expect W (((t.1, r), t.2), a) := by + simp only [ExtendedStochasticProgram.expect] + rfl + simp only [ExtendedStochasticProgram.objectiveE, hrew, hexp] + rfl + +/-- **The slice of the augmented value function is the value function at that rate.** -/ +theorem sliceAt_valueFunction (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) : + P.sliceAt (P.toAugmented hrlo hle).valueFunction r + = (P.withRate r hrr).toExtended.valueFunction := by + set W := (P.toAugmented hrlo hle).valueFunction with hW + set w := P.sliceAt W r with hw + -- the two programmes have the same feasible set and the same objective on the slice + have hfeas := P.feasible_aug_eq hrlo hle hr hrr + have hobj := fun (t : ℝ × Z) (a : ℝ) => P.objectiveE_aug_eq hrlo hle hr hrr W t a + -- so `w` is a fixed point of the `r`-operator + refine ((P.withRate r hrr).toExtended.eq_valueFunction ?_).symm ▸ rfl + refine BoundedContinuousFunction.ext fun t => ?_ + have hfix : W ((t.1, r), t.2) + = (P.toAugmented hrlo hle).bellmanFn W ((t.1, r), t.2) := by + rw [hW, ← (P.toAugmented hrlo hle).bellman_apply, + (P.toAugmented hrlo hle).bellman_valueFunction] + change (P.withRate r hrr).toExtended.bellmanFn w t = w t + rw [hw, sliceAt_apply, hfix] + simp only [ExtendedStochasticProgram.bellmanFn, ExtendedStochasticProgram.maxE, maxValueE] + rw [hfeas t] + exact congrArg EReal.toReal (congrArg sSup (Set.image_congr fun a _ => hobj t a)) + +/-! ### The transfer + +Uniqueness of the maximiser is IMPORTED from the slice rather than reproved: since the two +objectives and the two feasible sets coincide, the augmented argmax at `((a, r), z)` is the +`r`-programme's argmax at `(a, z)`, which is already known to be a singleton. So the whole +concavity and strict-concavity chain is reused rather than redone for the augmented state, +which is the reason this route was worth taking. -/ + +/-- An optimal saving choice for the augmented value function. -/ +noncomputable def augPolicy (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (x : P.AugState) : ℝ := + Classical.choose ((P.toAugmented hrlo hle).exists_optimal_action + (P.toAugmented hrlo hle).valueFunction x) + +theorem augPolicy_mem (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (x : P.AugState) : + P.augPolicy hrlo hle x ∈ (P.toAugmented hrlo hle).feasible x := + (Classical.choose_spec ((P.toAugmented hrlo hle).exists_optimal_action + (P.toAugmented hrlo hle).valueFunction x)).1 + +theorem augPolicy_optimal (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (x : P.AugState) : + (P.toAugmented hrlo hle).objectiveE (P.toAugmented hrlo hle).valueFunction x + (P.augPolicy hrlo hle x) + = (((P.toAugmented hrlo hle).bellmanFn (P.toAugmented hrlo hle).valueFunction x : ℝ) : + EReal) := + (Classical.choose_spec ((P.toAugmented hrlo hle).exists_optimal_action + (P.toAugmented hrlo hle).valueFunction x)).2 + +theorem augPolicy_mem_argmax (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (x : P.AugState) : + P.augPolicy hrlo hle x ∈ argmax ((P.toAugmented hrlo hle).objectiveE + (P.toAugmented hrlo hle).valueFunction) (P.toAugmented hrlo hle).feasible x := by + refine ⟨P.augPolicy_mem hrlo hle x, isMaxOn_iff.mpr fun b hb => ?_⟩ + rw [P.augPolicy_optimal hrlo hle x] + exact (P.toAugmented hrlo hle).le_bellmanFn _ hb + +/-- **The augmented argmax is the sliced programme's policy.** -/ +theorem argmax_aug_eq_singleton (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) {t : ℝ × Z} (ht : t.1 ∈ Icc (0 : ℝ) assetCap) : + argmax ((P.toAugmented hrlo hle).objectiveE (P.toAugmented hrlo hle).valueFunction) + (P.toAugmented hrlo hle).feasible ((t.1, r), t.2) + = {(P.withRate r hrr).policy t} := by + rw [← (P.withRate r hrr).argmax_eq_singleton ht] + have hobj : ∀ a, (P.withRate r hrr).toExtended.objectiveE + (P.withRate r hrr).toExtended.valueFunction t a + = (P.toAugmented hrlo hle).objectiveE (P.toAugmented hrlo hle).valueFunction + ((t.1, r), t.2) a := by + intro a + rw [← P.sliceAt_valueFunction hrlo hle hr hrr] + exact P.objectiveE_aug_eq hrlo hle hr hrr _ t a + have heq : (P.toAugmented hrlo hle).objectiveE (P.toAugmented hrlo hle).valueFunction + ((t.1, r), t.2) + = (P.withRate r hrr).toExtended.objectiveE + (P.withRate r hrr).toExtended.valueFunction t := (funext hobj).symm + have hfe := P.feasible_aug_eq hrlo hle hr hrr t + simp only [argmax, heq, ← hfe] + +theorem augPolicy_eq (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (hr : r ∈ Icc rlo rhi) + (hrr : P.RateOK r) {t : ℝ × Z} (ht : t.1 ∈ Icc (0 : ℝ) assetCap) : + P.augPolicy hrlo hle ((t.1, r), t.2) = (P.withRate r hrr).policy t := by + have hmem := P.augPolicy_mem_argmax hrlo hle ((t.1, r), t.2) + rw [P.argmax_aug_eq_singleton hrlo hle hr hrr ht] at hmem + exact hmem + +/-- **The policy is continuous in the state AND the interest rate, jointly.** -/ +theorem continuousOn_augPolicy (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) : + ContinuousOn (P.augPolicy hrlo hle) + {x : P.AugState | x.1.1 ∈ Icc (0 : ℝ) assetCap ∧ x.1.2 ∈ Icc rlo rhi} := by + refine continuousOn_of_upperHemicontinuous_singleton + ((P.toAugmented hrlo hle).upperHemicontinuous_argmax + (P.toAugmented hrlo hle).valueFunction) fun x hx => ?_ + have hrr : P.RateOK x.1.2 := rateOK_of_floor_zero (by have := hx.2.1; linarith) + have h := P.argmax_aug_eq_singleton hrlo hle hx.2 hrr (t := (x.1.1, x.2)) hx.1 + rw [h, P.augPolicy_eq hrlo hle hx.2 hrr (t := (x.1.1, x.2)) hx.1] + +/-! ### Uniform convergence of the policy + +Joint continuity plus compactness gives uniform continuity, and that is what resolves the +pointwise-versus-uniform difficulty recorded with `abs_markovFn_sub_le`: a parametric +argument would have produced continuity in `r` for each fixed state and then owed an +equicontinuity argument to make it uniform. Here uniformity is free, because a continuous +function on a compact set is uniformly continuous. + +As with the continuation value, `Z` carries no metric, so `(ℝ × ℝ) × Z` is not a metric space +and the modulus is taken in `(assets, rate)` for each income state, then combined over the +finitely many states. -/ + +/-- **The policy converges uniformly as the interest rate moves.** -/ +theorem exists_policy_modulus (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (z : Z), ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ r ∈ Icc rlo rhi, ∀ r' ∈ Icc rlo rhi, + |r - r'| < η → + |P.augPolicy hrlo hle ((a, r), z) - P.augPolicy hrlo hle ((a, r'), z)| < ε := by + classical + have hslice : ∀ z : Z, ∃ η > 0, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ r ∈ Icc rlo rhi, + ∀ r' ∈ Icc rlo rhi, |r - r'| < η → + |P.augPolicy hrlo hle ((a, r), z) - P.augPolicy hrlo hle ((a, r'), z)| < ε := by + intro z + set K : Set (ℝ × ℝ) := Icc (0 : ℝ) assetCap ×ˢ Icc rlo rhi with hK + have hKc : IsCompact K := isCompact_Icc.prod isCompact_Icc + have hmaps : MapsTo (fun p : ℝ × ℝ => ((p, z) : P.AugState)) K + {x : P.AugState | x.1.1 ∈ Icc (0 : ℝ) assetCap ∧ x.1.2 ∈ Icc rlo rhi} := + fun p hp => ⟨hp.1, hp.2⟩ + have hcont : ContinuousOn (fun p : ℝ × ℝ => P.augPolicy hrlo hle (p, z)) K := + (P.continuousOn_augPolicy hrlo hle).comp + (continuous_id.prodMk continuous_const).continuousOn hmaps + have huc := hKc.uniformContinuousOn_of_continuous hcont + obtain ⟨η, hη, hspec⟩ := Metric.uniformContinuousOn_iff.mp huc ε hε + refine ⟨η, hη, fun a ha r hr r' hr' hd => ?_⟩ + have hmem : ((a, r) : ℝ × ℝ) ∈ K := ⟨ha, hr⟩ + have hmem' : ((a, r') : ℝ × ℝ) ∈ K := ⟨ha, hr'⟩ + have hdist : dist ((a, r) : ℝ × ℝ) ((a, r') : ℝ × ℝ) < η := by + rw [Prod.dist_eq, dist_self, Real.dist_eq] + simpa using hd + have := hspec _ hmem _ hmem' hdist + rwa [Real.dist_eq] at this + choose η hη hspec using hslice + have hne : (Finset.univ : Finset Z).Nonempty := ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + refine ⟨Finset.univ.inf' hne η, (Finset.lt_inf'_iff _).2 fun z _ => hη z, ?_⟩ + exact fun z a ha r hr r' hr' hd => + hspec z a ha r hr r' hr' (lt_of_lt_of_le hd (Finset.inf'_le _ (Finset.mem_univ z))) + +/-- The same, stated for the policies of the sliced programmes. -/ +theorem exists_policy_modulus_withRate (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) {ε : ℝ} + (hε : 0 < ε) : + ∃ η > 0, ∀ (z : Z), ∀ a ∈ Icc (0 : ℝ) assetCap, + ∀ (r : ℝ) (_hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r), + ∀ (r' : ℝ) (_hr' : r' ∈ Icc rlo rhi) (hrr' : P.RateOK r'), + |r - r'| < η → + |(P.withRate r hrr).policy (a, z) - (P.withRate r' hrr').policy (a, z)| < ε := by + obtain ⟨η, hη, hspec⟩ := P.exists_policy_modulus hrlo hle hε + refine ⟨η, hη, fun z a ha r _hr hrr r' _hr' hrr' hd => ?_⟩ + rw [← P.augPolicy_eq hrlo hle _hr hrr (t := (a, z)) ha, + ← P.augPolicy_eq hrlo hle _hr' hrr' (t := (a, z)) ha] + exact hspec z a ha r _hr r' _hr' hd + +/-! ### The Markov operator converges uniformly + +Feeding the policy modulus through `abs_markovFn_sub_le`. The two successor states differ +only in their asset coordinate -- the income state is the same draw -- so a modulus for the +test function in that coordinate is all that is needed. -/ + +/-- A modulus for a test function in the asset coordinate, uniform over income states. -/ +theorem exists_modulus_state (h : P.State →ᵇ ℝ) {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (z' : Z) (x y : ↥(Icc (0 : ℝ) assetCap)), |(x : ℝ) - (y : ℝ)| < η → + |h (x, z') - h (y, z')| ≤ ε := by + classical + have : CompactSpace ↥(Icc (0 : ℝ) assetCap) := isCompact_iff_compactSpace.mp isCompact_Icc + have hslice : ∀ z' : Z, ∃ η > 0, ∀ x y : ↥(Icc (0 : ℝ) assetCap), + |(x : ℝ) - (y : ℝ)| < η → |h (x, z') - h (y, z')| ≤ ε := by + intro z' + have hcont : Continuous fun x : ↥(Icc (0 : ℝ) assetCap) => h (x, z') := + h.continuous.comp (continuous_id.prodMk continuous_const) + have huc : UniformContinuous fun x : ↥(Icc (0 : ℝ) assetCap) => h (x, z') := + CompactSpace.uniformContinuous_of_continuous hcont + obtain ⟨η, hη, hspec⟩ := Metric.uniformContinuous_iff.mp huc ε hε + refine ⟨η, hη, fun x y hxy => ?_⟩ + have hd : dist x y < η := by rw [Subtype.dist_eq, Real.dist_eq]; exact hxy + have := hspec hd + rw [Real.dist_eq] at this + exact this.le + choose η hη hspec using hslice + have hne : (Finset.univ : Finset Z).Nonempty := ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + refine ⟨Finset.univ.inf' hne η, (Finset.lt_inf'_iff _).2 fun z' _ => hη z', ?_⟩ + exact fun z' x y hxy => + hspec z' x y (lt_of_lt_of_le hxy (Finset.inf'_le _ (Finset.mem_univ z'))) + +/-- **The Markov operator converges uniformly as the interest rate moves.** This is the +statement the distribution's closed-graph argument consumes. -/ +theorem exists_markovFn_modulus (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) (h : P.State →ᵇ ℝ) + {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (r : ℝ) (_hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r) + (r' : ℝ) (_hr' : r' ∈ Icc rlo rhi) (hrr' : P.RateOK r'), + |r - r'| < η → ∀ s : P.State, + |(P.withRate r hrr).markovFn h s - (P.withRate r' hrr').markovFn h s| ≤ ε := by + obtain ⟨ηh, hηh, hh⟩ := P.exists_modulus_state h hε + obtain ⟨η, hη, hpol⟩ := P.exists_policy_modulus_withRate hrlo hle hηh + refine ⟨η, hη, fun r hr hrr r' hr' hrr' hd s => ?_⟩ + refine abs_markovFn_sub_le (A := P.withRate r hrr) (B := P.withRate r' hrr') rfl h + (fun s z' => ?_) s + -- the successor states differ only in the asset coordinate + have hmem : ((s.1 : ℝ)) ∈ Icc (0 : ℝ) assetCap := s.1.2 + have hgap := hpol s.2 (s.1 : ℝ) hmem r hr hrr r' hr' hrr' hd + exact hh z' _ _ hgap + +/-! ### The closed-graph estimate + +A distribution stationary for a NEARBY rate is almost stationary for the reference rate, with +an error controlled uniformly. This is the estimate the closed-graph argument runs on: the +error term below involves only the two fixed bounded continuous functions `markovOp h` and +`h`, so it survives passage to a weak limit, and uniqueness of the stationary distribution +then pins the limit down. -/ + +variable [MeasurableSpace Z] [BorelSpace Z] + +theorem exists_almost_stationary_modulus (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) + {r₀ : ℝ} (hr₀ : r₀ ∈ Icc rlo rhi) (hrr₀ : P.RateOK r₀) (h : P.State →ᵇ ℝ) + {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (r : ℝ) (_hr : r ∈ Icc rlo rhi) (hrr : P.RateOK r), |r - r₀| < η → + ∀ ν : ProbabilityMeasure P.State, (P.withRate r hrr).IsStationary ν → + |∫ s, (P.withRate r₀ hrr₀).markovOp h s ∂(ν : Measure P.State) + - ∫ s, h s ∂(ν : Measure P.State)| ≤ ε := by + obtain ⟨η, hη, hmod⟩ := P.exists_markovFn_modulus hrlo hle h hε + refine ⟨η, hη, fun r hr hrr hd ν hstat => ?_⟩ + set A := P.withRate r₀ hrr₀ with hA + set B := P.withRate r hrr with hB + -- stationarity at the nearby rate + have hpush : B.push (ν : Measure P.State) = (ν : Measure P.State) := + congrArg (fun x : ProbabilityMeasure P.State => (x : Measure P.State)) hstat + have hstat' : ∫ s, h s ∂(ν : Measure P.State) + = ∫ s, B.markovOp h s ∂(ν : Measure P.State) := by + have hp := B.integral_push (ν : Measure P.State) h + rwa [hpush] at hp + rw [hstat'] + -- the two operators are uniformly close + have hptwise : ∀ s : P.State, |A.markovOp h s - B.markovOp h s| ≤ ε := fun s => by + have hthis := hmod r₀ hr₀ hrr₀ r hr hrr (by rw [abs_sub_comm]; exact hd) s + rw [IncomeFluctuation.markovOp_apply, IncomeFluctuation.markovOp_apply] + exact hthis + have hnorm : ‖A.markovOp h - B.markovOp h‖ ≤ ε := + (BoundedContinuousFunction.norm_le hε.le).mpr fun s => by + rw [Real.norm_eq_abs] + simpa only [BoundedContinuousFunction.coe_sub, Pi.sub_apply] using hptwise s + -- so the integrals are close + have hsub : ∫ s, (A.markovOp h - B.markovOp h) s ∂(ν : Measure P.State) + = ∫ s, A.markovOp h s ∂(ν : Measure P.State) + - ∫ s, B.markovOp h s ∂(ν : Measure P.State) := by + simpa only [BoundedContinuousFunction.coe_sub, Pi.sub_apply] using + integral_sub ((A.markovOp h).integrable _) ((B.markovOp h).integrable _) + rw [← hsub] + exact le_trans (A.abs_integral_le _ _) hnorm + +/-! ### The stationary distribution moves continuously with the rate + +The cluster point step. Compactness of the space of probability measures supplies cluster +points; the estimate above makes any cluster point exactly stationary at the reference rate; +uniqueness there collapses it to a single point, and a filter with a unique cluster point in +a compact space converges. + +Uniqueness is needed only AT the reference rate. Existence at nearby rates is all that is +asked of them, and it is supplied by the selection `ν` rather than assumed of the model, so +the Doeblin hypotheses need not be carried across the whole interval. -/ + +theorem tendsto_stationary (hrlo : 0 < 1 + rlo) (hle : rlo ≤ rhi) {r₀ : ℝ} + (hr₀ : r₀ ∈ Icc rlo rhi) (hrr₀ : P.RateOK r₀) (ν : ℝ → ProbabilityMeasure P.State) + (hν : ∀ r ∈ Icc rlo rhi, ∀ hrr : P.RateOK r, (P.withRate r hrr).IsStationary (ν r)) + (huniq : ∀ μ : ProbabilityMeasure P.State, + (P.withRate r₀ hrr₀).IsStationary μ → μ = ν r₀) : + Tendsto ν (𝓝[Icc rlo rhi] r₀) (𝓝 (ν r₀)) := by + refine tendsto_nhds_of_unique_mapClusterPt fun μ hcl => ?_ + refine huniq μ ?_ + -- every cluster point is stationary at the reference rate + have key : ∀ h : P.State →ᵇ ℝ, + ∫ s, h s ∂(((P.withRate r₀ hrr₀).pushProb μ : ProbabilityMeasure P.State) : + Measure P.State) + = ∫ s, h s ∂(μ : Measure P.State) := by + intro h + set G : ProbabilityMeasure P.State → ℝ := fun ρ => + ∫ s, (P.withRate r₀ hrr₀).markovOp h s ∂(ρ : Measure P.State) + - ∫ s, h s ∂(ρ : Measure P.State) with hG + have hGc : Continuous G := + ((P.withRate r₀ hrr₀).continuous_integral_bcf _).sub + ((P.withRate r₀ hrr₀).continuous_integral_bcf h) + -- the estimate holds eventually along the filter + have hzero : G μ = 0 := by + have hbound : ∀ ε : ℝ, 0 < ε → |G μ| ≤ ε := by + intro ε hε + obtain ⟨η, hη, hest⟩ := + P.exists_almost_stationary_modulus hrlo hle hr₀ hrr₀ h hε + have hev : ∀ᶠ r in 𝓝[Icc rlo rhi] r₀, |G (ν r)| ≤ ε := by + have h1 : ∀ᶠ r in 𝓝[Icc rlo rhi] r₀, r ∈ Icc rlo rhi := self_mem_nhdsWithin + have h2 : ∀ᶠ r in 𝓝[Icc rlo rhi] r₀, |r - r₀| < η := by + refine nhdsWithin_le_nhds ?_ + filter_upwards [Metric.ball_mem_nhds r₀ hη] with x hx + rwa [Metric.mem_ball, Real.dist_eq] at hx + filter_upwards [h1, h2] with r hr1 hr2 + have hrr : P.RateOK r := rateOK_of_floor_zero (by have := hr1.1; linarith) + exact hest r hr1 hrr hr2 (ν r) (hν r hr1 hrr) + -- a cluster point inherits a closed condition + have hmap : ClusterPt (G μ) (Filter.map (fun r => G (ν r)) (𝓝[Icc rlo rhi] r₀)) := by + have hm := hcl.map hGc.continuousAt + (le_refl (Filter.map G (Filter.map ν (𝓝[Icc rlo rhi] r₀)))) + rwa [Filter.map_map] at hm + have hle' : Filter.map (fun r => G (ν r)) (𝓝[Icc rlo rhi] r₀) + ≤ Filter.principal {x : ℝ | |x| ≤ ε} := by + rw [Filter.le_principal_iff] + exact hev + have : G μ ∈ closure {x : ℝ | |x| ≤ ε} := + mem_closure_iff_clusterPt.mpr (hmap.mono hle') + rwa [IsClosed.closure_eq (isClosed_le (continuous_abs) continuous_const)] at this + have habs : |G μ| ≤ 0 := by + by_contra hcon + rw [not_le] at hcon + have := hbound (|G μ| / 2) (by linarith) + linarith + exact abs_eq_zero.mp (le_antisymm habs (abs_nonneg _)) + have hdual := (P.withRate r₀ hrr₀).integral_push (μ : Measure P.State) h + rw [(P.withRate r₀ hrr₀).coe_pushProb, hdual] + rw [hG] at hzero + linarith [hzero] + have hfin : ((P.withRate r₀ hrr₀).pushProb μ).toFiniteMeasure = μ.toFiniteMeasure := + FiniteMeasure.ext_of_forall_integral_eq key + exact ProbabilityMeasure.toMeasure_injective + (congrArg (fun x : FiniteMeasure P.State => (x : Measure P.State)) hfin) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationBounds.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationBounds.lean new file mode 100644 index 000000000..91ca0ff0b --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationBounds.lean @@ -0,0 +1,174 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation + +/-! +# Consumption at the optimum is bounded away from zero + +Positivity of optimal consumption is already known pointwise: a zero would make the reward +`⊥` and the value could not be real. What is needed for comparative statics is a UNIFORM +bound, a single `δ > 0` working at every state. + +The bound proved here is explicit rather than compactness-based, and that is the whole point. +A compactness argument would say that optimal consumption is continuous and positive on a +compact state space, hence has a positive minimum — true, and recorded below as a sanity +check, but useless for the parametric problem. There the bound has to be uniform over an +interval of interest rates as well, and the corresponding compactness argument would need +joint continuity in `(r, s)`, which is precisely what the bound is wanted to prove. It would +be circular. + +The explicit route avoids that. At the optimum the Bellman equation reads + + V s = u c* + β · E[V] + +so `u c* ≥ -(1 + β)‖V‖`, a lower bound on the UTILITY of optimal consumption depending only +on the sup norm of the value function. Since `u` falls to `-∞` at zero, that pins consumption +away from zero. Nothing in the argument refers to the state, so the bound is automatically +uniform in the state; and when `r` varies, `‖V‖` is controlled by the reward bounds, which are +themselves uniform over a compact interval of rates. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The expected continuation value is bounded by the sup norm of the value function. -/ +theorem abs_expectation_le (a : ℝ) (z : Z) : + |∑ z', P.transitionMatrix z z' * P.toExtended.valueFunction (a, z')| + ≤ ‖P.toExtended.valueFunction‖ := by + exact P.toExtended.abs_expect_le P.toExtended.valueFunction ((assetFloor, z), a) + +/-- **The utility of optimal consumption is bounded below**, by a quantity depending only on +the sup norm of the value function — not on the state. -/ +theorem le_utility_consumption_policy {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + -(1 + P.discount) * ‖P.toExtended.valueFunction‖ + ≤ P.u (P.consumption s (P.policy s)) := by + have hbell := P.valueFunction_eq_policy hs + have hexp := P.abs_expectation_le (P.policy s) s.2 + have hV : |P.toExtended.valueFunction s| ≤ ‖P.toExtended.valueFunction‖ := + P.toExtended.valueFunction.norm_coe_le_norm _ + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + rw [abs_le] at hexp hV + nlinarith [hexp.1, hexp.2, hV.1, hV.2, hbell] + +/-- **The utility bound in terms of the reward bounds alone** — no fixed point on the right. + +This is the form that survives into a parametric statement. A family of programs sharing a +utility function and a common bound on `max |u minConsumption| |u maxConsumption| / (1 - β)` has +a common `L`, and then `exists_lower_bound_of_utility_ge` hands it a single `δ`. -/ +theorem le_utility_consumption_policy_of_bounds {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + -(1 + P.discount) * (max |P.u P.minConsumption| |P.u P.maxConsumption| / (1 - P.discount)) + ≤ P.u (P.consumption s (P.policy s)) := by + refine le_trans ?_ (P.le_utility_consumption_policy hs) + have hnorm := P.toExtended.norm_valueFunction_le + have hmin : P.toExtended.rewardMin = P.u P.minConsumption := rfl + have hmax : P.toExtended.rewardMax = P.u P.maxConsumption := rfl + have hdisc : P.toExtended.discount = P.discount := rfl + rw [hmin, hmax, hdisc] at hnorm + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + nlinarith [hnorm] + +/-- **From a lower bound on utility to a lower bound on consumption.** The resulting `δ` +depends only on `u` and on `L` — NOT on the state, and not on anything that moves with the +interest rate. That is the property the parametric argument needs. -/ +theorem exists_lower_bound_of_utility_ge (hd : P.Unbounded) (L : ℝ) + (hL : ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → L ≤ P.u (P.consumption s (P.policy s))) : + ∃ δ > 0, ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → δ ≤ P.consumption s (P.policy s) := by + have hev : ∀ᶠ c in 𝓝[>] (0 : ℝ), P.u c < L - 1 := + P.tendsto_atBot_u hd (eventually_lt_atBot (L - 1)) + obtain ⟨ε, hε, hsub⟩ := (nhdsGT_basis (0 : ℝ)).eventually_iff.mp hev + refine ⟨ε, hε, fun s hs => ?_⟩ + by_contra hlt + rw [not_le] at hlt + have hpos : 0 < P.consumption s (P.policy s) := + P.consumption_policy_pos (P.positiveConsumption_of_unbounded hd) hs + have hbad : P.u (P.consumption s (P.policy s)) < L - 1 := hsub ⟨hpos, hlt⟩ + linarith [hL s hs] + +/-- **Consumption at the optimum is bounded away from zero, uniformly in the state.** + +No compactness and no continuity of the policy are used, which is exactly what lets the same +argument run uniformly in the interest rate: a compactness proof would need joint continuity +in `(r, s)`, which is what such a bound is wanted to prove in the first place. -/ +theorem exists_consumption_policy_lower_bound (hd : P.Unbounded) : + ∃ δ > 0, ∀ s : ℝ × Z, s.1 ∈ Icc assetFloor assetCap → δ ≤ P.consumption s (P.policy s) := + P.exists_lower_bound_of_utility_ge hd _ fun _ hs => + P.le_utility_consumption_policy_of_bounds hs + +/-! ### A choice set that does not move + +The Bellman operator maximises over `Icc 0 (maxSaving s)`, an interval whose RIGHT ENDPOINT +moves with the interest rate. Comparing the operator at two rates therefore means comparing +suprema over two different sets, which is the awkward part of any such estimate. + +Writing the choice as a fraction `θ ∈ [0,1]` of the maximum feasible saving removes the +problem: the choice set becomes `[0,1]` for every rate and every state, and all the rate +dependence moves into the objective, where it can be estimated pointwise. `|sSup f - sSup g|` +over a COMMON set is bounded by the pointwise gap; over two different sets it is not. -/ + +theorem assetFloor_le_maxSaving (s : ℝ × Z) : assetFloor ≤ P.maxSaving s := le_max_left _ _ + +/-- How much saving room the household has above the borrowing limit. -/ +noncomputable def savingRoom (s : ℝ × Z) : ℝ := P.maxSaving s - assetFloor + +theorem savingRoom_nonneg (s : ℝ × Z) : 0 ≤ P.savingRoom s := by + simp only [savingRoom]; linarith [P.assetFloor_le_maxSaving s] + +/-- The feasible set is the image of the FIXED interval `[0,1]` under the affine map that takes +`θ` to the borrowing limit plus a fraction `θ` of the saving room. With `assetFloor = 0` this is +just scaling by the maximum feasible saving. -/ +theorem feasible_eq_image (s : ℝ × Z) : + P.toExtended.feasible s = (fun θ => assetFloor + θ * P.savingRoom s) '' Icc 0 1 := by + have hcomp : (fun θ : ℝ => assetFloor + θ * P.savingRoom s) + = (fun y : ℝ => assetFloor + y) ∘ (fun θ : ℝ => θ * P.savingRoom s) := rfl + rw [P.feasible_eq, hcomp, Set.image_comp, + image_mul_right_Icc (by norm_num) (P.savingRoom_nonneg s), image_const_add_Icc] + simp only [zero_mul, one_mul, add_zero] + congr 1 + simp only [savingRoom]; ring + +/-- **The one-period value as a supremum over a fixed interval.** -/ +theorem maxE_eq_sSup_unit (v : (ℝ × Z) →ᵇ ℝ) (s : ℝ × Z) : + P.toExtended.maxE v s + = sSup ((fun θ => P.toExtended.objectiveE v s (assetFloor + θ * P.savingRoom s)) + '' Icc 0 1) := by + rw [ExtendedStochasticProgram.maxE, maxValueE, P.feasible_eq_image, Set.image_image] + +/-- **Consumption is bounded below by a quantity free of the state and the interest rate.** + +Along the reparametrised choice, consumption is at least `minConsumption * (1 - θ)`. The bound +holds because the saving room never exceeds what is left after servicing the debt, so taking a +fraction `θ` of it leaves at least a fraction `1 - θ`. + +This is the uniformity that makes the parametric estimate possible: it does not mention the +state, and the only model data it mentions -- `minConsumption` -- does not move with the interest +rate. Away from `θ = 1` the reward is therefore real and bounded, so the `-∞` cannot +interfere. -/ +theorem minConsumption_mul_le_consumption {θ : ℝ} (hθ : θ ∈ Icc (0 : ℝ) 1) (s : ℝ × Z) : + P.minConsumption * (1 - θ) ≤ P.consumption s (assetFloor + θ * P.savingRoom s) := by + have hms := P.maxSaving_le_resources s + have hroom := P.savingRoom_nonneg s + have hR := P.minConsumption_le_consumption_floor s + have hle : P.savingRoom s ≤ P.resources s - assetFloor := by + simp only [savingRoom]; linarith + have h1 : θ * P.savingRoom s ≤ θ * (P.resources s - assetFloor) := + mul_le_mul_of_nonneg_left hle hθ.1 + simp only [consumption] + nlinarith [hθ.1, hθ.2, hR, h1] + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCarrollKimball.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCarrollKimball.lean new file mode 100644 index 000000000..5702a656a --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCarrollKimball.lean @@ -0,0 +1,520 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationConsumption +public import LeanPool.AiyagariEquilibrium.Analysis.PowerMean + +/-! +# Concavity of the consumption function as a closed class + +`IncomeFluctuationConsumption` showed that concavity of the consumption function cannot follow +from concavity of `u` and of the value function, and that it is a statement about the FIXED +POINT. This file supplies the machinery that statements about the fixed point need, and reduces +Carroll and Kimball (1996) to its single inductive step. + +The pattern is the one used four times already — monotonicity, concavity, Lipschitz bounds and +increasing differences — but with one new difficulty. Those four are properties OF the value +function, closed in sup norm by `isClosed_concaveOn` and its relatives. Concavity of the +consumption function is a property of the value function's ARGMAX, and nothing says offhand +that an argmax survives a uniform limit. So the closure step has to be proved rather than +quoted, and that is what `tendsto_policyOf` does: along a uniformly convergent sequence of +continuation values the optimal actions converge, because a cluster point of the maximisers is +a maximiser of the limit and the maximiser of the limit is unique. + +That gives `concaveOn_consumptionFn_of_preserves`: if the Bellman operator carries a concave +consumption function to a concave consumption function, the fixed point's consumption function +is concave. The iteration starts from `0`, whose consumption function is `resources`, affine on +the asset region — `policyOf_zero`, an agent with no future eats everything. + +## What is proved, and what is left + +The hypothesis `hT` is the whole of Carroll and Kimball. Two thirds of it are now discharged. + +**The analysis.** On paper the argument runs through second derivatives: risk tolerances ADD +across the optimisation, `T_V(m) = T_u(c) + T_W(m - c)`, and Kimball's risk-tolerance +aggregation theorem propagates `T_V' ≥ T_u'` from the continuation to the value. `PowerMean` +supplies the integrated form of exactly that, with no derivative: for CRRA marginal utility the +consumption paired with a given stock of end-of-period assets is a positive multiple of the +weighted power mean, exponent `-γ`, of next period's consumptions, and a power mean with a +NEGATIVE exponent is concave and nondecreasing. Homogeneity turns that into the convexity of a +sublevel set, which is elementary. + +**The transfer.** Turning concavity of that map into concavity of the consumption function is +usually done by differentiating an inverse function. `concaveOn_of_egm` does it from the budget +identity alone: write the choice against end-of-period assets, and the two concavities are the +same statement. + +**The Euler equation** `hEuler` is proved in `IncomeFluctuationEuler`, and so is the step with +the BORROWING LIMIT allowed to bind. Saving more is feasible unless the household already saves +the maximum, so the inequality half of the first-order condition survives the corner; and at a +constrained state the saving is `assetFloor`, which lies below any average of savings, which is +the only thing `concaveOn_of_egm` ever needed. `concaveOn_of_egm_corner` and +`concaveOn_consumptionFnOf_bellman` are the results. + +**The induction's bookkeeping** is done in `IncomeFluctuationIterate`: positivity and +monotonicity of the consumption function, proved at the fixed point in this development, restated +for an arbitrary continuation, and a uniform bound on saving that covers every iterate at once. + +**So Carroll and Kimball is proved** -- `concaveOn_consumptionFn_of_marginal` for CRRA, +`concaveOn_consumptionFn_of_bounded_crra` for the bounded CES family `0 < γ < 1`, where positive +consumption comes from the marginal Inada condition run against an arbitrary continuation. + +One input is left explicit: a calibration inequality putting the asset cap above the bound on +saving. `crra_policyOf_le_mul` makes that bound the multiplicative one -- deviate to a FRACTION +of the saving and price the extra consumption where it lands -- so it involves the continuation's +OSCILLATION rather than its level, and the hypothesis is character for character the one +`crra_policy_lt_assetCap` carries. The witnesses that discharge that one discharge this one: +`nearLog_concaveOn_consumptionFn` is Carroll and Kimball for a concrete CES economy, at every +rate the equilibrium argument uses. + +**Toda's necessity result is uniform**, across arbitrary return and income laws under its +regularity hypotheses with borrowing permitted; see `Analysis.HARA`. It does not establish that `hT` +is false for an individual capped household outside HARA. This file proves sufficient results +for the stated CRRA families. + +## OWED: this is deferred, not abandoned + +The obstruction has moved four times. + +* Shifted CRRA was the first sufficient utility family; `Analysis.HARA` supplies this branch. +* The asset cap was the second, and worse, because it made the conclusion outright FALSE -- + `not_concaveOn_consumptionFn_of_cap_binds` below. `crra_policy_lt_assetCap` removes it by + calibration, and `nearLog` is recalibrated so that saving provably never reaches the cap. +* Differentiability was the third, and it is now GONE: `hasDerivAt_bellman` is the Clausen and + Strub sandwich run along the iteration, and `PowerMean` removes second derivatives altogether. +* The Euler equation was the fourth, and it is proved -- `euler_eq`, and without the envelope + theorem at that. +* The borrowing-constraint kink was the fifth, and `concaveOn_of_egm_corner` absorbs it. +* The induction's bookkeeping was the sixth, and `IncomeFluctuationIterate` does it. + +* Positive consumption for the bounded CES family was the seventh, and + `positiveConsumptionAll_of_marginalInada` does it. +* The cap calibration was the eighth, and `crra_policyOf_le_mul` makes it the one the witnesses + already prove. + +Nothing remains: `nearLog_concaveOn_consumptionFn` is the theorem at a concrete calibration. + +This file holds the analysis and the closure step; `IncomeFluctuationEuler` the first-order +condition and the corner; `IncomeFluctuationIterate` the bookkeeping and the theorem. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- Continuation values that are concave along each asset slice. This is what makes the optimal +action unique, and it is preserved by the Bellman operator. -/ +def ConcaveSlices (assetFloor assetCap : ℝ) (v : (ℝ × Z) →ᵇ ℝ) : Prop := + ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z) + +omit [Fintype Z] [Nonempty Z] [DiscreteTopology Z] in +theorem concaveSlices_zero : ConcaveSlices assetFloor assetCap (0 : (ℝ × Z) →ᵇ ℝ) := + fun _ => ⟨convex_Icc _ _, fun _ _ _ _ _ _ _ _ _ => by simp⟩ + +theorem concaveSlices_bellman {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices assetFloor assetCap v) : + ConcaveSlices assetFloor assetCap (P.toExtended.bellman v) := + fun z => P.concaveOn_bellman v hv z + +/-- Bellman iteration preserves concavity from any concave continuation. -/ +theorem concaveSlices_iterate_bellman {v : (ℝ × Z) →ᵇ ℝ} + (hv : ConcaveSlices assetFloor assetCap v) (n : ℕ) : + ConcaveSlices assetFloor assetCap ((P.toExtended.bellman)^[n] v) := by + induction n with + | zero => exact hv + | succ k ih => rw [Function.iterate_succ_apply']; exact P.concaveSlices_bellman ih + +theorem concaveSlices_valueFunction : + ConcaveSlices assetFloor assetCap P.toExtended.valueFunction := + P.concaveOn_valueFunction + +/-! ### The optimal action of an arbitrary continuation + +`IncomeFluctuation` proves uniqueness for every continuation whose asset slices are concave. +The closure argument applies that shared theorem along the whole approximating sequence; the +wrapper below gives its hypothesis the name `ConcaveSlices`. -/ + +/-- **The optimal action is unique** for any continuation with concave slices. -/ +theorem optimal_action_unique_of_concaveSlices {v : (ℝ × Z) →ᵇ ℝ} + (hv : ConcaveSlices assetFloor assetCap v) + {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) {a₀ a₁ : ℝ} + (h₀ : a₀ ∈ P.toExtended.feasible s) (h₁ : a₁ ∈ P.toExtended.feasible s) + (hm₀ : P.toExtended.objectiveE v s a₀ = ((P.toExtended.bellmanFn v s : ℝ) : EReal)) + (hm₁ : P.toExtended.objectiveE v s a₁ = ((P.toExtended.bellmanFn v s : ℝ) : EReal)) : + a₀ = a₁ := by + exact P.optimal_action_unique_of_concave hv hs h₀ h₁ hm₀ hm₁ + +/-! ### The agent with no future eats everything + +The iteration starts at the zero continuation, where saving buys nothing and the optimal plan +is to consume all cash on hand. Its consumption function is `resources`, affine on the asset +region, so the induction has a base. -/ + +/-- Saving more cannot raise the reward: consumption falls, and utility rises with it. -/ +theorem reward_antitone {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) {x y : ℝ} + (hx : x ∈ P.toExtended.feasible s) (hy : y ∈ P.toExtended.feasible s) (hxy : x ≤ y) : + P.toExtended.reward (s, y) ≤ P.toExtended.reward (s, x) := by + by_cases h : P.consumption s y ∈ P.dom + · have hcx : P.consumption s x ∈ P.dom := + P.dom_upward h (by simp only [consumption]; linarith) + rw [P.reward_eq_coe_dom hs hx hcx, P.reward_eq_coe_dom hs hy h, EReal.coe_le_coe_iff] + exact P.monotoneOn_u_dom h hcx (by simp only [consumption]; linarith) + · have hb : P.toExtended.reward (s, y) = ⊥ := by + by_contra hne + exact h (P.consumption_mem_dom_of_ne_bot hs hy hne) + rw [hb]; exact bot_le + +/-- **With nothing to gain from saving, the household saves nothing.** -/ +theorem policyOf_zero {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + P.policyOf 0 s = assetFloor := by + have hmem0 : assetFloor ∈ P.toExtended.feasible s := ⟨le_rfl, le_max_left _ _⟩ + have hc0 : 0 < P.consumption s assetFloor := by + have h1 := P.minConsumption_le_consumption_floor s + have h2 := P.minConsumption_pos + simp only [consumption]; linarith + have hobj : ∀ a : ℝ, P.toExtended.objectiveE (0 : (ℝ × Z) →ᵇ ℝ) s a + = P.toExtended.reward (s, a) := by + intro a + simp [ExtendedStochasticProgram.objectiveE, ExtendedStochasticProgram.expect] + have hle : ∀ a ∈ P.toExtended.feasible s, + P.toExtended.objectiveE (0 : (ℝ × Z) →ᵇ ℝ) s a + ≤ ((P.u (P.consumption s assetFloor) : ℝ) : EReal) := by + intro a ha + rw [hobj a, ← P.reward_eq_coe hs hmem0 hc0] + exact P.reward_antitone hs hmem0 ha ha.1 + have hb := P.toExtended.bellmanFn_le (0 : (ℝ × Z) →ᵇ ℝ) hle + have hopt : P.toExtended.objectiveE (0 : (ℝ × Z) →ᵇ ℝ) s assetFloor + = ((P.toExtended.bellmanFn (0 : (ℝ × Z) →ᵇ ℝ) s : ℝ) : EReal) := by + refine le_antisymm (P.toExtended.le_bellmanFn _ hmem0) ?_ + rw [hobj assetFloor, P.reward_eq_coe hs hmem0 hc0, EReal.coe_le_coe_iff] + exact hb + exact optimal_action_unique_of_concaveSlices P concaveSlices_zero hs (P.policyOf_mem _ _) hmem0 + (P.policyOf_optimal _ _) hopt + +theorem consumptionFnOf_zero {z : Z} {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + P.consumptionFnOf 0 z a = P.income z + (1 + P.interest) * a - assetFloor := by + rw [consumptionFnOf, P.policyOf_zero (s := (a, z)) ha] + simp only [consumption, resources, max_eq_right ha.1] + +theorem concaveOn_consumptionFnOf_zero (z : Z) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf 0 z) := by + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + have hm : θ • x + φ • y ∈ Icc assetFloor assetCap := convex_Icc _ _ hx hy hθ hφ hθφ + simp only [smul_eq_mul] at hm ⊢ + rw [P.consumptionFnOf_zero hx, P.consumptionFnOf_zero hy, P.consumptionFnOf_zero hm] + have hφ' : φ = 1 - θ := by linarith + subst hφ' + exact le_of_eq (by ring) + +/-! ### The closure step: optimal actions survive a uniform limit + +This is what the four earlier closed-class arguments got for free from `isClosed_concaveOn`. +Concavity of the consumption function is a property of the ARGMAX, so closure has to be proved: +a cluster point of maximisers of `vₙ` is a maximiser of the limit, and the maximiser of the +limit is unique. -/ + +theorem abs_expect_sub_le_norm (v w : (ℝ × Z) →ᵇ ℝ) (p : (ℝ × Z) × ℝ) : + |P.toExtended.expect v p - P.toExtended.expect w p| ≤ ‖v - w‖ := by + have h : P.toExtended.expect v p - P.toExtended.expect w p + = P.toExtended.expect (v - w) p := by + simp only [ExtendedStochasticProgram.expect, ← Finset.sum_sub_distrib, + BoundedContinuousFunction.coe_sub, Pi.sub_apply, mul_sub] + rw [h] + exact P.toExtended.abs_expect_le _ _ + +theorem tendsto_expect {v : ℕ → (ℝ × Z) →ᵇ ℝ} {w : (ℝ × Z) →ᵇ ℝ} (hv : Tendsto v atTop (𝓝 w)) + {x : ℕ → ℝ} {y : ℝ} (hx : Tendsto x atTop (𝓝 y)) (s : ℝ × Z) : + Tendsto (fun n => P.toExtended.expect (v n) (s, x n)) atTop + (𝓝 (P.toExtended.expect w (s, y))) := by + have hnorm : Tendsto (fun n => ‖v n - w‖) atTop (𝓝 0) := + tendsto_iff_norm_sub_tendsto_zero.mp hv + have hcont : Tendsto (fun n => P.toExtended.expect w (s, x n)) atTop + (𝓝 (P.toExtended.expect w (s, y))) := + ((P.toExtended.continuous_expect w).tendsto _).comp (tendsto_const_nhds.prodMk_nhds hx) + have hdiff : Tendsto (fun n => P.toExtended.expect (v n) (s, x n) + - P.toExtended.expect w (s, x n)) atTop (𝓝 0) := by + refine squeeze_zero_norm (fun n => ?_) hnorm + simpa only [Real.norm_eq_abs] using P.abs_expect_sub_le_norm (v n) w (s, x n) + simpa using hdiff.add hcont + +theorem tendsto_objectiveE {v : ℕ → (ℝ × Z) →ᵇ ℝ} {w : (ℝ × Z) →ᵇ ℝ} (hv : Tendsto v atTop (𝓝 w)) + {x : ℕ → ℝ} {y : ℝ} (hx : Tendsto x atTop (𝓝 y)) (s : ℝ × Z) : + Tendsto (fun n => P.toExtended.objectiveE (v n) s (x n)) atTop + (𝓝 (P.toExtended.objectiveE w s y)) := by + have hr : Tendsto (fun n => P.toExtended.reward (s, x n)) atTop + (𝓝 (P.toExtended.reward (s, y))) := + (P.toExtended.reward.continuous.tendsto _).comp (tendsto_const_nhds.prodMk_nhds hx) + have he : Tendsto (fun n => + ((P.toExtended.discount * P.toExtended.expect (v n) (s, x n) : ℝ) : EReal)) atTop + (𝓝 ((P.toExtended.discount * P.toExtended.expect w (s, y) : ℝ) : EReal)) := + (continuous_coe_real_ereal.tendsto _).comp + (tendsto_const_nhds.mul (P.tendsto_expect hv hx s)) + exact Filter.Tendsto.comp + (EReal.continuousAt_add (Or.inr (EReal.coe_ne_bot _)) (Or.inr (EReal.coe_ne_top _))) + (hr.prodMk_nhds he) + +/-- **Optimal actions survive a uniform limit.** -/ +theorem tendsto_policyOf {v : ℕ → (ℝ × Z) →ᵇ ℝ} {w : (ℝ × Z) →ᵇ ℝ} + (hv : Tendsto v atTop (𝓝 w)) (hw : ConcaveSlices assetFloor assetCap w) + {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + Tendsto (fun n => P.policyOf (v n) s) atTop (𝓝 (P.policyOf w s)) := by + refine tendsto_of_subseq_tendsto fun ns hns => ?_ + obtain ⟨L, hLK, φ, hφ, hφlim⟩ := (P.toExtended.isCompact_feasible s).tendsto_subseq + (x := fun i => P.policyOf (v (ns i)) s) (fun i => P.policyOf_mem _ _) + have hv' : Tendsto (fun i => v (ns (φ i))) atTop (𝓝 w) := + hv.comp (hns.comp hφ.tendsto_atTop) + have hlim' : Tendsto (fun i => P.policyOf (v (ns (φ i))) s) atTop (𝓝 L) := hφlim + -- every feasible action is beaten by the limit of the maximisers + have key : ∀ x ∈ P.toExtended.feasible s, + P.toExtended.objectiveE w s x ≤ P.toExtended.objectiveE w s L := by + intro x hx + refine le_of_tendsto_of_tendsto (P.tendsto_objectiveE hv' tendsto_const_nhds s) + (P.tendsto_objectiveE hv' hlim' s) (Eventually.of_forall fun i => ?_) + have hle := P.toExtended.le_bellmanFn (v (ns (φ i))) hx + rwa [← P.policyOf_optimal (v (ns (φ i))) s] at hle + have hLopt : P.toExtended.objectiveE w s L = ((P.toExtended.bellmanFn w s : ℝ) : EReal) := by + refine le_antisymm (P.toExtended.le_bellmanFn w hLK) ?_ + rw [← P.policyOf_optimal w s] + exact key _ (P.policyOf_mem w s) + have hLeq : L = P.policyOf w s := + optimal_action_unique_of_concaveSlices P hw hs hLK (P.policyOf_mem w s) hLopt + (P.policyOf_optimal w s) + exact ⟨φ, by rw [← hLeq]; exact hlim'⟩ + +/-- The limit step, split off from `concaveOn_consumptionFn_of_preserves`: concavity of the +consumption functions of the iterates passes to the fixed point, because optimal actions survive +a uniform limit. -/ +theorem concaveOn_consumptionFn_of_iterates + (hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z)) (z : Z) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + set T := P.toExtended.bellman with hTdef + have hlim : Tendsto (fun n => T^[n] (0 : (ℝ × Z) →ᵇ ℝ)) atTop + (𝓝 P.toExtended.valueFunction) := P.toExtended.tendsto_iterate_valueFunction 0 + have hpt : ∀ a ∈ Icc assetFloor assetCap, + Tendsto (fun n => P.consumptionFnOf (T^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) atTop + (𝓝 (P.consumptionFn z a)) := by + intro a ha + have hp := P.tendsto_policyOf hlim P.concaveSlices_valueFunction (s := (a, z)) ha + have he : P.consumptionFn z a + = P.resources (a, z) - P.policyOf P.toExtended.valueFunction (a, z) := rfl + rw [he] + simpa only [consumptionFnOf, consumption] using tendsto_const_nhds.sub hp + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + have hm : θ • x + φ • y ∈ Icc assetFloor assetCap := convex_Icc _ _ hx hy hθ hφ hθφ + refine le_of_tendsto_of_tendsto + (((hpt x hx).const_smul θ).add ((hpt y hy).const_smul φ)) (hpt _ hm) + (Eventually.of_forall fun n => (hcons n z).2 hx hy hθ hφ hθφ) + +/-! ### Carroll and Kimball, reduced to one step -/ + +/-- **The consumption function of the fixed point is concave whenever the Bellman operator +preserves concavity of the consumption function.** Everything except the hypothesis `hT` is +discharged: the base case, the induction, the limit. + +`hT` is Carroll and Kimball (1996) — see the module docstring for what proving it would take. -/ +theorem concaveOn_consumptionFn_of_preserves + (hT : ∀ v : (ℝ × Z) →ᵇ ℝ, ConcaveSlices assetFloor assetCap v → + (∀ z, ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z)) → + ∀ z, ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf (P.toExtended.bellman v) z)) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + set T := P.toExtended.bellman with hTdef + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap (T^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact concaveSlices_zero + | succ k ih => rw [Function.iterate_succ_apply']; exact P.concaveSlices_bellman ih + have hcons : ∀ n : ℕ, ∀ z, ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (T^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z) := by + intro n + induction n with + | zero => exact P.concaveOn_consumptionFnOf_zero + | succ k ih => rw [Function.iterate_succ_apply']; exact hT _ (hslices k) ih + exact P.concaveOn_consumptionFn_of_iterates hcons z + +/-! ### The endogenous-gridpoint transfer + +The step that carries concavity forward is not about the Bellman operator at all. Write the +household's choice against END-OF-PERIOD assets rather than against cash on hand: let `C A` be +the consumption it pairs with saving `A`, so that the budget reads `y + R a = A + C A`. The +graph of the consumption function is then the curve `A ↦ (A + C A, C A)`, and concavity of the +consumption function is exactly concavity of `C`. + +That equivalence is elementary — it is proved below with no derivative and no inverse function, +from the budget identity and the fact that `A + C A` is strictly increasing. What it buys is +that the hard step moves to `C`, where the first-order condition makes it a POWER MEAN of next +period's consumptions, and `Analysis/PowerMean` settles those. -/ + +/-- If `C` is nondecreasing then cash on hand is strictly increasing in end-of-period assets. -/ +theorem strictMonoOn_add_egm {S : Set ℝ} {C : ℝ → ℝ} (hC : MonotoneOn C S) : + StrictMonoOn (fun A => A + C A) S := fun _ ha _ hb hab => by + have := hC ha hb hab.le + simp only + linarith + +/-- **The endogenous-gridpoint transfer.** With the budget written against end-of-period assets, +concavity of the consumption function is concavity of `C`. Derivative-free. -/ +theorem concaveOn_of_egm {z : Z} {g C h : ℝ → ℝ} + (hC : ConcaveOn ℝ (Icc assetFloor assetCap) C) + (hM : StrictMonoOn (fun A => A + C A) (Icc assetFloor assetCap)) + (hg : ∀ a ∈ Icc assetFloor assetCap, g a ∈ Icc assetFloor assetCap) + (hbud : ∀ a ∈ Icc assetFloor assetCap, + P.income z + (1 + P.interest) * a = g a + C (g a)) + (hh : ∀ a ∈ Icc assetFloor assetCap, h a = C (g a)) : + ConcaveOn ℝ (Icc assetFloor assetCap) h := by + refine ⟨convex_Icc _ _, fun a ha b hb θ φ hθ hφ hθφ => ?_⟩ + simp only [smul_eq_mul] + have hm : θ * a + φ * b ∈ Icc assetFloor assetCap := by + simpa only [smul_eq_mul] using convex_Icc assetFloor assetCap ha hb hθ hφ hθφ + have hA₁ := hg a ha + have hA₂ := hg b hb + have hAθ := hg _ hm + have hAm : θ * g a + φ * g b ∈ Icc assetFloor assetCap := by + simpa only [smul_eq_mul] using convex_Icc assetFloor assetCap hA₁ hA₂ hθ hφ hθφ + have h1 := hbud a ha + have h2 := hbud b hb + have h3 := hbud _ hm + -- the budget at the midpoint, split two ways + have hcomb : P.income z + (1 + P.interest) * (θ * a + φ * b) + = (θ * g a + φ * g b) + (θ * C (g a) + φ * C (g b)) := by + linear_combination θ * h1 + φ * h2 - P.income z * hθφ + -- concavity of `C` puts the midpoint's saving below the average + have hCc : θ * C (g a) + φ * C (g b) ≤ C (θ * g a + φ * g b) := by + simpa only [smul_eq_mul] using hC.2 hA₁ hA₂ hθ hφ hθφ + have hle : g (θ * a + φ * b) + C (g (θ * a + φ * b)) + ≤ (θ * g a + φ * g b) + C (θ * g a + φ * g b) := by + rw [← h3, hcomb]; linarith + have hAle : g (θ * a + φ * b) ≤ θ * g a + φ * g b := by + by_contra hcon + exact absurd (hM hAm hAθ (not_le.mp hcon)) (not_lt.mpr hle) + -- and the budget converts that into the concavity of the consumption function + rw [hh a ha, hh b hb, hh _ hm] + have hval : C (g (θ * a + φ * b)) + = (θ * g a + φ * g b) + (θ * C (g a) + φ * C (g b)) - g (θ * a + φ * b) := by + rw [← hcomb, h3]; ring + rw [hval] + linarith + +/-! ### Carroll and Kimball for CRRA, from the first-order condition + +With CRRA marginal utility `u' c = c ^ (-γ)` the first-order condition says exactly that the +consumption paired with saving `A` is a positive multiple of the weighted power mean, exponent +`-γ`, of next period's consumptions at `A`. `Analysis/PowerMean` makes that map concave and +nondecreasing, and the transfer above does the rest. -/ + +/-- **The endogenous-gridpoint map for CRRA.** The consumption a household with continuation `v` +pairs with end-of-period assets `A`, read off the Euler equation. -/ +noncomputable def egmMap (v : (ℝ × Z) →ᵇ ℝ) (γ : ℝ) (z : Z) (A : ℝ) : ℝ := + ((P.discount : ℝ) * (1 + P.interest)) ^ (-(1 / γ)) + * powerMean (P.transitionMatrix z) (-γ) fun z' => P.consumptionFnOf v z' A + +theorem concaveOn_egmMap {γ : ℝ} (hγ : 0 < γ) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.egmMap v γ z) := by + have hmean := concaveOn_powerMean_comp (convex_Icc assetFloor assetCap) + (neg_neg_of_pos hγ) (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hpos hconc + have hk : (0 : ℝ) ≤ ((P.discount : ℝ) * (1 + P.interest)) ^ (-(1 / γ)) := + Real.rpow_nonneg (mul_nonneg P.discount.coe_nonneg P.interest_gt_neg_one.le) _ + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + have hstep := hmean.2 hx hy hθ hφ hθφ + simp only [smul_eq_mul] at hstep ⊢ + simp only [egmMap] + linarith [mul_le_mul_of_nonneg_left hstep hk] + +theorem monotoneOn_egmMap {γ : ℝ} (hγ : 0 < γ) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) : + MonotoneOn (P.egmMap v γ z) (Icc assetFloor assetCap) := by + have hmean := monotoneOn_powerMean_comp (D := Icc assetFloor assetCap) + (neg_neg_of_pos hγ) (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hpos hmono + intro x hx y hy hxy + exact mul_le_mul_of_nonneg_left (hmean hx hy hxy) + (Real.rpow_nonneg (mul_nonneg P.discount.coe_nonneg P.interest_gt_neg_one.le) _) + +/-- **Carroll and Kimball (1996), the induction step, for CRRA.** Given the Euler equation, the +Bellman operator carries a concave consumption function to a concave consumption function. + +Everything except `hEuler` is now proved: the power-mean concavity that Carroll and Kimball +obtain from Kimball's risk-tolerance aggregation, and the endogenous-gridpoint transfer that +turns it into concavity of the consumption function. `hEuler` is the first-order condition of +the maximisation, which holds wherever the choice is interior — see the module docstring. -/ +theorem concaveOn_consumptionFnOf_bellman_of_euler {γ : ℝ} (hγ : 0 < γ) {v : (ℝ × Z) →ᵇ ℝ} + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) (z : Z) + (hEuler : ∀ a ∈ Icc assetFloor assetCap, + P.consumptionFnOf (P.toExtended.bellman v) z a + = P.egmMap v γ z (P.policyOf (P.toExtended.bellman v) (a, z))) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_of_egm (z := z) (g := fun a => P.policyOf (P.toExtended.bellman v) (a, z)) + (C := P.egmMap v γ z) (P.concaveOn_egmMap hγ z hpos hconc) + (strictMonoOn_add_egm (P.monotoneOn_egmMap hγ z hpos hmono)) + (fun a _ => P.feasible_subset_region (P.policyOf_mem _ (a, z))) (fun a ha => ?_) + (fun a ha => hEuler a ha) + rw [← hEuler a ha] + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + simp only [consumptionFnOf, consumption, hres] + ring + +/-! ### The asset cap obstructs concavity, and `hT` is stated too strongly + +Attempting `hT` for log utility — the one CRRA specification this structure admits with Light's +condition, by `crra_pinch` — turns up an obstruction that has nothing to do with Carroll and +Kimball and everything to do with the cap. + +Where the cap binds the household cannot save more, so every extra unit of resources is consumed +and the consumption function has slope exactly `1 + r`. Below, wherever it saves strictly more, +the slope is strictly less. Increments therefore RISE across the kink, which concavity forbids. +`not_concaveOn_consumptionFn_of_cap_binds` proves it. + +So `hT` as stated is not merely hard, it is FALSE for any calibration whose cap binds, quite +apart from the HARA question. Concavity of the consumption function on the asset region needs the +cap to be SLACK — above the natural asset bound, so that the household never wants to save that +much. That is exactly what `exists_decline_of_consumption_lower_bound` is about, and by +`one_sub_mpc_mul_of_asymptotic` the condition behind it is `β (1 + r) < 1`. + +Which ties the two halves of the development together: the imposed `assetCap`, so far a +bookkeeping device for compactness, has to be justified economically before the Carroll–Kimball +hypothesis can even be true. -/ + +/-- Once the cap binds it binds for ever after, since the policy is monotone and capped. -/ +theorem policy_eq_assetCap_of_le {y w : ℝ} {z : Z} (hy : y ∈ Icc assetFloor assetCap) + (hw : w ∈ Icc assetFloor assetCap) (hyw : y ≤ w) (h : P.policy (y, z) = assetCap) : + P.policy (w, z) = assetCap := by + exact le_antisymm (P.policy_mem_region _).2 + (le_trans (le_of_eq h.symm) (P.policy_mono hy hw hyw)) + +/-- **A binding asset cap rules out a concave consumption function.** -/ +theorem not_concaveOn_consumptionFn_of_cap_binds {x y w : ℝ} {z : Z} + (hx : x ∈ Icc assetFloor assetCap) (hw : w ∈ Icc assetFloor assetCap) + (hxy : x < y) (hyw : y < w) + (hrise : P.policy (x, z) < P.policy (y, z)) + (hflat : P.policy (y, z) = P.policy (w, z)) : + ¬ ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + intro hconc + have hy : y ∈ Icc assetFloor assetCap := ⟨le_trans hx.1 hxy.le, le_trans hyw.le hw.2⟩ + have h := hconc.slope_anti_adjacent hx hw hxy hyw + have hres : ∀ a ∈ Icc assetFloor assetCap, + P.resources (a, z) = P.income z + (1 + P.interest) * a := + fun a ha => by simp only [resources, max_eq_right ha.1] + simp only [consumptionFn, consumption, hres x hx, hres y hy, hres w hw] at h + rw [div_le_div_iff₀ (by linarith) (by linarith)] at h + nlinarith [h, hrise, hflat, sub_pos.mpr hxy, sub_pos.mpr hyw] + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationConsumption.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationConsumption.lean new file mode 100644 index 000000000..ad7750603 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationConsumption.lean @@ -0,0 +1,286 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationMonotone +public import LeanPool.AiyagariEquilibrium.Analysis.RelativeRiskAversion + +/-! +# The consumption function + +Numbered Light references use the October 31, 2018 author draft, printed page 13: +https://www.researchgate.net/publication/314238962 + +`consumptionFn z a` is what the household actually eats at assets `a`: cash on hand less the +optimal saving. Light (2018) Theorem 1 needs three things from it — positivity, monotonicity, +and CONCAVITY in assets (his Lemma 5). The first two are proved here. The third is not, and +this file says exactly why. + +## Consumption rises with assets + +`consumptionFn_mono` is the theorem of this file, and it is the mirror image of `policy_mono`. +That one swapped the two households' savings and used increasing differences of `u`; this one +swaps them by the SAME resource gap, `b ↦ b + Δ` and `b' ↦ b' - Δ`, which leaves every +consumption level untouched and moves only the continuation. So the roles are exchanged: the +argument runs on increasing differences of the CONTINUATION, and it is again +`ConcaveOn.sub_le_sub_of_shift` — applied to `cont` rather than to `u`. + +Together the two say the marginal propensities are both non-negative: + + `0 ≤ c(a') - c(a) ≤ resources(a') - resources(a)` for `a ≤ a'`, + +which is `sub_le_sub_of_le_resources`. Neither half needs a derivative. + +## Concavity is NOT free, and this file does not prove it + +It would be pleasant if concavity of `c` followed from concavity of `u` and of the value +function, the way monotonicity does. It does not. With CRRA `u` and a continuation of constant +absolute risk tolerance, the marginal propensity to consume is `T_u(c) / (T_u(c) + T_W(b))` +with `T_u(c) = c/γ` and `T_W` constant, which RISES with consumption — so the consumption +function is CONVEX. Concavity therefore cannot follow from concavity of the two primitives; it +needs the continuation to inherit the curvature of `u`, which is what makes it a statement +about the FIXED POINT rather than about one application of the operator. + +That is the Carroll and Kimball (1996) theorem — the Bellman operator preserves concavity of +the consumption function when utility is HARA — and it is the shape of the closed-class +arguments already used here for monotonicity, concavity, Lipschitz bounds and increasing +differences. Light states it as his Lemma 5 and cites Jensen (2017) rather than proving it. +It is left as a hypothesis: `mul_marginal_le_of_scale` takes it as an argument, and everything +else that theorem needs about the consumption function is discharged here. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **The continuation value is concave in savings**, being a non-negative combination of the +value function's concave slices. -/ +theorem concaveOn_cont (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.cont z) := by + exact P.concaveOn_contOf P.concaveOn_valueFunction z + +/-- Consumption at the optimum, as a function of assets. -/ +noncomputable def consumptionFn (z : Z) (a : ℝ) : ℝ := + P.consumption (a, z) (P.policy (a, z)) + +/-- The consumption function implied by an arbitrary continuation. -/ +noncomputable def consumptionFnOf (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (a : ℝ) : ℝ := + P.consumption (a, z) (P.policyOf v (a, z)) + +theorem consumptionFnOf_valueFunction : + P.consumptionFnOf P.toExtended.valueFunction = P.consumptionFn := rfl + +/-- **Consumption at the optimum lies in the utility domain**, whatever the continuation. This +needs no positivity: the optimum's objective is a real number, so the reward is not `⊥`. -/ +theorem consumption_policyOf_mem_dom (v : (ℝ × Z) →ᵇ ℝ) {z : Z} {a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) : P.consumptionFnOf v z a ∈ P.dom := + (P.bellmanFn_eq_of_optimal (s := (a, z)) ha (P.policyOf_mem v (a, z)) + (P.policyOf_optimal v (a, z))).1 + +theorem consumptionFn_pos (hpc : P.PositiveConsumption) {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) + (z : Z) : + 0 < P.consumptionFn z a := + P.consumption_policy_pos hpc (s := (a, z)) ha + +/-- The saving cap cannot grow by more than resources do. -/ +theorem maxSaving_le_add_sub {a a' : ℝ} {z : Z} (hle : a ≤ a') : + P.maxSaving (a', z) + ≤ P.maxSaving (a, z) + (P.resources (a', z) - P.resources (a, z)) := by + have hΔ : 0 ≤ P.resources (a', z) - P.resources (a, z) := by + linarith [P.resources_mono (z := z) hle] + have hmin : min assetCap (P.resources (a', z)) + ≤ min assetCap (P.resources (a, z)) + (P.resources (a', z) - P.resources (a, z)) := by + rcases le_total assetCap (P.resources (a, z)) with h | h + · rw [min_eq_left h] + exact le_trans (min_le_left _ _) (by linarith) + · rw [min_eq_right h] + exact le_trans (min_le_right _ _) (by linarith) + rw [P.maxSaving_eq, P.maxSaving_eq] + linarith + +/-- **Consumption is ordered across states** against a continuation whose increments are +antitone in income. -/ +theorem consumptionFnOf_le_across_states {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) {z₁ z₂ : Z} + (hid : ∀ x y : ℝ, x ∈ Icc assetFloor assetCap → y ∈ Icc assetFloor assetCap → x ≤ y → + P.contOf v z₂ y - P.contOf v z₂ x ≤ P.contOf v z₁ y - P.contOf v z₁ x) + {a a' : ℝ} (ha : a ∈ Icc assetFloor assetCap) (ha' : a' ∈ Icc assetFloor assetCap) + (hres : P.resources (a, z₁) ≤ P.resources (a', z₂)) : + P.consumptionFnOf v z₁ a ≤ P.consumptionFnOf v z₂ a' := by + set Δ : ℝ := P.resources (a', z₂) - P.resources (a, z₁) with hΔdef + have hΔ : 0 ≤ Δ := by rw [hΔdef]; linarith + set b : ℝ := P.policyOf v (a, z₁) with hbdef + set b' : ℝ := P.policyOf v (a', z₂) with hb'def + by_contra hcon + rw [not_le] at hcon + have hgap : b + Δ < b' := by + simp only [consumptionFnOf, consumption] at hcon + rw [hΔdef]; linarith + have hbmem : b ∈ P.toExtended.feasible (a, z₁) := P.policyOf_mem v (a, z₁) + have hb'mem : b' ∈ P.toExtended.feasible (a', z₂) := P.policyOf_mem v (a', z₂) + rw [P.feasible_eq] at hbmem hb'mem + have hms : P.maxSaving (a', z₂) ≤ P.maxSaving (a, z₁) + Δ := by + have hmin : min assetCap (P.resources (a', z₂)) + ≤ min assetCap (P.resources (a, z₁)) + Δ := by + rcases le_total assetCap (P.resources (a, z₁)) with h | h + · rw [min_eq_left h] + exact le_trans (min_le_left _ _) (by linarith) + · rw [min_eq_right h] + exact le_trans (min_le_right _ _) (by rw [hΔdef]; linarith) + rw [P.maxSaving_eq, P.maxSaving_eq] + linarith + have hshiftup : b + Δ ∈ P.toExtended.feasible (a', z₂) := by + rw [P.feasible_eq] + exact ⟨by linarith [hbmem.1], by linarith [hb'mem.2]⟩ + have hshiftdown : b' - Δ ∈ P.toExtended.feasible (a, z₁) := by + rw [P.feasible_eq] + exact ⟨by linarith [hbmem.1], by linarith [hb'mem.2]⟩ + have hc1 : P.consumption (a, z₁) b ∈ P.dom := P.consumption_policyOf_mem_dom v ha + have hc2 : P.consumption (a', z₂) b' ∈ P.dom := P.consumption_policyOf_mem_dom v ha' + have he1 : P.consumption (a', z₂) (b + Δ) = P.consumption (a, z₁) b := by + simp only [consumption, hΔdef]; ring + have he2 : P.consumption (a, z₁) (b' - Δ) = P.consumption (a', z₂) b' := by + simp only [consumption, hΔdef]; ring + have hI := P.objROf_le_of_mem v ha hshiftdown (by rw [he2]; exact hc2) + have hII := P.objROf_le_of_mem v ha' hshiftup (by rw [he1]; exact hc1) + have hbreg : b ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policyOf_mem v _) + have hb'reg : b' ∈ Icc assetFloor assetCap := P.feasible_subset_region (P.policyOf_mem v _) + -- concavity of the poorer continuation, shifted by `Δ` + have hshift := (P.concaveOn_contOf hv z₁).sub_le_sub_of_shift (c₁ := b) (c₂ := b' - Δ) (Δ := Δ) + hbreg (by simpa using hb'reg) (by linarith) hΔ + rw [sub_add_cancel] at hshift + -- and the richer continuation is flatter still + have hacross := hid (b + Δ) b' ⟨by linarith [hbreg.1], by linarith [hb'reg.2]⟩ hb'reg hgap.le + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hchain := mul_le_mul_of_nonneg_left (le_trans hacross hshift) hβ + simp only [objROf, he1, he2] at hI hII + have hIIeq : P.objROf v (a', z₂) (b + Δ) = P.objROf v (a', z₂) b' := by + simp only [objROf, he1] + linarith [hI, hII, hchain] + have hbell : P.toExtended.objectiveE v (a', z₂) (b + Δ) + = ((P.toExtended.bellmanFn v (a', z₂) : ℝ) : EReal) := by + rw [P.objectiveE_eq_coe_of v ha' hshiftup (by rw [he1]; exact hc1), hIIeq, + ← P.objectiveE_eq_coe_of v ha' (P.policyOf_mem v _) hc2] + exact P.policyOf_optimal v _ + exact absurd (P.optimal_action_unique_of_concave hv ha' hshiftup (P.policyOf_mem v _) + hbell (P.policyOf_optimal v _)) (by rw [← hb'def]; linarith) + +/-- **Consumption rises with assets, whatever the continuation.** The exchange argument of +`consumptionFn_mono`, run against `v`. -/ +theorem consumptionFnOf_mono {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) {a a' : ℝ} {z : Z} + (ha : a ∈ Icc assetFloor assetCap) (ha' : a' ∈ Icc assetFloor assetCap) (hle : a ≤ a') : + P.consumptionFnOf v z a ≤ P.consumptionFnOf v z a' := by + exact P.consumptionFnOf_le_across_states hv (fun _ _ _ _ _ => le_rfl) ha ha' + (P.resources_mono hle) + +/-- **Consumption rises with assets.** The two households swap savings plans shifted by their +resource gap, which leaves consumption alone and moves only the continuation — so the +comparison is increasing differences of the continuation, exactly as `policy_mono` is +increasing differences of `u`. -/ +theorem consumptionFn_mono {a a' : ℝ} {z : Z} + (ha : a ∈ Icc assetFloor assetCap) + (ha' : a' ∈ Icc assetFloor assetCap) (hle : a ≤ a') : + P.consumptionFn z a ≤ P.consumptionFn z a' := by + simpa only [P.consumptionFnOf_valueFunction] using + P.consumptionFnOf_mono P.concaveOn_valueFunction ha ha' hle + +/-- **The marginal propensities are both non-negative.** Consumption rises with assets, and by +no more than resources do — the second half is `policy_mono`. -/ +theorem sub_le_sub_of_le_resources {a a' : ℝ} {z : Z} + (ha : a ∈ Icc assetFloor assetCap) + (ha' : a' ∈ Icc assetFloor assetCap) (hle : a ≤ a') : + 0 ≤ P.consumptionFn z a' - P.consumptionFn z a ∧ + P.consumptionFn z a' - P.consumptionFn z a + ≤ P.resources (a', z) - P.resources (a, z) := by + refine ⟨by linarith [P.consumptionFn_mono (z := z) ha ha' hle], ?_⟩ + have := P.policy_mono (z := z) ha ha' hle + simp only [consumptionFn, consumption] + linarith + +theorem monotoneOn_consumptionFn (z : Z) : + MonotoneOn (P.consumptionFn z) (Icc assetFloor assetCap) := + fun _ ha _ ha' h => P.consumptionFn_mono ha ha' h + +/-! ### A linear lower bound on consumption makes assets decline + +Ma and Toda study asymptotic linearity in an UNCAPPED savings problem. Corollary 2.10 of their +[March 8, 2021 draft](https://qingyin-ma.github.io/papers/AL_10_arxiv.pdf) gives the following +asymptotic marginal propensity under its standing assumptions, bounded relative risk aversion, +constant discount and return, and `R ≥ 1`. The standing infinite-horizon assumptions include +`β R < 1` and regular variation of marginal utility with index `-γ`, where `γ > 0`: + + `c_bar = 1 - (β R^(1-γ))^(1/γ)`. + +No identification of that uncapped problem with this capped household is asserted. What is +proved here is a conditional implication: a linear lower bound on consumption forces assets +to fall above an explicit threshold. That is precisely the hypothesis `hdecl` of +`exists_exhaust_of_decline`, on which the whole Doeblin uniqueness and convergence argument +rests, and which has been a raw assumption until now. + +For the displayed candidate slope, the algebraic growth factor is +`(1 - c_bar) R = (β R)^(1/γ)` — `one_sub_mpc_mul_of_asymptotic`, below — so the condition +`(1 - ε) (1 + r) < 1` demanded by `policy_lt_self_of_consumption_lower_bound` is exactly `β R < 1`. +The lower-bound premise remains explicit; the identity alone supplies no asset bound. -/ + +/-- **The asymptotic growth factor of assets is `(β R)^(1/γ)`.** With Ma and Toda's asymptotic +MPC `c_bar = 1 - (β R^(1-γ))^(1/γ)`, the factor `(1 - c_bar) R` by which assets grow collapses to +`(β R)^(1/γ)`, so assets are eventually contracted exactly when `β R < 1`. -/ +theorem one_sub_mpc_mul_of_asymptotic {β R γ : ℝ} (hβ : 0 ≤ β) (hR : 0 < R) (hγ : 0 < γ) : + (β * R ^ (1 - γ)) ^ (1 / γ) * R = (β * R) ^ (1 / γ) := by + have h1 : (R ^ (1 - γ)) ^ (1 / γ) * R = R ^ (1 / γ) := by + have hadd : R ^ ((1 - γ) * (1 / γ)) * R ^ (1 : ℝ) = R ^ ((1 - γ) * (1 / γ) + 1) := + (Real.rpow_add hR _ _).symm + rw [Real.rpow_one] at hadd + rw [← Real.rpow_mul hR.le, hadd, show (1 - γ) * (1 / γ) + 1 = 1 / γ by field_simp; ring] + rw [Real.mul_rpow hβ (Real.rpow_nonneg hR.le _), mul_assoc, h1, ← Real.mul_rpow hβ hR.le] + +/-- **A household that consumes a fixed share of cash on hand runs its assets down**, once +assets pass an explicit threshold. The share `ε` has to be large enough that `(1 - ε)(1 + r) < 1` +— with Ma and Toda's asymptotic MPC that is exactly `β(1 + r) < 1`. -/ +theorem policy_lt_self_of_consumption_lower_bound {z : Z} {ε a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hlb : ε * (P.resources (a, z) - assetFloor) ≤ P.consumptionFn z a) + (hgt : (1 - ε) * P.income z + ε * assetFloor + < (1 - (1 - ε) * (1 + P.interest)) * a) : + P.policy (a, z) < a := by + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + have hpol : P.policy (a, z) + = P.income z + (1 + P.interest) * a - P.consumptionFn z a := by + simp only [consumptionFn, consumption, hres]; ring + rw [hres] at hlb + rw [hpol] + have e1 : P.income z + (1 + P.interest) * a - P.consumptionFn z a + ≤ (1 - ε) * P.income z + (1 - ε) * ((1 + P.interest) * a) + ε * assetFloor := by + nlinarith [hlb] + rw [show (1 - (1 - ε) * (1 + P.interest)) * a + = a - (1 - ε) * ((1 + P.interest) * a) by ring] at hgt + linarith + +/-- **Every asset level above an explicit threshold declines**, given a linear lower bound on +consumption. This is the hypothesis `hdecl` of `exists_exhaust_of_decline`. -/ +theorem exists_decline_of_consumption_lower_bound {z : Z} {ε : ℝ} + (hε : (1 - ε) * (1 + P.interest) < 1) + (hlb : ∀ a ∈ Icc assetFloor assetCap, + ε * (P.resources (a, z) - assetFloor) ≤ P.consumptionFn z a) : + ∃ ā : ℝ, ∀ a ∈ Icc assetFloor assetCap, ā < a → P.policy (a, z) < a := by + refine ⟨((1 - ε) * P.income z + ε * assetFloor) + / (1 - (1 - ε) * (1 + P.interest)), fun a ha hā => ?_⟩ + refine P.policy_lt_self_of_consumption_lower_bound ha (hlb a ha) ?_ + rw [div_lt_iff₀ (by linarith)] at hā + linarith + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCrossRate.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCrossRate.lean new file mode 100644 index 000000000..8dbb9c9cc --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationCrossRate.lean @@ -0,0 +1,322 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationBounds + +/-! +# Comparing two interest rates + +Estimates relating two household problems that share an income process and an asset cap but +differ in the interest rate. These are the cross-rate ingredients of the sup-norm estimate on +the Bellman operator; they are stated for two programs rather than for a family, which is +more flexible and needs no indexing. + +They are only expressible at all because `assetCap` is a structure PARAMETER: `P` and `Q` +below have the same type, so their state spaces and feasible sets are directly comparable. + +## The trap + +The obvious estimate is false as stated. Resources differ by + + resources_P s - resources_Q s = (P.interest - Q.interest) * max 0 s.1 + +which is UNBOUNDED in the state: a household with enormous assets sees an enormous change in +income from a small change in the rate. A sup-norm estimate over all states cannot come from +this quantity directly. + +What rescues it is that the difference is only visible through a clamp. Maximum feasible +saving is `min assetCap resources`, so once resources exceed the cap at BOTH rates the +difference is exactly zero. The difference can be nonzero only where some rate leaves +resources below the cap, and that confines the state: `(1 + r) * max 0 s.1 < assetCap`. So +wherever the estimate is not trivially zero, the offending factor is bounded — by +`assetCap / (1 + rlo)`, with `rlo` any lower bound on the rates considered. + +That is the shape every cross-rate estimate here takes: a case split on whether the clamp +binds, trivial on one side, and bounded on the other precisely because the clamp failing to +bind confines the state. +-/ + +@[expose] public section + +open Set BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} + +/-- `min c ·` is 1-Lipschitz. A direct corollary of Mathlib's `lipschitzWith_min`. -/ +theorem abs_min_sub_min_le (c x y : ℝ) : |min c x - min c y| ≤ |x - y| := by + have h : LipschitzWith 1 fun t : ℝ => min c t := LipschitzWith.const_min LipschitzWith.id c + simpa [Real.dist_eq] using h.dist_le_mul x y + +/-- Resources differ by the rate gap times assets — a quantity unbounded in the state. -/ +theorem resources_sub {P Q : IncomeFluctuation Z assetFloor assetCap} (hinc : P.income = Q.income) + (s : ℝ × Z) : + P.resources s - Q.resources s = (P.interest - Q.interest) * max assetFloor s.1 := by + simp only [resources, hinc] + ring + +/-- The uniform bound on the state coordinate that the confinement lemmas deliver. With a +borrowing limit the coordinate can be negative, so what is bounded is its ABSOLUTE value, and +the limit contributes its own modulus to the constant. -/ +noncomputable def stateBoundCross (P : IncomeFluctuation Z assetFloor assetCap) (rlo : ℝ) : ℝ := + |assetFloor| + |assetCap| + (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) + +theorem maxIncome_pos (P : IncomeFluctuation Z assetFloor assetCap) : 0 < P.maxIncome := + lt_of_lt_of_le P.minIncome_pos P.minIncome_le_maxIncome + +theorem stateBoundCross_nonneg (P : IncomeFluctuation Z assetFloor assetCap) {rlo : ℝ} + (hr : 0 < 1 + rlo) : 0 ≤ P.stateBoundCross rlo := by + have h1 : 0 ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := by + have := P.maxIncome_pos + positivity + have := abs_nonneg assetFloor + have := abs_nonneg assetCap + simp only [stateBoundCross]; linarith + +theorem abs_assetCap_le_stateBoundCross (P : IncomeFluctuation Z assetFloor assetCap) {rlo : ℝ} + (hr : 0 < 1 + rlo) : |assetCap| ≤ P.stateBoundCross rlo := by + have h1 : 0 ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := by + have := P.maxIncome_pos + positivity + have := abs_nonneg assetFloor + simp only [stateBoundCross]; linarith + +/-- The elementary division step the two confinement lemmas share. -/ +private theorem div_le_div_of_le_denom {a b c : ℝ} (ha : 0 ≤ a) (hb : 0 < b) (hc : 0 < c) + (hbc : b ≤ c) : a / c ≤ a / b := by + rw [div_le_div_iff₀ hc hb] + nlinarith + +/-- **Where the cap fails to bind, the state is confined.** This is what tames the unbounded +factor above. -/ +theorem abs_max_le_of_resources_lt {P : IncomeFluctuation Z assetFloor assetCap} {rlo : ℝ} + (hr : 0 < 1 + rlo) (hP : rlo ≤ P.interest) {s : ℝ × Z} (hlt : P.resources s < assetCap) : + |max assetFloor s.1| ≤ P.stateBoundCross rlo := by + have hp1 : 0 < 1 + P.interest := P.interest_gt_neg_one + have hy : 0 < P.income s.2 := lt_of_lt_of_le P.minIncome_pos (P.minIncome_le s.2) + have hres : P.resources s = P.income s.2 + (1 + P.interest) * max assetFloor s.1 := rfl + have hcapabs := le_abs_self assetCap + have hfl := abs_nonneg assetFloor + have hca := abs_nonneg assetCap + have hmi := P.maxIncome_pos + have hup : max assetFloor s.1 ≤ |assetCap| / (1 + P.interest) := + (le_div_iff₀ hp1).mpr (by linarith) + have hmono : |assetCap| / (1 + P.interest) ≤ |assetCap| / (1 + rlo) := + div_le_div_of_le_denom hca hr hp1 (by linarith) + have hnum : |assetCap| / (1 + rlo) + ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := by + rw [div_le_div_iff₀ hr hr] + nlinarith + have hlow : -|assetFloor| ≤ max assetFloor s.1 := + le_trans (neg_abs_le _) (le_max_left _ _) + have hdiv : 0 ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := by positivity + rw [abs_le] + simp only [stateBoundCross] + constructor <;> linarith + +/-- **The maximum feasible saving moves little when the interest rate does.** + +The bound is uniform over the whole state space, which the raw resources gap is not. -/ +theorem abs_maxSaving_sub_le {P Q : IncomeFluctuation Z assetFloor assetCap} + (hinc : P.income = Q.income) (hmi : P.maxIncome = Q.maxIncome) + {rlo : ℝ} (hr : 0 < 1 + rlo) (hP : rlo ≤ P.interest) (hQ : rlo ≤ Q.interest) (s : ℝ × Z) : + |P.maxSaving s - Q.maxSaving s| + ≤ |P.interest - Q.interest| * P.stateBoundCross rlo := by + rw [P.maxSaving_eq, Q.maxSaving_eq] + rcases lt_or_ge (P.resources s) assetCap with hPlt | hPge + · -- the cap fails to bind at `P`, so the state is confined + refine le_trans (abs_min_sub_min_le _ _ _) ?_ + rw [resources_sub hinc, abs_mul] + exact mul_le_mul_of_nonneg_left (abs_max_le_of_resources_lt hr hP hPlt) (abs_nonneg _) + · rcases lt_or_ge (Q.resources s) assetCap with hQlt | hQge + · -- symmetrically at `Q` + refine le_trans (abs_min_sub_min_le _ _ _) ?_ + rw [resources_sub hinc, abs_mul] + refine mul_le_mul_of_nonneg_left ?_ (abs_nonneg _) + have := abs_max_le_of_resources_lt hr hQ hQlt + simpa only [stateBoundCross, hmi] using this + · -- the cap binds at both rates: the difference is exactly zero + rw [min_eq_left hPge, min_eq_left hQge, sub_self, abs_zero] + exact mul_nonneg (abs_nonneg _) (P.stateBoundCross_nonneg hr) + +/-! ### The clamped consumption gap + +Harder than the saving bound in two ways. The clamp LEVEL `maxConsumption` now moves with the +rate too, since it is `maxIncome + (1 + r) * assetCap - assetFloor`; and the chosen saving has +to be carried along, which is where the `θ` reparametrisation earns its place — with the choice +written as the borrowing limit plus a fraction of the saving room, the same `θ` can be fed to +both programs. -/ + +/-- `min` is 1-Lipschitz in both arguments at once: `lipschitzWith_min` read through the +product's supremum metric. -/ +theorem abs_min_sub_min_le_max (A B x y : ℝ) : + |min A x - min B y| ≤ max |A - B| |x - y| := by + simpa [Prod.dist_eq, Real.dist_eq] using lipschitzWith_min.dist_le_mul (A, x) (B, y) + +/-- The clamp level itself moves with the rate, but only by a bounded amount. -/ +theorem abs_maxConsumption_sub {P Q : IncomeFluctuation Z assetFloor assetCap} + (hmax : P.maxIncome = Q.maxIncome) : + |P.maxConsumption - Q.maxConsumption| = |P.interest - Q.interest| * |assetCap| := by + have hexp : P.maxConsumption - Q.maxConsumption + = (P.interest - Q.interest) * assetCap := by + simp only [maxConsumption, hmax]; ring + rw [hexp, abs_mul] + +/-- **Where the consumption clamp fails to bind, the state is confined.** The same move as +`abs_max_le_of_resources_lt`, one clamp further along. -/ +theorem abs_max_le_of_consumption_lt {P : IncomeFluctuation Z assetFloor assetCap} {rlo : ℝ} + (hr : 0 < 1 + rlo) (hP : rlo ≤ P.interest) {θ : ℝ} (hθ : θ ∈ Icc (0 : ℝ) 1) {s : ℝ × Z} + (hlt : P.consumption s (assetFloor + θ * P.savingRoom s) < P.maxConsumption) : + |max assetFloor s.1| ≤ P.stateBoundCross rlo := by + have hp1 : 0 < 1 + P.interest := P.interest_gt_neg_one + have hmi := P.maxIncome_pos + have hy : 0 < P.income s.2 := lt_of_lt_of_le P.minIncome_pos (P.minIncome_le s.2) + have hres : P.resources s = P.income s.2 + (1 + P.interest) * max assetFloor s.1 := rfl + have hmc : P.maxConsumption = P.maxIncome + (1 + P.interest) * assetCap - assetFloor := rfl + have hcons : P.consumption s (assetFloor + θ * P.savingRoom s) + = P.resources s - (assetFloor + θ * P.savingRoom s) := rfl + have hroom := P.savingRoom_nonneg s + have hroomle : P.savingRoom s ≤ assetCap - assetFloor := by + simp only [savingRoom]; linarith [P.maxSaving_le_assetCap s] + -- saving at most the cap, so consumption is at least resources minus the cap + have hsav : assetFloor + θ * P.savingRoom s ≤ assetCap := by nlinarith [hθ.1, hθ.2] + have hcapabs := le_abs_self assetCap + have hflabs : -assetFloor ≤ |assetFloor| := neg_le_abs _ + have hfl := abs_nonneg assetFloor + have hca := abs_nonneg assetCap + have hcapmul : (1 + P.interest) * assetCap ≤ (1 + P.interest) * |assetCap| := + mul_le_mul_of_nonneg_left hcapabs hp1.le + have hstep : (max assetFloor s.1 - |assetCap|) * (1 + P.interest) + < P.maxIncome + |assetFloor| + |assetCap| := by + rw [hcons, hres, hmc] at hlt + nlinarith + have hlt' : max assetFloor s.1 - |assetCap| + < (P.maxIncome + |assetFloor| + |assetCap|) / (1 + P.interest) := + (lt_div_iff₀ hp1).mpr hstep + have hdiv : (P.maxIncome + |assetFloor| + |assetCap|) / (1 + P.interest) + ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := + div_le_div_of_le_denom (by linarith) hr hp1 (by linarith) + have hlow : -|assetFloor| ≤ max assetFloor s.1 := + le_trans (neg_abs_le _) (le_max_left _ _) + have hdivnn : 0 ≤ (P.maxIncome + |assetFloor| + |assetCap|) / (1 + rlo) := by positivity + rw [abs_le] + simp only [stateBoundCross] + constructor <;> linarith + +/-- **The clamped consumption gap.** Uniform over the state space and over the choice `θ`. -/ +theorem abs_clamped_consumption_sub_le {P Q : IncomeFluctuation Z assetFloor assetCap} + (hinc : P.income = Q.income) (hmax : P.maxIncome = Q.maxIncome) + {rlo : ℝ} (hr : 0 < 1 + rlo) (hP : rlo ≤ P.interest) (hQ : rlo ≤ Q.interest) + {θ : ℝ} (hθ : θ ∈ Icc (0 : ℝ) 1) (s : ℝ × Z) : + |min P.maxConsumption (P.consumption s (assetFloor + θ * P.savingRoom s)) + - min Q.maxConsumption (Q.consumption s (assetFloor + θ * Q.savingRoom s))| + ≤ |P.interest - Q.interest| * (2 * P.stateBoundCross rlo) := by + have hgap : (0 : ℝ) ≤ |P.interest - Q.interest| := abs_nonneg _ + have hB : 0 ≤ P.stateBoundCross rlo := P.stateBoundCross_nonneg hr + have hBQ : Q.stateBoundCross rlo = P.stateBoundCross rlo := by + simp only [stateBoundCross, hmax] + -- the gap in unclamped consumption, given a bound on the state + have hcons : |max assetFloor s.1| ≤ P.stateBoundCross rlo → + |P.consumption s (assetFloor + θ * P.savingRoom s) + - Q.consumption s (assetFloor + θ * Q.savingRoom s)| + ≤ |P.interest - Q.interest| * (2 * P.stateBoundCross rlo) := by + intro hBd + have hms := abs_maxSaving_sub_le hinc hmax hr hP hQ s + have hres := resources_sub hinc s + have hexp : P.consumption s (assetFloor + θ * P.savingRoom s) + - Q.consumption s (assetFloor + θ * Q.savingRoom s) + = (P.resources s - Q.resources s) - θ * (P.maxSaving s - Q.maxSaving s) := by + simp only [consumption, savingRoom]; ring + rw [hexp, hres] + have h1 : |(P.interest - Q.interest) * max assetFloor s.1| + ≤ |P.interest - Q.interest| * P.stateBoundCross rlo := by + rw [abs_mul] + exact mul_le_mul_of_nonneg_left hBd hgap + have h2 : |θ * (P.maxSaving s - Q.maxSaving s)| + ≤ |P.interest - Q.interest| * P.stateBoundCross rlo := by + rw [abs_mul, abs_of_nonneg hθ.1] + calc θ * |P.maxSaving s - Q.maxSaving s| + ≤ 1 * |P.maxSaving s - Q.maxSaving s| := + mul_le_mul_of_nonneg_right hθ.2 (abs_nonneg _) + _ = |P.maxSaving s - Q.maxSaving s| := one_mul _ + _ ≤ _ := hms + have h3 := abs_sub (((P.interest - Q.interest) * max assetFloor s.1)) + (θ * (P.maxSaving s - Q.maxSaving s)) + nlinarith [h1, h2, h3] + -- the clamp-level gap is always available as a bound + have hlevel : |P.maxConsumption - Q.maxConsumption| + ≤ |P.interest - Q.interest| * (2 * P.stateBoundCross rlo) := by + rw [abs_maxConsumption_sub hmax] + refine mul_le_mul_of_nonneg_left ?_ hgap + linarith [P.abs_assetCap_le_stateBoundCross hr] + rcases le_or_gt P.maxConsumption (P.consumption s (assetFloor + θ * P.savingRoom s)) + with hPge | hPlt + · rcases le_or_gt Q.maxConsumption (Q.consumption s (assetFloor + θ * Q.savingRoom s)) + with hQge | hQlt + · -- BOTH clamps bind, so the two operators see the same clamped value and the + -- unclamped gap -- which may be enormous -- is invisible + rw [min_eq_left hPge, min_eq_left hQge] + exact hlevel + · -- `Q`'s clamp fails to bind, which confines the state + refine le_trans (abs_min_sub_min_le_max _ _ _ _) (max_le hlevel ?_) + refine hcons ?_ + have := abs_max_le_of_consumption_lt hr hQ hθ hQlt + rwa [hBQ] at this + · -- `P`'s clamp fails to bind, which confines the state + refine le_trans (abs_min_sub_min_le_max _ _ _ _) (max_le hlevel ?_) + exact hcons (abs_max_le_of_consumption_lt hr hP hθ hPlt) + +/-! ### Assembling: from pointwise bounds to a bound on the supremum + +The sup-norm estimate compares two suprema, and the reparametrisation has already made them +suprema over the SAME set `[0,1]`. What remains is to get from a bound on the objectives to a +bound on their suprema. + +A naive pointwise bound would not do, and the reason is worth stating. Where consumption is +tiny but positive, `u` of the two consumptions can differ by an arbitrary amount however close +the rates are, because `u` falls to `-∞`; so + + ∀ θ, objective_P θ ≤ objective_Q θ + ε + +is FALSE. Those actions are dominated -- consuming almost nothing is far worse than the +always-available choice `θ = 0` -- so they cannot affect either supremum, but a pointwise +statement cannot see that. + +The cutoff `L` is what encodes it. The hypothesis is only required at actions worth at least +`L`, and `L` is separately known to sit below the other program's supremum. Actions below `L` +are then bounded by `L` itself and need no comparison at all. In use, `L` will be the value of +`θ = 0`, which both programs have available. -/ + +theorem maxE_le_of_pointwise {P Q : IncomeFluctuation Z assetFloor assetCap} (v : (ℝ × Z) →ᵇ ℝ) + (s : ℝ × Z) (L : EReal) (ε : EReal) + (hL : L ≤ Q.toExtended.maxE v s + ε) + (h : ∀ θ ∈ Icc (0 : ℝ) 1, + L ≤ P.toExtended.objectiveE v s (assetFloor + θ * P.savingRoom s) → + P.toExtended.objectiveE v s (assetFloor + θ * P.savingRoom s) + ≤ Q.toExtended.objectiveE v s (assetFloor + θ * Q.savingRoom s) + ε) : + P.toExtended.maxE v s ≤ Q.toExtended.maxE v s + ε := by + rw [P.maxE_eq_sSup_unit] + refine sSup_le ?_ + rintro x ⟨θ, hθ, rfl⟩ + rcases le_or_gt L (P.toExtended.objectiveE v s (assetFloor + θ * P.savingRoom s)) + with hge | hlt + · -- worth at least the cutoff: the pointwise hypothesis applies + refine le_trans (h θ hθ hge) ?_ + have hmem : assetFloor + θ * Q.savingRoom s ∈ Q.toExtended.feasible s := by + rw [Q.feasible_eq_image]; exact ⟨θ, hθ, rfl⟩ + gcongr + exact le_maxValueE hmem + · -- below the cutoff: dominated, and bounded by the cutoff itself + exact le_trans hlt.le hL + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEnvelope.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEnvelope.lean new file mode 100644 index 000000000..7cc507618 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEnvelope.lean @@ -0,0 +1,211 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Analysis.DifferentiableSandwich +public import Mathlib.Analysis.Calculus.FDeriv.Extend + +/-! +# The lazy agent's value, and the envelope condition + +Clausen and Strub's construction: to show the value function is differentiable in assets, +exhibit a differentiable LOWER bound touching it. Freeze the optimal saving and vary the +state — the "lazy" agent, who meets an unfamiliar state by making the choice that was optimal +at a familiar one. + +The point is structural. Once the saving is frozen, current assets enter the objective ONLY +through the reward, because the continuation value depends on the saving chosen and on the +current income state, never on current assets. So the lazy value is `u` composed with an +affine map plus a constant, and is differentiable wherever `u` is. + +Concavity of the value function, already proved, then supplies the upper half of the sandwich +via `ConcaveOn.hasDerivAt_of_lowerBound`, and the derivative is the envelope condition + + V'(a) = (1 + r) · u'(c), + +which is the expression Light (2018) Theorem 1 is built on. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **The lazy agent's value.** The saving `a'` is frozen; only current assets vary. -/ +noncomputable def lazyValue (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (a' a : ℝ) : ℝ := + P.u (P.resources (a, z) - a') + + P.discount * ∑ z' : Z, P.transitionMatrix z z' * v (a', z') + +/-- Resources are affine in assets above the borrowing constraint. -/ +theorem hasDerivAt_resources {z : Z} {a : ℝ} (ha : assetFloor < a) : + HasDerivAt (fun x => P.resources (x, z)) (1 + P.interest) a := by + have heq : (fun x => P.resources (x, z)) =ᶠ[𝓝 a] fun x => P.income z + (1 + P.interest) * x := by + filter_upwards [lt_mem_nhds ha] with x hx + simp only [resources, max_eq_right hx.le] + refine HasDerivAt.congr_of_eventuallyEq ?_ heq + simpa using ((hasDerivAt_id a).const_mul (1 + P.interest)).const_add (P.income z) + +/-- **The lazy value is differentiable**, because current assets enter only through the +reward once the saving is frozen. -/ +theorem hasDerivAt_lazyValue (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (a' : ℝ) {a du : ℝ} + (ha : assetFloor < a) + (hu : HasDerivAt P.u du (P.resources (a, z) - a')) : + HasDerivAt (P.lazyValue v z a') ((1 + P.interest) * du) a := by + have hinner : HasDerivAt (fun x => P.resources (x, z) - a') (1 + P.interest) a := + (P.hasDerivAt_resources ha).sub_const a' + have hcomp : HasDerivAt (fun x => P.u (P.resources (x, z) - a')) + (du * (1 + P.interest)) a := hu.comp a hinner + have hsum := hcomp.add_const + ((P.discount : ℝ) * ∑ z' : Z, P.transitionMatrix z z' * v (a', z')) + rw [mul_comm du (1 + P.interest)] at hsum + exact hsum + +/-- **The lazy value is a lower bound**: freezing a feasible saving can only do worse than +optimising. Stated on the utility DOMAIN, so that it survives the bounded family, where zero +consumption is admissible and positivity has to be earned. -/ +theorem lazyValue_le_dom (v : (ℝ × Z) →ᵇ ℝ) (z : Z) {a' a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hmem : a' ∈ P.toExtended.feasible (a, z)) (hc : P.consumption (a, z) a' ∈ P.dom) : + P.lazyValue v z a' a ≤ P.toExtended.bellmanFn v (a, z) := by + have hobj := P.toExtended.le_bellmanFn v hmem + have hrw := P.reward_eq_coe_dom (s := (a, z)) ha hmem hc + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + rw [ExtendedStochasticProgram.objectiveE, hrw, hdx, ← EReal.coe_add, + EReal.coe_le_coe_iff] at hobj + exact hobj + +theorem lazyValue_le (v : (ℝ × Z) →ᵇ ℝ) (z : Z) {a' a : ℝ} (ha : a ∈ Icc assetFloor assetCap) + (hmem : a' ∈ P.toExtended.feasible (a, z)) (hc : 0 < P.consumption (a, z) a') : + P.lazyValue v z a' a ≤ P.toExtended.bellmanFn v (a, z) := + P.lazyValue_le_dom v z ha hmem (P.mem_dom_of_pos hc) + +/-- At the optimum the lazy value touches the Bellman value. -/ +theorem lazyValue_eq_dom (v : (ℝ × Z) →ᵇ ℝ) (z : Z) {a' a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hmem : a' ∈ P.toExtended.feasible (a, z)) (hc : P.consumption (a, z) a' ∈ P.dom) + (hopt : P.toExtended.objectiveE v (a, z) a' + = ((P.toExtended.bellmanFn v (a, z) : ℝ) : EReal)) : + P.lazyValue v z a' a = P.toExtended.bellmanFn v (a, z) := by + have hrw := P.reward_eq_coe_dom (s := (a, z)) ha hmem hc + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + rw [ExtendedStochasticProgram.objectiveE, hrw, hdx, ← EReal.coe_add, + EReal.coe_eq_coe_iff] at hopt + exact hopt + +theorem lazyValue_eq (v : (ℝ × Z) →ᵇ ℝ) (z : Z) {a' a : ℝ} (ha : a ∈ Icc assetFloor assetCap) + (hmem : a' ∈ P.toExtended.feasible (a, z)) (hc : 0 < P.consumption (a, z) a') + (hopt : P.toExtended.objectiveE v (a, z) a' + = ((P.toExtended.bellmanFn v (a, z) : ℝ) : EReal)) : + P.lazyValue v z a' a = P.toExtended.bellmanFn v (a, z) := + P.lazyValue_eq_dom v z ha hmem (P.mem_dom_of_pos hc) hopt + +/-! ### The envelope theorem + +Concavity supplies the upper half of the sandwich, the lazy agent the lower half. The only +work is the neighbourhood: the frozen saving is feasible at the state it came from, but at +poorer states it need not be, so the lower bound is only local. Continuity of the feasible +boundary gives an interval on which it survives, provided the saving is interior there. -/ + +/-- **The envelope condition for `bellman v`.** The marginal value of assets is the gross +interest rate times the marginal utility of the consumption the household actually chooses. +Clausen and Strub's sandwich, run one step of the iteration at a time rather than at the fixed +point. -/ +theorem hasDerivAt_bellman_of_optimal {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a a' du : ℝ} + (ha : assetFloor < a) (hacap : a < assetCap) + (hconc : ConcaveOn ℝ (Icc assetFloor assetCap) fun x => (P.toExtended.bellman v) (x, z)) + (ha'mem : a' ∈ P.toExtended.feasible (a, z)) + (hopt : P.toExtended.objectiveE v (a, z) a' + = ((P.toExtended.bellmanFn v (a, z) : ℝ) : EReal)) + (hint : a' < P.maxSaving (a, z)) + (hc : 0 < P.consumption (a, z) a') + (hu : HasDerivAt P.u du (P.consumption (a, z) a')) : + HasDerivAt (fun x => (P.toExtended.bellman v) (x, z)) ((1 + P.interest) * du) a := by + have hamem : ((a, z) : ℝ × Z).1 ∈ Icc assetFloor assetCap := ⟨ha.le, hacap.le⟩ + have ha'0 : assetFloor ≤ a' := ha'mem.1 + have hba : ∀ x : ℝ, P.toExtended.bellmanFn v (x, z) = (P.toExtended.bellman v) (x, z) := + fun x => by rw [← P.toExtended.bellman_apply] + -- the frozen saving stays feasible, and consumption stays positive, near `a` + have hcms : ContinuousAt (fun x => P.maxSaving (x, z)) a := + (P.continuous_maxSaving.comp (continuous_id.prodMk continuous_const)).continuousAt + have hccon : ContinuousAt (fun x => P.consumption (x, z) a') a := + ((P.continuous_resources.comp (continuous_id.prodMk continuous_const)).sub + continuous_const).continuousAt + have hev : {x : ℝ | assetFloor < x ∧ x < assetCap ∧ a' < P.maxSaving (x, z) + ∧ 0 < P.consumption (x, z) a'} ∈ 𝓝 a := by + have h1 : ∀ᶠ x in 𝓝 a, assetFloor < x := lt_mem_nhds ha + have h2 : ∀ᶠ x in 𝓝 a, x < assetCap := gt_mem_nhds hacap + have h3 : ∀ᶠ x in 𝓝 a, a' < P.maxSaving (x, z) := hcms.eventually_const_lt hint + have h4 : ∀ᶠ x in 𝓝 a, 0 < P.consumption (x, z) a' := hccon.eventually_const_lt hc + filter_upwards [h1, h2, h3, h4] with x hx1 hx2 hx3 hx4 using ⟨hx1, hx2, hx3, hx4⟩ + obtain ⟨l, r, hmem, hsub⟩ := mem_nhds_iff_exists_Ioo_subset.mp hev + have hIsub : Ioo l r ⊆ Icc assetFloor assetCap := fun x hx => + ⟨(hsub hx).1.le, (hsub hx).2.1.le⟩ + have hlow : ∀ x ∈ Ioo l r, P.lazyValue v z a' x ≤ (P.toExtended.bellman v) (x, z) := by + intro x hx + obtain ⟨hx1, hx2, hx3, hx4⟩ := hsub hx + have hfe : a' ∈ P.toExtended.feasible (x, z) := ⟨ha'0, hx3.le⟩ + have := P.lazyValue_le v z ⟨hx1.le, hx2.le⟩ hfe hx4 + rwa [hba x] at this + have htouch : P.lazyValue v z a' a = (P.toExtended.bellman v) (a, z) := by + have hfe : a' ∈ P.toExtended.feasible (a, z) := ha'mem + have := P.lazyValue_eq v z hamem hfe hc hopt + rwa [hba a] at this + refine (hconc.subset hIsub (convex_Ioo l r)).hasDerivAt_of_lowerBound hmem + (Ioo_mem_nhds hmem.1 hmem.2) + ⟨(l + a) / 2, ⟨by linarith [hmem.1, hmem.2], by linarith [hmem.1, hmem.2]⟩, + by linarith [hmem.1, hmem.2]⟩ + ⟨(a + r) / 2, ⟨by linarith [hmem.1, hmem.2], by linarith [hmem.1, hmem.2]⟩, + by linarith [hmem.1, hmem.2]⟩ + hlow htouch (P.hasDerivAt_lazyValue v z a' ha hu) + +theorem hasDerivAt_valueFunction (hpc : P.PositiveConsumption) {z : Z} {a du : ℝ} + (ha : assetFloor < a) (hacap : a < assetCap) + (hint : P.policy (a, z) < P.maxSaving (a, z)) + (hu : HasDerivAt P.u du (P.resources (a, z) - P.policy (a, z))) : + HasDerivAt (fun x => P.toExtended.valueFunction (x, z)) ((1 + P.interest) * du) a := by + simpa only [P.toExtended.bellman_valueFunction] using + P.hasDerivAt_bellman_of_optimal ha hacap + (by simpa only [P.toExtended.bellman_valueFunction] using P.concaveOn_valueFunction z) + (P.policy_mem (a, z)) (P.policy_optimal (a, z)) hint + (P.consumption_policy_pos hpc ⟨ha.le, hacap.le⟩) hu + +/-! ### At the borrowing constraint + +The sandwich does NOT extend to the left endpoint, and the failure is not technical. Its first +step is that `U - L` has a local minimum where the bounds touch, forcing the two derivatives to +agree; at an endpoint a minimum gives only `d' ≥ 0`, so the upper bound may be strictly +steeper and the derivative is not pinned. + +The right-derivative at the constraint is instead obtained as a LIMIT of the interior +derivatives, using that a derivative extends continuously to an endpoint +(`hasDerivWithinAt_Ici_of_tendsto_deriv`). This matters because `a = 0` is precisely the atom +the whole ergodic argument runs on, so a statement that stops short of it would stop short of +the states that carry the stationary distribution. -/ + +theorem hasDerivWithinAt_valueFunction_Ici {z : Z} {g : ℝ → ℝ} {e b : ℝ} (hb : 0 < b) + (hderiv : ∀ x ∈ Ioo (0 : ℝ) b, + HasDerivAt (fun y => P.toExtended.valueFunction (y, z)) (g x) x) + (hlim : Tendsto g (𝓝[>] (0 : ℝ)) (𝓝 e)) : + HasDerivWithinAt (fun y => P.toExtended.valueFunction (y, z)) e (Ici 0) 0 := by + have hmem : Ioo (0 : ℝ) b ∈ 𝓝[>] (0 : ℝ) := Ioo_mem_nhdsGT hb + refine hasDerivWithinAt_Ici_of_tendsto_deriv (s := Ioo 0 b) ?_ ?_ hmem ?_ + · exact fun x hx => ((hderiv x hx).differentiableAt).differentiableWithinAt + · exact (P.toExtended.valueFunction.continuous.comp + (continuous_id.prodMk continuous_const)).continuousWithinAt + · refine hlim.congr' ?_ + filter_upwards [hmem] with x hx + exact ((hderiv x hx).deriv).symm + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEuler.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEuler.lean new file mode 100644 index 000000000..78751f8af --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationEuler.lean @@ -0,0 +1,700 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEnvelope +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationCarrollKimball +public import LeanPool.AiyagariEquilibrium.Models.CRRA + +/-! +# The Euler equation along the iteration, and the borrowing limit + +Carroll and Kimball's induction runs along the ITERATES `Tⁿ 0`, so everything it needs must hold +one step of the iteration at a time, and at states where the borrowing limit BINDS. + +## The envelope condition + +`hasDerivAt_bellman` is Clausen and Strub's theorem for `bellman v` rather than for the fixed +point. Nothing in the lazy-agent argument used the fixed point: freezing a feasible saving and +varying current assets gives a differentiable lower bound touching `bellman v` at the optimum, +and concavity of the slice supplies the upper half of the sandwich. `euler_of_interior` reads +the first-order condition off it. + +## But the Euler equation does not need it + +The same freezing trick gives BOTH one-sided bounds on the marginal value of assets — carry the +poorer household's plan forward (`bellman_sub_ge`), or the richer household's plan back +(`bellman_sub_le`) — and those are enough. Comparing the optimum with a deviation of size `ε` +and letting `ε` shrink gives + +* `euler_le`, from saving MORE, which is feasible unless the household already saves the + maximum, and +* `euler_ge`, from saving LESS, which is feasible unless the household is at the borrowing + limit; + +and `euler_eq` is the two together. No envelope theorem, no second derivative: only that `u` is +differentiable at the consumptions concerned. + +## The borrowing limit + +That asymmetry is the whole point. At the limit `euler_ge` fails and the first-order condition +degenerates to the inequality `euler_le`, which in endogenous-gridpoint form says consumption is +at most `egmMap` (`egmMap_ge`). `concaveOn_of_egm_corner` shows that is enough: at a constrained +state the saving is `assetFloor`, which lies below ANY average of savings, and that is the only +thing the transfer of `IncomeFluctuationCarrollKimball` ever needed. So the kink costs nothing, +and `concaveOn_consumptionFnOf_bellman` is Carroll and Kimball's induction step with the +borrowing limit allowed to bind anywhere. + +What it still assumes is that the asset CAP is slack — a different condition, discharged by +calibration in `crra_policy_lt_assetCap` — and the induction hypotheses on the continuation's +consumption function. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### The envelope condition for an iterate -/ + +/-- **The envelope condition for `bellman v`.** The marginal value of assets is the gross +interest rate times the marginal utility of the consumption the household actually chooses. +Clausen and Strub's sandwich, run one step of the iteration at a time rather than at the fixed +point. -/ +theorem hasDerivAt_bellman {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a du : ℝ} + (ha : assetFloor < a) (hacap : a < assetCap) + (hconc : ConcaveOn ℝ (Icc assetFloor assetCap) fun x => (P.toExtended.bellman v) (x, z)) + (hint : P.policyOf v (a, z) < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf v z a) + (hu : HasDerivAt P.u du (P.consumptionFnOf v z a)) : + HasDerivAt (fun x => (P.toExtended.bellman v) (x, z)) ((1 + P.interest) * du) a := by + exact P.hasDerivAt_bellman_of_optimal ha hacap hconc + (P.policyOf_mem v (a, z)) (P.policyOf_optimal v (a, z)) hint hc hu + +/-! ### The two lazy bounds on the marginal value of assets + +Both directions come from the same trick: carry a plan that was optimal at one asset level over +to another. Neither needs the envelope theorem, and neither needs the borrowing limit to be +slack. -/ + +/-- **The poorer household's plan, carried forward.** The generalisation of +`valueFunction_sub_ge` to one step of the iteration. -/ +theorem bellman_sub_ge {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) (hxy : x ≤ y) + (hc : 0 < P.consumptionFnOf v z x) : + P.u (P.consumptionFnOf v z x + (1 + P.interest) * (y - x)) - P.u (P.consumptionFnOf v z x) + ≤ (P.toExtended.bellman v) (y, z) - (P.toExtended.bellman v) (x, z) := by + set b := P.policyOf v (x, z) with hb + have hbm : b ∈ P.toExtended.feasible (x, z) := P.policyOf_mem v (x, z) + have hbm' : b ∈ P.toExtended.feasible (y, z) := P.feasible_mono hxy hbm + have hres : P.resources (y, z) = P.resources (x, z) + (1 + P.interest) * (y - x) := by + simp only [resources, max_eq_right hx.1, max_eq_right hy.1]; ring + have hch : P.consumption (y, z) b + = P.consumptionFnOf v z x + (1 + P.interest) * (1 * (y - x)) := by + simp only [consumptionFnOf, consumption, hres, ← hb]; ring + have hchpos : 0 < P.consumption (y, z) b := by + rw [hch] + have : (0 : ℝ) ≤ (1 + P.interest) * (1 * (y - x)) := + mul_nonneg P.interest_gt_neg_one.le (by linarith) + linarith + have hle := P.lazyValue_le v z hy hbm' hchpos + have heq := P.lazyValue_eq v z hx hbm hc (P.policyOf_optimal v (x, z)) + have hba : ∀ t : ℝ, P.toExtended.bellmanFn v (t, z) = (P.toExtended.bellman v) (t, z) := + fun t => by rw [← P.toExtended.bellman_apply] + rw [hba y] at hle + rw [hba x] at heq + simp only [lazyValue] at hle heq + have hcx : P.resources (x, z) - b = P.consumptionFnOf v z x := rfl + have hcy : P.resources (y, z) - b + = P.consumptionFnOf v z x + (1 + P.interest) * (y - x) := by + simp only [← hcx, hres]; ring + rw [hcy] at hle + rw [hcx] at heq + linarith + +/-- **The richer household's plan, carried back.** Feasible only if the richer plan is still +affordable at the poorer state, which is what `hfe` says. -/ +theorem bellman_sub_le {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hfe : P.policyOf v (y, z) ≤ P.maxSaving (x, z)) + (hcx : 0 < P.consumptionFnOf v z y - (1 + P.interest) * (y - x)) + (hc : 0 < P.consumptionFnOf v z y) : + (P.toExtended.bellman v) (y, z) - (P.toExtended.bellman v) (x, z) + ≤ P.u (P.consumptionFnOf v z y) + - P.u (P.consumptionFnOf v z y - (1 + P.interest) * (y - x)) := by + set b := P.policyOf v (y, z) with hb + have hbm : b ∈ P.toExtended.feasible (y, z) := P.policyOf_mem v (y, z) + have hbm' : b ∈ P.toExtended.feasible (x, z) := ⟨hbm.1, hfe⟩ + have hres : P.resources (y, z) = P.resources (x, z) + (1 + P.interest) * (y - x) := by + simp only [resources, max_eq_right hx.1, max_eq_right hy.1]; ring + have hcy : P.resources (y, z) - b = P.consumptionFnOf v z y := rfl + have hcxv : P.resources (x, z) - b + = P.consumptionFnOf v z y - (1 + P.interest) * (y - x) := by + simp only [← hcy, hres]; ring + have hcpos : 0 < P.consumption (x, z) b := by + simp only [consumption, hcxv]; exact hcx + have hle := P.lazyValue_le v z hx hbm' hcpos + have heq := P.lazyValue_eq v z hy hbm hc (P.policyOf_optimal v (y, z)) + have hba : ∀ t : ℝ, P.toExtended.bellmanFn v (t, z) = (P.toExtended.bellman v) (t, z) := + fun t => by rw [← P.toExtended.bellman_apply] + rw [hba x] at hle + rw [hba y] at heq + simp only [lazyValue] at hle heq + rw [hcxv] at hle + rw [hcy] at heq + linarith + +/-! ### The Euler inequality, which survives the borrowing limit + +Saving MORE is feasible whenever the household is not already saving the maximum, and that has +nothing to do with the borrowing limit. So this half of the first-order condition holds at the +corner too, and it is the half the concavity argument needs there. -/ + +/-- **The Euler inequality.** Marginal utility today is at least the discounted, interest- +augmented expected marginal utility tomorrow. The only interiority is `hAmax`: the household +could save a little more. -/ +theorem euler_le {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A du : ℝ} {du' : Z → ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hu : HasDerivAt P.u du (P.consumptionFnOf (P.toExtended.bellman v) z a)) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) + (hu' : ∀ z' : Z, HasDerivAt P.u (du' z') (P.consumptionFnOf v z' A)) : + (P.discount : ℝ) * ((1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z') + ≤ du := by + subst hA + set W := P.toExtended.bellman v with hWdef + set A := P.policyOf W (a, z) with hAdef + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hAmem : A ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem W (a, z)) + set c := P.consumptionFnOf W z a with hcdef + have hcA : c = P.resources (a, z) - A := rfl + set D : ℝ := du - (P.discount : ℝ) + * ((1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z') with hDdef + set F : ℝ → ℝ := fun ε => (P.u c - P.u (c - ε)) + - (P.discount : ℝ) * ∑ z' : Z, P.transitionMatrix z z' + * (P.u (P.consumptionFnOf v z' A + (1 + P.interest) * ε) + - P.u (P.consumptionFnOf v z' A)) with hFdef + have hF0 : F 0 = 0 := by simp [hFdef] + -- `F` is differentiable at zero, with derivative the Euler gap + have hd1 : HasDerivAt (fun ε : ℝ => P.u c - P.u (c - ε)) du 0 := by + have h1 : HasDerivAt (fun ε : ℝ => c - ε) (-1) 0 := by + simpa using (hasDerivAt_id (0 : ℝ)).const_sub c + have h2 : HasDerivAt (fun ε : ℝ => P.u (c - ε)) (du * (-1)) 0 := + HasDerivAt.comp (0 : ℝ) (by simpa using hu) h1 + simpa using h2.const_sub (P.u c) + have hd2 : ∀ z' : Z, HasDerivAt + (fun ε : ℝ => P.u (P.consumptionFnOf v z' A + (1 + P.interest) * ε) + - P.u (P.consumptionFnOf v z' A)) (du' z' * (1 + P.interest)) 0 := by + intro z' + have h1 : HasDerivAt (fun ε : ℝ => P.consumptionFnOf v z' A + (1 + P.interest) * ε) + (1 + P.interest) 0 := by + simpa using ((hasDerivAt_id (0 : ℝ)).const_mul (1 + P.interest)).const_add + (P.consumptionFnOf v z' A) + have h2 := HasDerivAt.comp (0 : ℝ) (by simpa using hu' z') h1 + simpa using h2.sub_const (P.u (P.consumptionFnOf v z' A)) + have hFd : HasDerivAt F D 0 := by + have hsum : HasDerivAt (fun ε : ℝ => ∑ z' : Z, P.transitionMatrix z z' + * (P.u (P.consumptionFnOf v z' A + (1 + P.interest) * ε) + - P.u (P.consumptionFnOf v z' A))) + (∑ z' : Z, P.transitionMatrix z z' * (du' z' * (1 + P.interest))) 0 := + HasDerivAt.fun_sum fun z' _ => (hd2 z').const_mul _ + have hcomm : (1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z' + = ∑ z' : Z, P.transitionMatrix z z' * (du' z' * (1 + P.interest)) := by + rw [Finset.mul_sum]; exact Finset.sum_congr rfl fun z' _ => by ring + rw [hDdef, hcomm] + exact hd1.sub (hsum.const_mul (P.discount : ℝ)) + -- and it is non-negative to the right of zero, because saving more is not profitable + have hFnn : ∀ᶠ ε in 𝓝[>] (0 : ℝ), 0 ≤ F ε := by + have hδ : 0 < min (P.maxSaving (a, z) - A) c := lt_min (by linarith) hc + filter_upwards [Ioo_mem_nhdsGT hδ] with ε hε + have hε0 : 0 < ε := hε.1 + have hε1 : ε < P.maxSaving (a, z) - A := lt_of_lt_of_le hε.2 (min_le_left _ _) + have hε2 : ε < c := lt_of_lt_of_le hε.2 (min_le_right _ _) + have hAe : A + ε ∈ Icc assetFloor assetCap := + ⟨by linarith [hAmem.1], by linarith [P.maxSaving_le_assetCap (a, z)]⟩ + have hfeas : A + ε ∈ P.toExtended.feasible (a, z) := + ⟨by linarith [hAmem.1], by linarith⟩ + have hcpos : 0 < P.consumption (a, z) (A + ε) := by + simp only [consumption]; rw [hcA] at hε2; linarith + have hle := P.lazyValue_le W z ha hfeas hcpos + have heq := P.lazyValue_eq W z ha (P.policyOf_mem W (a, z)) hc + (P.policyOf_optimal W (a, z)) + have hstep : P.lazyValue W z (A + ε) a ≤ P.lazyValue W z A a := by rw [heq]; exact hle + simp only [lazyValue] at hstep + rw [show P.resources (a, z) - (A + ε) = c - ε from by rw [hcA]; ring, + show P.resources (a, z) - A = c from hcA.symm] at hstep + have hgain : ∀ z' : Z, + P.u (P.consumptionFnOf v z' A + (1 + P.interest) * ε) + - P.u (P.consumptionFnOf v z' A) ≤ W (A + ε, z') - W (A, z') := by + intro z' + have hg := P.bellman_sub_ge (z := z') hAmem hAe (by linarith) (hc' z') + rwa [show A + ε - A = ε from by ring] at hg + have hsum := Finset.sum_le_sum fun z' (_ : z' ∈ Finset.univ) => + mul_le_mul_of_nonneg_left (hgain z') (P.transitionMatrix_nonneg z z') + have hsplit : ∑ z' : Z, P.transitionMatrix z z' * (W (A + ε, z') - W (A, z')) + = (∑ z' : Z, P.transitionMatrix z z' * W (A + ε, z')) + - ∑ z' : Z, P.transitionMatrix z z' * W (A, z') := by + rw [← Finset.sum_sub_distrib]; exact Finset.sum_congr rfl fun z' _ => by ring + have hb1 := mul_le_mul_of_nonneg_left hsum hβ + rw [hsplit] at hb1 + simp only [hFdef] + linarith + -- a differentiable function that is zero at zero and non-negative to the right of it has a + -- non-negative derivative + have hmono : Tendsto (slope F 0) (𝓝[>] (0 : ℝ)) (𝓝 D) := + (hasDerivAt_iff_tendsto_slope.mp hFd).mono_left + (nhdsWithin_mono _ fun x hx => ne_of_gt hx) + have hnn : ∀ᶠ ε in 𝓝[>] (0 : ℝ), 0 ≤ slope F 0 ε := by + filter_upwards [hFnn, self_mem_nhdsWithin] with ε hFe hε0 + rw [slope_def_field, hF0, sub_zero, sub_zero] + exact div_nonneg hFe (le_of_lt hε0) + have hD : 0 ≤ D := ge_of_tendsto hmono hnn + rw [hDdef] at hD + linarith + +/-! ### The first-order condition + +With the envelope available at the saving actually chosen, the objective is differentiable in +the saving, the choice is a local maximum, and the derivative vanishes. That is the Euler +equation: the marginal utility of consuming now equals the discounted, interest-augmented +expected marginal utility of consuming next period. -/ + +/-- **The Euler equation along the iteration.** Every hypothesis is an interiority or +differentiability condition at the single state `(a, z)` and at the saving it chooses; nothing +is assumed globally. -/ +theorem euler_of_interior {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A du : ℝ} {du' : Z → ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hA0 : assetFloor < A) (hAcap : A < assetCap) (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hu : HasDerivAt P.u du (P.consumptionFnOf (P.toExtended.bellman v) z a)) + (hconc : ∀ z' : Z, ConcaveOn ℝ (Icc assetFloor assetCap) + fun x => (P.toExtended.bellman v) (x, z')) + (hint' : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z')) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) + (hu' : ∀ z' : Z, HasDerivAt P.u (du' z') (P.consumptionFnOf v z' A)) : + du = (P.discount : ℝ) * ((1 + P.interest) * ∑ z', P.transitionMatrix z z' * du' z') := by + subst hA + set W := P.toExtended.bellman v with hWdef + set A := P.policyOf W (a, z) with hAdef + have hcA : P.consumptionFnOf W z a = P.resources (a, z) - A := rfl + -- the continuation is differentiable in the saving, by the envelope one step on + have hWderiv : ∀ z' : Z, HasDerivAt (fun x => W (x, z')) ((1 + P.interest) * du' z') A := + fun z' => P.hasDerivAt_bellman hA0 hAcap (hconc z') (hint' z') (hc' z') (hu' z') + have hsum : HasDerivAt (fun x => ∑ z' : Z, P.transitionMatrix z z' * W (x, z')) + (∑ z' : Z, P.transitionMatrix z z' * ((1 + P.interest) * du' z')) A := + HasDerivAt.fun_sum fun z' _ => (hWderiv z').const_mul _ + -- and so is the reward + have hres : HasDerivAt (fun x : ℝ => P.resources (a, z) - x) (-1) A := by + simpa using (hasDerivAt_id A).const_sub (P.resources (a, z)) + have hrew : HasDerivAt (fun x => P.u (P.resources (a, z) - x)) (du * (-1)) A := + HasDerivAt.comp A (hcA ▸ hu) hres + have hΦ : HasDerivAt (fun x => P.lazyValue W z x a) + (du * (-1) + (P.discount : ℝ) + * ∑ z' : Z, P.transitionMatrix z z' * ((1 + P.interest) * du' z')) A := + hrew.add (hsum.const_mul (P.discount : ℝ)) + -- the chosen saving is a local maximum of the objective + have hlm : IsLocalMax (fun x => P.lazyValue W z x a) A := by + have hcont : ContinuousAt (fun x : ℝ => P.resources (a, z) - x) A := + (continuous_const.sub continuous_id).continuousAt + have h1 : ∀ᶠ x in 𝓝 A, assetFloor < x := lt_mem_nhds hA0 + have h2 : ∀ᶠ x in 𝓝 A, x < P.maxSaving (a, z) := gt_mem_nhds hAmax + have hcpos : (0 : ℝ) < P.resources (a, z) - A := by rw [← hcA]; exact hc + have h3 : ∀ᶠ x in 𝓝 A, 0 < P.resources (a, z) - x := hcont.eventually_const_lt hcpos + have hAopt : P.lazyValue W z A a = P.toExtended.bellmanFn W (a, z) := + P.lazyValue_eq W z ha (P.policyOf_mem W (a, z)) hc (P.policyOf_optimal W (a, z)) + filter_upwards [h1, h2, h3] with x hx1 hx2 hx3 + rw [hAopt] + exact P.lazyValue_le W z ha ⟨hx1.le, hx2.le⟩ hx3 + -- so the derivative vanishes, which is the Euler equation + have hzero := hlm.hasDerivAt_eq_zero hΦ + have hfac : ∑ z' : Z, P.transitionMatrix z z' * ((1 + P.interest) * du' z') + = (1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z' := by + rw [Finset.mul_sum] + exact Finset.sum_congr rfl fun z' _ => by ring + rw [hfac] at hzero + linarith + +/-- **The reverse Euler inequality.** Saving LESS is feasible only away from the borrowing +limit, which is why this half fails at the corner. Like `euler_le` it uses only the two lazy +bounds: the envelope theorem is not needed for the Euler equation. -/ +theorem euler_ge {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A du : ℝ} {du' : Z → ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hA0 : assetFloor < A) + (hslack : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z')) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hu : HasDerivAt P.u du (P.consumptionFnOf (P.toExtended.bellman v) z a)) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) + (hu' : ∀ z' : Z, HasDerivAt P.u (du' z') (P.consumptionFnOf v z' A)) : + du ≤ (P.discount : ℝ) + * ((1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z') := by + subst hA + set W := P.toExtended.bellman v with hWdef + set A := P.policyOf W (a, z) with hAdef + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hAmem : A ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem W (a, z)) + set c := P.consumptionFnOf W z a with hcdef + have hcA : c = P.resources (a, z) - A := rfl + set D : ℝ := (P.discount : ℝ) + * ((1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z') - du with hDdef + set G : ℝ → ℝ := fun ε => (P.discount : ℝ) * ∑ z' : Z, P.transitionMatrix z z' + * (P.u (P.consumptionFnOf v z' A) + - P.u (P.consumptionFnOf v z' A - (1 + P.interest) * ε)) + - (P.u (c + ε) - P.u c) with hGdef + have hG0 : G 0 = 0 := by simp [hGdef] + -- the derivative at zero is the Euler gap, with the sign reversed + have hd1 : HasDerivAt (fun ε : ℝ => P.u (c + ε) - P.u c) du 0 := by + have h1 : HasDerivAt (fun ε : ℝ => c + ε) 1 0 := by + simpa using (hasDerivAt_id (0 : ℝ)).const_add c + have h2 : HasDerivAt (fun ε : ℝ => P.u (c + ε)) (du * 1) 0 := + HasDerivAt.comp (0 : ℝ) (by simpa using hu) h1 + simpa using h2.sub_const (P.u c) + have hd2 : ∀ z' : Z, HasDerivAt + (fun ε : ℝ => P.u (P.consumptionFnOf v z' A) + - P.u (P.consumptionFnOf v z' A - (1 + P.interest) * ε)) + (du' z' * (1 + P.interest)) 0 := by + intro z' + have h1 : HasDerivAt (fun ε : ℝ => P.consumptionFnOf v z' A - (1 + P.interest) * ε) + (-(1 + P.interest)) 0 := by + simpa using ((hasDerivAt_id (0 : ℝ)).const_mul (1 + P.interest)).const_sub + (P.consumptionFnOf v z' A) + have h2 := HasDerivAt.comp (0 : ℝ) (by simpa using hu' z') h1 + have h3 := h2.const_sub (P.u (P.consumptionFnOf v z' A)) + have hrw : -(du' z' * -(1 + P.interest)) = du' z' * (1 + P.interest) := by ring + rwa [hrw] at h3 + have hGd : HasDerivAt G D 0 := by + have hsum : HasDerivAt (fun ε : ℝ => ∑ z' : Z, P.transitionMatrix z z' + * (P.u (P.consumptionFnOf v z' A) + - P.u (P.consumptionFnOf v z' A - (1 + P.interest) * ε))) + (∑ z' : Z, P.transitionMatrix z z' * (du' z' * (1 + P.interest))) 0 := + HasDerivAt.fun_sum fun z' _ => (hd2 z').const_mul _ + have hcomm : (1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z' + = ∑ z' : Z, P.transitionMatrix z z' * (du' z' * (1 + P.interest)) := by + rw [Finset.mul_sum]; exact Finset.sum_congr rfl fun z' _ => by ring + rw [hDdef, hcomm] + exact (hsum.const_mul (P.discount : ℝ)).sub hd1 + -- and it is non-negative to the right of zero, because saving less is not profitable + have hGnn : ∀ᶠ ε in 𝓝[>] (0 : ℝ), 0 ≤ G ε := by + have hloc : ∀ z' : Z, ∀ᶠ ε in 𝓝[>] (0 : ℝ), + P.policyOf v (A, z') ≤ P.maxSaving (A - ε, z') + ∧ 0 < P.consumptionFnOf v z' A - (1 + P.interest) * ε := by + intro z' + have hcont : ContinuousAt (fun ε : ℝ => P.maxSaving (A - ε, z')) 0 := + (P.continuous_maxSaving.comp + ((continuous_const.sub continuous_id).prodMk continuous_const)).continuousAt + have h1 : ∀ᶠ ε in 𝓝 (0 : ℝ), P.policyOf v (A, z') < P.maxSaving (A - ε, z') := + hcont.eventually_const_lt (by simpa using hslack z') + have hcont2 : ContinuousAt + (fun ε : ℝ => P.consumptionFnOf v z' A - (1 + P.interest) * ε) 0 := + (continuous_const.sub (continuous_const.mul continuous_id)).continuousAt + have h2 : ∀ᶠ ε in 𝓝 (0 : ℝ), + 0 < P.consumptionFnOf v z' A - (1 + P.interest) * ε := + hcont2.eventually_const_lt (by simpa using hc' z') + filter_upwards [(h1.filter_mono nhdsWithin_le_nhds), + (h2.filter_mono nhdsWithin_le_nhds)] with ε he1 he2 using ⟨he1.le, he2⟩ + have hall : ∀ᶠ ε in 𝓝[>] (0 : ℝ), ∀ z' : Z, + P.policyOf v (A, z') ≤ P.maxSaving (A - ε, z') + ∧ 0 < P.consumptionFnOf v z' A - (1 + P.interest) * ε := + Filter.eventually_all.mpr hloc + filter_upwards [Ioo_mem_nhdsGT (show (0 : ℝ) < A - assetFloor by linarith), hall] + with ε hε hz' + have hε0 : 0 < ε := hε.1 + have hε1 : ε < A - assetFloor := hε.2 + have hAe : A - ε ∈ Icc assetFloor assetCap := + ⟨by linarith, by linarith [hAmem.2]⟩ + have hfeas : A - ε ∈ P.toExtended.feasible (a, z) := + ⟨by linarith, by linarith [(P.policyOf_mem W (a, z)).2]⟩ + have hcpos : 0 < P.consumption (a, z) (A - ε) := by + simp only [consumption]; rw [hcA] at hc; linarith + have hle := P.lazyValue_le W z ha hfeas hcpos + have heq := P.lazyValue_eq W z ha (P.policyOf_mem W (a, z)) hc + (P.policyOf_optimal W (a, z)) + have hstep : P.lazyValue W z (A - ε) a ≤ P.lazyValue W z A a := by rw [heq]; exact hle + simp only [lazyValue] at hstep + rw [show P.resources (a, z) - (A - ε) = c + ε from by rw [hcA]; ring, + show P.resources (a, z) - A = c from hcA.symm] at hstep + have hloss : ∀ z' : Z, W (A, z') - W (A - ε, z') + ≤ P.u (P.consumptionFnOf v z' A) + - P.u (P.consumptionFnOf v z' A - (1 + P.interest) * ε) := by + intro z' + have hg := P.bellman_sub_le (z := z') hAe hAmem (hz' z').1 + (by have := (hz' z').2; rw [show A - (A - ε) = ε from by ring]; linarith) + (hc' z') + rwa [show A - (A - ε) = ε from by ring] at hg + have hsum := Finset.sum_le_sum fun z' (_ : z' ∈ Finset.univ) => + mul_le_mul_of_nonneg_left (hloss z') (P.transitionMatrix_nonneg z z') + have hsplit : ∑ z' : Z, P.transitionMatrix z z' * (W (A, z') - W (A - ε, z')) + = (∑ z' : Z, P.transitionMatrix z z' * W (A, z')) + - ∑ z' : Z, P.transitionMatrix z z' * W (A - ε, z') := by + rw [← Finset.sum_sub_distrib]; exact Finset.sum_congr rfl fun z' _ => by ring + have hb1 := mul_le_mul_of_nonneg_left hsum hβ + rw [hsplit] at hb1 + simp only [hGdef] + linarith + have hmono : Tendsto (slope G 0) (𝓝[>] (0 : ℝ)) (𝓝 D) := + (hasDerivAt_iff_tendsto_slope.mp hGd).mono_left + (nhdsWithin_mono _ fun x hx => ne_of_gt hx) + have hnn : ∀ᶠ ε in 𝓝[>] (0 : ℝ), 0 ≤ slope G 0 ε := by + filter_upwards [hGnn, self_mem_nhdsWithin] with ε hGe hε0 + rw [slope_def_field, hG0, sub_zero, sub_zero] + exact div_nonneg hGe (le_of_lt hε0) + have hD : 0 ≤ D := ge_of_tendsto hmono hnn + rw [hDdef] at hD + linarith + +/-- **The Euler equation**, from the two deviations. No envelope theorem, no second +derivatives: only that `u` is differentiable at the consumptions concerned. -/ +theorem euler_eq {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A du : ℝ} {du' : Z → ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hA0 : assetFloor < A) (hAmax : A < P.maxSaving (a, z)) + (hslack : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z')) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hu : HasDerivAt P.u du (P.consumptionFnOf (P.toExtended.bellman v) z a)) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) + (hu' : ∀ z' : Z, HasDerivAt P.u (du' z') (P.consumptionFnOf v z' A)) : + du = (P.discount : ℝ) + * ((1 + P.interest) * ∑ z' : Z, P.transitionMatrix z z' * du' z') := + le_antisymm (P.euler_ge ha hA hA0 hslack hc hu hc' hu') + (P.euler_le ha hA hAmax hc hu hc' hu') + +/-! ### CRRA: the Euler equation in endogenous-gridpoint form + +With `u' c = c ^ (-γ)` the Euler equation says that consumption is a positive multiple of the +weighted power mean, exponent `-γ`, of next period's consumptions — which is exactly `egmMap`, +and exactly what `concaveOn_consumptionFnOf_bellman_of_euler` asks for. -/ + +theorem egm_of_euler {γ : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) + (heuler : P.consumptionFnOf (P.toExtended.bellman v) z a ^ (-γ) + = (P.discount : ℝ) * ((1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ))) : + P.consumptionFnOf (P.toExtended.bellman v) z a = P.egmMap v γ z A := by + have hγn : γ ≠ 0 := ne_of_gt hγ0 + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hS : (0 : ℝ) < ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hc' + have hprod : P.consumptionFnOf (P.toExtended.bellman v) z a ^ (-γ) + = ((P.discount : ℝ) * (1 + P.interest)) + * ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ) := by + rw [heuler]; ring + have hexp : (-γ) * (-(1 / γ)) = 1 := by field_simp + have hinv : (P.consumptionFnOf (P.toExtended.bellman v) z a ^ (-γ)) ^ (-(1 / γ)) + = P.consumptionFnOf (P.toExtended.bellman v) z a := by + rw [← Real.rpow_mul hc.le, hexp, Real.rpow_one] + rw [← hinv, hprod, Real.mul_rpow hK.le hS.le] + simp only [egmMap, powerMean] + congr 1 + rw [show -(1 / γ) = 1 / (-γ) by field_simp] + +/-- **The hypothesis `hEuler` of `concaveOn_consumptionFnOf_bellman_of_euler`, discharged.** +Everything that remains is interiority: the saving chosen must be strictly inside the asset +region and strictly below the feasible maximum, this period and next. -/ +theorem egmMap_consumptionFnOf {γ : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + (hu : P.u = crraUtility γ) {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hA0 : assetFloor < A) (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hint' : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z')) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.consumptionFnOf (P.toExtended.bellman v) z a = P.egmMap v γ z A := by + have hd : HasDerivAt P.u (P.consumptionFnOf (P.toExtended.bellman v) z a ^ (-γ)) + (P.consumptionFnOf (P.toExtended.bellman v) z a) := by + rw [hu]; exact hasDerivAt_crraUtility γ hc + have hd' : ∀ z' : Z, HasDerivAt P.u (P.consumptionFnOf v z' A ^ (-γ)) + (P.consumptionFnOf v z' A) := fun z' => by + rw [hu]; exact hasDerivAt_crraUtility γ (hc' z') + exact P.egm_of_euler hγ0 hβ hc hc' (P.euler_eq ha hA hA0 hAmax hint' hc hd hc' hd') + +/-- The endogenous-gridpoint map, written as a single power of the Euler right-hand side. -/ +theorem egmMap_eq_rpow {γ : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {A : ℝ} (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.egmMap v γ z A = ((P.discount : ℝ) * (1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ)) ^ (-(1 / γ)) := by + have hγn : γ ≠ 0 := ne_of_gt hγ0 + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hS : (0 : ℝ) < ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hc' + rw [Real.mul_rpow hK.le hS.le] + simp only [egmMap, powerMean] + congr 1 + rw [show -(1 / γ) = 1 / (-γ) by field_simp] + +/-- **The Euler inequality in endogenous-gridpoint form.** Consumption never exceeds the value +the endogenous grid assigns to the saving actually chosen — and this holds AT the borrowing +limit, where the equality fails. -/ +theorem egmMap_ge {γ : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + (hu : P.u = crraUtility γ) {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.consumptionFnOf (P.toExtended.bellman v) z a ≤ P.egmMap v γ z A := by + have hγn : γ ≠ 0 := ne_of_gt hγ0 + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hS : (0 : ℝ) < ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hc' + have hd : HasDerivAt P.u (P.consumptionFnOf (P.toExtended.bellman v) z a ^ (-γ)) + (P.consumptionFnOf (P.toExtended.bellman v) z a) := by + rw [hu]; exact hasDerivAt_crraUtility γ hc + have hd' : ∀ z' : Z, HasDerivAt P.u (P.consumptionFnOf v z' A ^ (-γ)) + (P.consumptionFnOf v z' A) := fun z' => by + rw [hu]; exact hasDerivAt_crraUtility γ (hc' z') + have heuler := P.euler_le ha hA hAmax hc hd hc' hd' + rw [P.egmMap_eq_rpow hγ0 hβ hc'] + have hKS : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' * P.consumptionFnOf v z' A ^ (-γ) := mul_pos hK hS + have hstep := Real.rpow_le_rpow_of_nonpos hKS (by linarith [heuler]) + (show -(1 / γ) ≤ 0 by rw [neg_nonpos]; positivity) + have hexp : (-γ) * (-(1 / γ)) = 1 := by field_simp + rwa [← Real.rpow_mul hc.le, hexp, Real.rpow_one] at hstep + +/-! ### The borrowing limit, and the kink it makes + +Where the constraint binds the Euler equation is only an inequality, and the endogenous-gridpoint +transfer has to absorb that. It does, and the reason is short: at a constrained state the saving +is `assetFloor`, which is below ANY average of savings, so the step the transfer needs is free +there. -/ + +/-- **The endogenous-gridpoint transfer, with the corner.** Concavity of the consumption +function needs the Euler equation only where the borrowing limit is slack; at the limit the +inequality `hle` suffices, because the saving is then at its minimum. -/ +theorem concaveOn_of_egm_corner {z : Z} {g C h : ℝ → ℝ} + (hC : ConcaveOn ℝ (Icc assetFloor assetCap) C) + (hM : StrictMonoOn (fun A => A + C A) (Icc assetFloor assetCap)) + (hg : ∀ a ∈ Icc assetFloor assetCap, g a ∈ Icc assetFloor assetCap) + (hbud : ∀ a ∈ Icc assetFloor assetCap, + P.income z + (1 + P.interest) * a = g a + h a) + (hle : ∀ a ∈ Icc assetFloor assetCap, h a ≤ C (g a)) + (heq : ∀ a ∈ Icc assetFloor assetCap, assetFloor < g a → h a = C (g a)) : + ConcaveOn ℝ (Icc assetFloor assetCap) h := by + refine ⟨convex_Icc _ _, fun a ha b hb θ φ hθ hφ hθφ => ?_⟩ + simp only [smul_eq_mul] + have hm : θ * a + φ * b ∈ Icc assetFloor assetCap := by + simpa only [smul_eq_mul] using convex_Icc assetFloor assetCap ha hb hθ hφ hθφ + have hA₁ := hg a ha + have hA₂ := hg b hb + have hAθ := hg _ hm + have hAm : θ * g a + φ * g b ∈ Icc assetFloor assetCap := by + simpa only [smul_eq_mul] using convex_Icc assetFloor assetCap hA₁ hA₂ hθ hφ hθφ + have h1 := hbud a ha + have h2 := hbud b hb + have h3 := hbud _ hm + have hcomb : P.income z + (1 + P.interest) * (θ * a + φ * b) + = (θ * g a + φ * g b) + (θ * h a + φ * h b) := by + linear_combination θ * h1 + φ * h2 - P.income z * hθφ + have hAle : g (θ * a + φ * b) ≤ θ * g a + φ * g b := by + rcases eq_or_lt_of_le hAθ.1 with hcorner | hinterior + · rw [← hcorner] + have hsum : θ * assetFloor + φ * assetFloor = assetFloor := by + rw [← add_mul, hθφ, one_mul] + linarith [mul_nonneg hθ (sub_nonneg.mpr hA₁.1), mul_nonneg hφ (sub_nonneg.mpr hA₂.1)] + · have hCc : θ * h a + φ * h b ≤ C (θ * g a + φ * g b) := by + have hca := hle a ha + have hcb := hle b hb + have hmid := hC.2 hA₁ hA₂ hθ hφ hθφ + simp only [smul_eq_mul] at hmid + nlinarith [mul_le_mul_of_nonneg_left hca hθ, mul_le_mul_of_nonneg_left hcb hφ] + have hstep : g (θ * a + φ * b) + C (g (θ * a + φ * b)) + ≤ (θ * g a + φ * g b) + C (θ * g a + φ * g b) := by + rw [← heq _ hm hinterior, ← h3, hcomb]; linarith + by_contra hcon + exact absurd (hM hAm hAθ (not_le.mp hcon)) (not_lt.mpr hstep) + have hval : h (θ * a + φ * b) + = (θ * g a + φ * g b) + (θ * h a + φ * h b) - g (θ * a + φ * b) := by + rw [← hcomb, h3]; ring + rw [hval] + linarith + +/-! ### Carroll and Kimball's induction step, with the Euler equation discharged -/ + +/-- **The interiority the first-order condition needs.** At `(a, z)` the household saves +strictly inside the asset region and strictly below its feasible maximum, and does so again next +period in every income state. This is exactly what fails at the borrowing limit, where +`policy_eq_zero_of_corner_at` shows the constraint binds. -/ +def EulerInterior (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (a : ℝ) : Prop := + assetFloor < P.policyOf (P.toExtended.bellman v) (a, z) + ∧ P.policyOf (P.toExtended.bellman v) (a, z) < P.maxSaving (a, z) + ∧ ∀ z' : Z, P.policyOf v (P.policyOf (P.toExtended.bellman v) (a, z), z') + < P.maxSaving (P.policyOf (P.toExtended.bellman v) (a, z), z') + +/-- **Carroll and Kimball (1996), the induction step for CRRA, with no Euler hypothesis.** + +`concaveOn_consumptionFnOf_bellman_of_euler` assumed the first-order condition; here it is +proved, from the Clausen–Strub envelope run along the iteration. What is assumed instead is +interiority, which is a property of the state rather than of the model, and the reason the +statement is not yet unconditional. -/ +theorem concaveOn_consumptionFnOf_bellman_of_interior {γ : ℝ} (hγ0 : 0 < γ) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z' : Z, ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconcv : ∀ z' : Z, ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmonov : ∀ z' : Z, MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) + (hcW : ∀ a ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hin : ∀ a ∈ Icc assetFloor assetCap, P.EulerInterior v z a) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_consumptionFnOf_bellman_of_euler hγ0 hpos hconcv hmonov z fun a ha => ?_ + obtain ⟨h1, h2, h3⟩ := hin a ha + exact P.egmMap_consumptionFnOf hγ0 hβ hu ha rfl h1 h2 (hcW a ha) h3 + fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z))) + +/-- **Carroll and Kimball (1996), the induction step for CRRA, with the borrowing limit allowed +to BIND.** No interiority at the limit is assumed; the only slackness required is of the asset +CAP, which `crra_policy_lt_assetCap` discharges by calibration and which is a different +condition entirely. + +Where the limit binds the Euler equation degenerates to an inequality, and `concaveOn_of_egm_corner` +absorbs that: the saving is then at its minimum, so it sits below any average of savings, which +is the only thing the transfer needs. -/ +theorem concaveOn_consumptionFnOf_bellman {γ : ℝ} (hγ0 : 0 < γ) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z' : Z, ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconcv : ∀ z' : Z, ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmonov : ∀ z' : Z, MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) + (hcW : ∀ a ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hslackW : ∀ a ∈ Icc assetFloor assetCap, + P.policyOf (P.toExtended.bellman v) (a, z) < P.maxSaving (a, z)) + (hslackv : ∀ A ∈ Icc assetFloor assetCap, ∀ z' : Z, + P.policyOf v (A, z') < P.maxSaving (A, z')) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_of_egm_corner (z := z) + (g := fun a => P.policyOf (P.toExtended.bellman v) (a, z)) (C := P.egmMap v γ z) + (P.concaveOn_egmMap hγ0 z hpos hconcv) + (strictMonoOn_add_egm (P.monotoneOn_egmMap hγ0 z hpos hmonov)) + (fun a _ => P.feasible_subset_region (P.policyOf_mem _ (a, z))) (fun a ha => ?_) + (fun a ha => ?_) fun a ha hfloor => ?_ + · have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + simp only [consumptionFnOf, consumption, hres]; ring + · exact P.egmMap_ge hγ0 hβ hu ha rfl (hslackW a ha) (hcW a ha) + fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z))) + · exact P.egmMap_consumptionFnOf hγ0 hβ hu ha rfl hfloor (hslackW a ha) (hcW a ha) + (hslackv _ (P.feasible_subset_region (P.policyOf_mem _ (a, z)))) + fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z))) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationHARA.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationHARA.lean new file mode 100644 index 000000000..ac0e36b8a --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationHARA.lean @@ -0,0 +1,743 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEuler +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationIterate +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRateStep +public import LeanPool.AiyagariEquilibrium.Models.ImpatientDecline +public import LeanPool.AiyagariEquilibrium.Equilibrium.SignChange +public import LeanPool.AiyagariEquilibrium.Analysis.HARA + +/-! +# Carroll and Kimball beyond CRRA: the HARA induction step + +`IncomeFluctuationCarrollKimball` proves the induction step for `u = crraUtility γ`, where the +Euler equation makes consumption a multiple of the power mean of next period's consumptions and +`powerMean_concave` — Kimball's risk-tolerance aggregation — does the rest. Carroll and Kimball's +theorem is not about CRRA but about HARA, and this file covers the `b ≠ 0` branch, +`u c = crraUtility γ (η + c)`: CRRA with a subsistence level, which includes CRRA itself at +`η = 0`. + +## Nothing new is needed + +Marginal utility is `(η + c) ^ (-γ)`, so the Euler equation reads + + `(η + C(A)) ^ (-γ) = β R · Σ_{z'} π_{z z'} (η + c_{z'}(A)) ^ (-γ)`, + +which is the CRRA equation in the variable `η + c`. So the endogenous-gridpoint map is the CRRA +one applied to SHIFTED consumptions, with the shift removed at the end (`haraEgmMap`), and it is +concave for exactly the same reason: `η + c_{z'}` is concave when `c_{z'}` is, the power mean of +concave positive functions is concave, and a constant shift changes nothing. + +That the shift is harmless is the content of "linear risk tolerance". Risk tolerance +`-u' / u''` is `(η + c) / γ`, affine in consumption, and Kimball's aggregation theorem needs +precisely affineness — the level `η` rides through untouched. + +## What is and is not covered + +The induction STEP is here, for the whole `b ≠ 0` HARA branch. What is not is the assembled +statement: the iteration also needs positive consumption and the asset cap slack. Those are no +longer CRRA-specific — `consumptionFnOf_pos_of_marginal` gets positivity from a FINITE marginal +bound, which is what a shifted CRRA has at zero (`(η + c) ^ (-γ)` at `c = 0` is `η ^ (-γ)`), and +`policyOf_le_of_marginal_bound` was always general. Both branches now carry the assembly +through to a witness: `IncomeFluctuationCARA.caraCal` for CARA, and `StoneGearyWitness.stoneGeary` +for this one. The CARA branch `b = 0`, where marginal utility is `exp (-α c)`, needs a +different aggregator — the soft minimum `-(1/α) log Σ π exp (-α c)` — and is in +`IncomeFluctuationCARA`, on top of `Analysis.SoftMin`. +-/ + +@[expose] public section + +open scoped NNReal +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **The endogenous-gridpoint map for shifted CRRA.** The CRRA map in the variable `η + c`. -/ +noncomputable def haraEgmMap (v : (ℝ × Z) →ᵇ ℝ) (γ η : ℝ) (z : Z) (A : ℝ) : ℝ := + ((P.discount : ℝ) * (1 + P.interest)) ^ (-(1 / γ)) + * powerMean (P.transitionMatrix z) (-γ) (fun z' => η + P.consumptionFnOf v z' A) - η + +@[simp] theorem haraEgmMap_zero_shift (v : (ℝ × Z) →ᵇ ℝ) (γ : ℝ) (z : Z) (A : ℝ) : + P.haraEgmMap v γ 0 z A = P.egmMap v γ z A := by + simp only [haraEgmMap, egmMap, zero_add, sub_zero] + +theorem concaveOn_haraEgmMap {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.haraEgmMap v γ η z) := by + have hmean := concaveOn_powerMean_comp (convex_Icc assetFloor assetCap) + (neg_neg_of_pos hγ) (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) + (f := fun z' a => η + P.consumptionFnOf v z' a) + (fun z' A hA => by linarith [hpos z' A hA]) + (fun z' => ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => by + have h := (hconc z').2 hx hy hθ hφ hθφ + simp only [smul_eq_mul] at h ⊢ + nlinarith [h, hθφ]⟩) + have hk : (0 : ℝ) ≤ ((P.discount : ℝ) * (1 + P.interest)) ^ (-(1 / γ)) := + Real.rpow_nonneg (mul_nonneg P.discount.coe_nonneg P.interest_gt_neg_one.le) _ + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + have hstep := hmean.2 hx hy hθ hφ hθφ + simp only [smul_eq_mul] at hstep ⊢ + simp only [haraEgmMap] + nlinarith [mul_le_mul_of_nonneg_left hstep hk, hθφ] + +theorem monotoneOn_haraEgmMap {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) : + MonotoneOn (P.haraEgmMap v γ η z) (Icc assetFloor assetCap) := by + have hmean := monotoneOn_powerMean_comp (D := Icc assetFloor assetCap) + (neg_neg_of_pos hγ) (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) + (f := fun z' a => η + P.consumptionFnOf v z' a) + (fun z' A hA => by linarith [hpos z' A hA]) + (fun z' x hx y hy hxy => by linarith [hmono z' hx hy hxy]) + intro x hx y hy hxy + simp only [haraEgmMap] + have := mul_le_mul_of_nonneg_left (hmean hx hy hxy) + (Real.rpow_nonneg (mul_nonneg P.discount.coe_nonneg P.interest_gt_neg_one.le) + (-(1 / γ))) + linarith + +/-- **The Euler equation puts consumption on the endogenous grid**, for shifted CRRA. -/ +theorem hara_egm_of_euler {γ η : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (hc : 0 < η + P.consumptionFnOf (P.toExtended.bellman v) z a) + (hc' : ∀ z' : Z, 0 < η + P.consumptionFnOf v z' A) + (heuler : (η + P.consumptionFnOf (P.toExtended.bellman v) z a) ^ (-γ) + = (P.discount : ℝ) * ((1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' * (η + P.consumptionFnOf v z' A) ^ (-γ))) : + P.consumptionFnOf (P.toExtended.bellman v) z a = P.haraEgmMap v γ η z A := by + have hγn : γ ≠ 0 := ne_of_gt hγ0 + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hS : (0 : ℝ) + < ∑ z' : Z, P.transitionMatrix z z' * (η + P.consumptionFnOf v z' A) ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hc' + have hprod : (η + P.consumptionFnOf (P.toExtended.bellman v) z a) ^ (-γ) + = ((P.discount : ℝ) * (1 + P.interest)) + * ∑ z' : Z, P.transitionMatrix z z' * (η + P.consumptionFnOf v z' A) ^ (-γ) := by + rw [heuler]; ring + have hexp : (-γ) * (-(1 / γ)) = 1 := by field_simp + have hinv : ((η + P.consumptionFnOf (P.toExtended.bellman v) z a) ^ (-γ)) ^ (-(1 / γ)) + = η + P.consumptionFnOf (P.toExtended.bellman v) z a := by + rw [← Real.rpow_mul hc.le, hexp, Real.rpow_one] + have hkey : η + P.consumptionFnOf (P.toExtended.bellman v) z a + = ((P.discount : ℝ) * (1 + P.interest)) ^ (-(1 / γ)) + * powerMean (P.transitionMatrix z) (-γ) + (fun z' => η + P.consumptionFnOf v z' A) := by + rw [← hinv, hprod, Real.mul_rpow hK.le hS.le] + simp only [powerMean] + congr 1 + rw [show -(1 / γ) = 1 / (-γ) by field_simp] + simp only [haraEgmMap] + linarith + +/-- **The Euler hypothesis discharged**, for shifted CRRA: interiority is all that is left. -/ +theorem hara_egmMap_consumptionFnOf {γ η : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) + (hu : P.u = haraUtility γ η) {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (hη : 0 ≤ η) (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hA0 : assetFloor < A) (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hint' : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z')) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.consumptionFnOf (P.toExtended.bellman v) z a = P.haraEgmMap v γ η z A := by + have hcη : 0 < η + P.consumptionFnOf (P.toExtended.bellman v) z a := by linarith + have hcη' : ∀ z' : Z, 0 < η + P.consumptionFnOf v z' A := fun z' => by linarith [hc' z'] + have hd : HasDerivAt P.u ((η + P.consumptionFnOf (P.toExtended.bellman v) z a) ^ (-γ)) + (P.consumptionFnOf (P.toExtended.bellman v) z a) := by + rw [hu]; exact hasDerivAt_haraUtility γ hcη + have hd' : ∀ z' : Z, HasDerivAt P.u ((η + P.consumptionFnOf v z' A) ^ (-γ)) + (P.consumptionFnOf v z' A) := fun z' => by + rw [hu]; exact hasDerivAt_haraUtility γ (hcη' z') + exact P.hara_egm_of_euler hγ0 hβ hcη hcη' + (P.euler_eq ha hA hA0 hAmax hint' hc hd hc' hd') + +/-- **Carroll and Kimball (1996), the induction step for shifted CRRA.** Given the Euler +equation, the Bellman operator carries a concave consumption function to a concave one. -/ +theorem concaveOn_consumptionFnOf_bellman_of_hara_euler {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) + {v : (ℝ × Z) →ᵇ ℝ} + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) (z : Z) + (hEuler : ∀ a ∈ Icc assetFloor assetCap, + P.consumptionFnOf (P.toExtended.bellman v) z a + = P.haraEgmMap v γ η z (P.policyOf (P.toExtended.bellman v) (a, z))) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_of_egm (z := z) (g := fun a => P.policyOf (P.toExtended.bellman v) (a, z)) + (C := P.haraEgmMap v γ η z) (P.concaveOn_haraEgmMap hγ hη z hpos hconc) + (strictMonoOn_add_egm (P.monotoneOn_haraEgmMap hγ hη z hpos hmono)) + (fun a _ => P.feasible_subset_region (P.policyOf_mem _ (a, z))) (fun a ha => ?_) + (fun a ha => hEuler a ha) + rw [← hEuler a ha] + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + simp only [consumptionFnOf, consumption, hres] + ring + +/-- **The induction step for shifted CRRA, with the Euler equation proved**, exactly as +`concaveOn_consumptionFnOf_bellman_of_interior` does it for CRRA: what is assumed is interiority, +a property of the state, not of the model. -/ +theorem concaveOn_consumptionFnOf_bellman_of_hara {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = haraUtility γ η) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) + (hcpos : ∀ a ∈ Icc assetFloor assetCap, + 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hint : ∀ a ∈ Icc assetFloor assetCap, P.EulerInterior v z a) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_consumptionFnOf_bellman_of_hara_euler hγ hη hpos hconc hmono z + fun a ha => ?_ + obtain ⟨hA0, hAmax, hint'⟩ := hint a ha + exact P.hara_egmMap_consumptionFnOf hγ hβ hu hη ha rfl hA0 hAmax (hcpos a ha) hint' + (fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z)))) + +/-- **CRRA is the `η = 0` member**, so the HARA step really does generalise the CRRA one rather +than sitting beside it. -/ +theorem concaveOn_consumptionFnOf_bellman_of_crra_via_hara {γ : ℝ} (hγ : 0 < γ) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z', ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z', ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmono : ∀ z', MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) + (hcpos : ∀ a ∈ Icc assetFloor assetCap, + 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hint : ∀ a ∈ Icc assetFloor assetCap, P.EulerInterior v z a) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := + P.concaveOn_consumptionFnOf_bellman_of_hara hγ le_rfl hβ + (by rw [hu, haraUtility_zero_shift]) z hpos hconc hmono hcpos hint + +/-! ### Marginal bounds + +A shifted CRRA has FINITE marginal utility at zero, `η ^ (-γ)`, so like CARA it satisfies no +Inada condition and its consumption floor is an inequality rather than a limit. Both bounds are +the CRRA ones read at `η + c`. -/ + +theorem haraUtility_marginal_bound {γ η R : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) (hη : 0 ≤ η) + {c d : ℝ} (hd : 0 ≤ d) (hdc : d ≤ c) (hcR : c ≤ R) : + (η + R) ^ (-γ) * (c - d) ≤ haraUtility γ η c - haraUtility γ η d := by + have hstep := crra_marginal_bound_Ici (γ := γ) (R := η + R) hγ0 hγ1 + (d := η + d) (c := η + c) (mem_Ici.mpr (by linarith)) (by linarith) (by linarith) + simp only [haraUtility] + have he : η + c - (η + d) = c - d := by ring + rwa [he] at hstep + +/-- The same with the restriction moved from `γ` to the ARGUMENT: a strictly positive +subsistence level keeps `η + c` away from zero, so `γ ≥ 1` — log Stone–Geary included — is +admissible. -/ +theorem haraUtility_marginal_bound_pos {γ η R : ℝ} (hγ0 : 0 < γ) {c d : ℝ} (hd : 0 < η + d) + (hdc : d ≤ c) (hcR : c ≤ R) : + (η + R) ^ (-γ) * (c - d) ≤ haraUtility γ η c - haraUtility γ η d := by + have hstep := crra_marginal_bound (γ := γ) (R := η + R) (c := η + c) (d := η + d) hγ0 hd + (by linarith) (by linarith) + simp only [haraUtility] + have he : η + c - (η + d) = c - d := by ring + rwa [he] at hstep + +/-- **A marginal bound near zero for a strictly positive subsistence level.** -/ +theorem marginalBoundOn_haraUtility_pos {γ η δ : ℝ} (hγ0 : 0 < γ) (hη : 0 < η) (hδ : 0 < δ) : + MarginalBoundOn (Ici (0 : ℝ)) (haraUtility γ η) ((η + δ) ^ (-γ)) := + ⟨δ, hδ, fun _c _c' hc hcc' hc'δ => + haraUtility_marginal_bound_pos hγ0 (by linarith [mem_Ici.mp hc]) hcc'.le hc'δ⟩ + +theorem marginalBoundOn_haraUtility {γ η δ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) (hη : 0 ≤ η) + (hδ : 0 < δ) : MarginalBoundOn (Ici (0 : ℝ)) (haraUtility γ η) ((η + δ) ^ (-γ)) := + ⟨δ, hδ, fun _c _c' hc hcc' hc'δ => + haraUtility_marginal_bound hγ0 hγ1 hη (mem_Ici.mp hc) hcc'.le hc'δ⟩ + +/-! ### The Euler inequality, and the corner -/ + +theorem haraEgmMap_eq_rpow {γ η : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) (hη : 0 ≤ η) + {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {A : ℝ} (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.haraEgmMap v γ η z A = ((P.discount : ℝ) * (1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' + * (η + P.consumptionFnOf v z' A) ^ (-γ)) ^ (-(1 / γ)) - η := by + have hγn : γ ≠ 0 := ne_of_gt hγ0 + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hpos : ∀ z' : Z, 0 < η + P.consumptionFnOf v z' A := fun z' => by linarith [hc' z'] + have hS : (0 : ℝ) < ∑ z' : Z, P.transitionMatrix z z' + * (η + P.consumptionFnOf v z' A) ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hpos + rw [Real.mul_rpow hK.le hS.le] + simp only [haraEgmMap, powerMean] + congr 2 + rw [show -(1 / γ) = 1 / (-γ) by field_simp] + +/-- **The Euler INEQUALITY in endogenous-gridpoint form**, for shifted CRRA: it holds AT the +borrowing limit, where the equality fails. -/ +theorem hara_egmMap_ge {γ η : ℝ} (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) {v : (ℝ × Z) →ᵇ ℝ} {z : Z} {a A : ℝ} + (ha : a ∈ Icc assetFloor assetCap) + (hA : P.policyOf (P.toExtended.bellman v) (a, z) = A) + (hAmax : A < P.maxSaving (a, z)) + (hc : 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A) : + P.consumptionFnOf (P.toExtended.bellman v) z a ≤ P.haraEgmMap v γ η z A := by + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hK : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + have hcη : 0 < η + P.consumptionFnOf (P.toExtended.bellman v) z a := by linarith + have hcη' : ∀ z' : Z, 0 < η + P.consumptionFnOf v z' A := fun z' => by linarith [hc' z'] + have hS : (0 : ℝ) < ∑ z' : Z, P.transitionMatrix z z' + * (η + P.consumptionFnOf v z' A) ^ (-γ) := + sum_rpow_pos (P.transitionMatrix_nonneg z) (P.transitionMatrix_sum z) hcη' + have hd : HasDerivAt P.u ((η + P.consumptionFnOf (P.toExtended.bellman v) z a) ^ (-γ)) + (P.consumptionFnOf (P.toExtended.bellman v) z a) := by + rw [hu]; exact hasDerivAt_haraUtility γ hcη + have hd' : ∀ z' : Z, HasDerivAt P.u ((η + P.consumptionFnOf v z' A) ^ (-γ)) + (P.consumptionFnOf v z' A) := fun z' => by + rw [hu]; exact hasDerivAt_haraUtility γ (hcη' z') + have heuler := P.euler_le ha hA hAmax hc hd hc' hd' + rw [P.haraEgmMap_eq_rpow hγ0 hβ hη hc'] + have hKS : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) + * ∑ z' : Z, P.transitionMatrix z z' * (η + P.consumptionFnOf v z' A) ^ (-γ) := + mul_pos hK hS + have hstep := Real.rpow_le_rpow_of_nonpos hKS (by linarith [heuler]) + (show -(1 / γ) ≤ 0 by rw [neg_nonpos]; positivity) + have hexp : (-γ) * (-(1 / γ)) = 1 := by field_simp + rw [← Real.rpow_mul hcη.le, hexp, Real.rpow_one] at hstep + linarith + +/-- **Carroll and Kimball for shifted CRRA, across the borrowing-limit kink.** -/ +theorem concaveOn_consumptionFnOf_bellman_of_hara_corner {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = haraUtility γ η) {v : (ℝ × Z) →ᵇ ℝ} (z : Z) + (hpos : ∀ z' : Z, ∀ A ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z' A) + (hconc : ∀ z' : Z, ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFnOf v z')) + (hmono : ∀ z' : Z, MonotoneOn (P.consumptionFnOf v z') (Icc assetFloor assetCap)) + (hcW : ∀ a ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (hslackW : ∀ a ∈ Icc assetFloor assetCap, + P.policyOf (P.toExtended.bellman v) (a, z) < P.maxSaving (a, z)) + (hslackv : ∀ A ∈ Icc assetFloor assetCap, ∀ z' : Z, + P.policyOf v (A, z') < P.maxSaving (A, z')) : + ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf (P.toExtended.bellman v) z) := by + refine P.concaveOn_of_egm_corner (z := z) + (g := fun a => P.policyOf (P.toExtended.bellman v) (a, z)) (C := P.haraEgmMap v γ η z) + (P.concaveOn_haraEgmMap hγ hη z hpos hconc) + (strictMonoOn_add_egm (P.monotoneOn_haraEgmMap hγ hη z hpos hmono)) + (fun a _ => P.feasible_subset_region (P.policyOf_mem _ (a, z))) (fun a ha => ?_) + (fun a ha => ?_) fun a ha hfloor => ?_ + · have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + simp only [consumptionFnOf, consumption, hres]; ring + · exact P.hara_egmMap_ge hγ hβ hη hu ha rfl (hslackW a ha) (hcW a ha) + fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z))) + · exact P.hara_egmMap_consumptionFnOf hγ hβ hu hη ha rfl hfloor (hslackW a ha) (hcW a ha) + (hslackv _ (P.feasible_subset_region (P.policyOf_mem _ (a, z)))) + fun z' => hpos z' _ (P.feasible_subset_region (P.policyOf_mem _ (a, z))) + +/-! ### The proportional bound on saving + +`policyOf_le_of_marginal_bound` prices a deviation against the LEVEL of the continuation, which +for a bounded utility is far too crude to keep the cap slack at any interesting discount factor. +The CRRA development fixes that with `crra_policyOf_le_mul`: deviate to a FRACTION `θ` of the +saving rather than a fixed amount, and price the extra consumption at the margin where it lands. +The bound then involves the continuation's OSCILLATION and is proportional to consumption. + +The shifted-CRRA version is that argument with the marginal step read at `η + c`. Everything +else — the deviation, the concavity of the continuation, the oscillation — knows nothing about +preferences. -/ + +theorem hara_policyOf_le_mul {γ η θ G : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hθ0 : 0 < θ) (hθ1 : θ < 1) (hu : P.u = haraUtility γ η) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices assetFloor assetCap v) (hG : 0 ≤ G) + (hosc : P.OscOn v G) {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) + (hcpos : 0 < P.consumptionFnOf v z a) : + P.policyOf v (a, z) - assetFloor + ≤ ((P.discount : ℝ) * G / θ) + * (η + P.consumptionFnOf v z a + + (1 - θ) * (P.policyOf v (a, z) - assetFloor)) ^ γ := by + refine P.policyOf_le_mul_of_power_marginal hθ0 hθ1 hv hG hosc ?_ ha z (by linarith) + intro c d _ hcd hc + rw [hu] + exact haraUtility_marginal_bound_pos hγ0 hc hcd le_rfl + +/-- **Saving never reaches the asset cap**, for shifted CRRA. -/ +theorem hara_policyOf_lt_assetCap {γ η θ G : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hθ0 : 0 < θ) (hθ1 : θ < 1) (hu : P.u = haraUtility γ η) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices assetFloor assetCap v) (hG : 0 ≤ G) + (hosc : P.OscOn v G) (hcpos : ∀ z : Z, ∀ a ∈ Icc assetFloor assetCap, + 0 < P.consumptionFnOf v z a) + (hlt : ((P.discount : ℝ) * G / θ) + * (η + P.maxIncome + (1 + P.interest - θ) * assetCap - (1 - θ) * assetFloor) ^ γ + < assetCap - assetFloor) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + P.policyOf v (a, z) < assetCap := by + exact P.policyOf_lt_cap_of_power_bound hγ0.le hθ0.le hθ1.le + (div_nonneg (mul_nonneg P.discount.coe_nonneg hG) hθ0.le) ha z + (by linarith [hcpos z a ha]) + (P.hara_policyOf_le_mul hγ0 hη hθ0 hθ1 hu hv hG hosc ha z (hcpos z a ha)) hlt + +/-! ### The assembly -/ + +theorem concaveOn_consumptionFnOf_iterates_of_hara {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = haraUtility γ η) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc assetFloor assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hslack : ∀ n : ℕ, ∀ a ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < P.maxSaving (a, z)) : + ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z) := by + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap + ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact concaveSlices_zero + | succ k ih => + rw [Function.iterate_succ_apply'] + exact P.concaveSlices_bellman ih + intro n + induction n with + | zero => exact P.concaveOn_consumptionFnOf_zero + | succ k ih => + intro z + have hnext : ∀ a ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf + (P.toExtended.bellman ((P.toExtended.bellman)^[k] (0 : (ℝ × Z) →ᵇ ℝ))) z a := by + intro a ha + have := hpos (k + 1) z a ha + rwa [Function.iterate_succ_apply'] at this + have hnextslack : ∀ a ∈ Icc assetFloor assetCap, P.policyOf + (P.toExtended.bellman ((P.toExtended.bellman)^[k] (0 : (ℝ × Z) →ᵇ ℝ))) (a, z) + < P.maxSaving (a, z) := by + intro a ha + have := hslack (k + 1) a ha z + rwa [Function.iterate_succ_apply'] at this + rw [Function.iterate_succ_apply'] + exact P.concaveOn_consumptionFnOf_bellman_of_hara_corner hγ hη hβ hu z + (fun z' A hA => hpos k z' A hA) ih + (fun z' x hx y hy hxy => P.consumptionFnOf_mono (hslices k) hx hy hxy) + hnext hnextslack (fun A hA z' => hslack k A hA z') + +/-- **Carroll and Kimball for shifted CRRA, from two inequalities.** The same two the CARA +assembly needs, and for the same reasons: marginal utility at zero is finite, so the consumption +floor has to be earned, and the cap has to sit above the saving the marginal bound allows. -/ +theorem concaveOn_consumptionFn_of_hara {γ η δ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) (hη : 0 ≤ η) + (hδ : 0 < δ) (hβ : 0 < (P.discount : ℝ)) (hb : P.Bounded) (hu : P.u = haraUtility γ η) + (hfloor : (P.discount : ℝ) * P.oscSlopeConst P.oscSpread < (η + δ) ^ (-γ)) + (hcap : assetFloor + 4 * (P.discount : ℝ) + * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount)) + / (η + P.maxConsumption) ^ (-γ) < assetCap) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap + ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact concaveSlices_zero + | succ k ih => + rw [Function.iterate_succ_apply'] + exact P.concaveSlices_bellman ih + have hM : MarginalBoundOn P.dom P.u ((η + δ) ^ (-γ)) := by + rw [hb, hu] + exact marginalBoundOn_haraUtility hγ0 hγ1 hη hδ + have hpos : ∀ n : ℕ, ∀ z' : Z, ∀ a ∈ Icc assetFloor assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z' a := + fun n z' a ha => P.consumptionFnOf_pos_of_marginal hM P.oscSpread_nonneg (hslices n) + (P.oscOn_iterate n) hfloor z' ha + have hmpos : (0 : ℝ) < (η + P.maxConsumption) ^ (-γ) := + Real.rpow_pos_of_pos (by linarith [P.maxConsumption_pos]) _ + have hslack : ∀ n : ℕ, ∀ a ∈ Icc assetFloor assetCap, ∀ z' : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z') < P.maxSaving (a, z') := by + intro n a ha z' + refine P.policyOf_lt_maxSaving_of_pos (hpos n z' a ha) ?_ + have hbound := P.policyOf_le_of_marginal_bound (hslices n) hmpos ?_ ha z' + · have hn := P.norm_iterate_le n + have h4 : (0 : ℝ) ≤ 4 * (P.discount : ℝ) := by positivity + have hstep := mul_le_mul_of_nonneg_left hn h4 + have hdiv : 4 * (P.discount : ℝ) * ‖(P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)‖ + / (η + P.maxConsumption) ^ (-γ) + ≤ 4 * (P.discount : ℝ) + * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount)) + / (η + P.maxConsumption) ^ (-γ) := by + rw [div_le_div_iff_of_pos_right hmpos] + linarith + linarith + · intro c d hd hdc hcmax + rw [hu] + exact haraUtility_marginal_bound hγ0 hγ1 hη (P.nonneg_of_mem_dom hd) hdc hcmax + exact P.concaveOn_consumptionFn_of_iterates + (P.concaveOn_consumptionFnOf_iterates_of_hara hγ0 hη hβ hu hpos hslack) z + +/-! ### The two comparative-statics corollaries, for shifted CRRA + +Light's Theorem 1 and the impatience-driven decline both take the marginal-utility hypotheses +abstractly, so shifted CRRA discharges them the same way CRRA does — with `(η + c) ^ (-γ)` in +place of `c ^ (-γ)`. Relative risk aversion of the shifted family is `γc/(η + c) ≤ γ`, so a +NON-NEGATIVE subsistence level only helps Light's condition. -/ + +theorem hara_marginal_antitone {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) : + AntitoneOn (fun c => (η + c) ^ (-γ)) (Ioi (0 : ℝ)) := fun x hx y _ hxy => + Real.rpow_le_rpow_of_nonpos (by linarith [mem_Ioi.mp hx]) (by linarith) (by linarith) + +theorem hara_marginal_pos {γ η : ℝ} (hη : 0 ≤ η) {c : ℝ} (hc : 0 < c) : + 0 < (η + c) ^ (-γ) := Real.rpow_pos_of_pos (by linarith) _ + +/-- **Light (2018) Theorem 1 for shifted CRRA.** -/ +theorem policy_mono_withRate_hara {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + {γ η : ℝ} (hγ0 : 0 < γ) (hγ1 : γ ≤ 1) (hη : 0 ≤ η) (hu : P.u = haraUtility γ η) + {r₁ r₂ : ℝ} (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hposw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := + P.policy_mono_withRate' h₁ h₂ hr (du := fun c => (η + c) ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_haraUtility γ (by linarith)) + (hara_marginal_antitone hγ0 hη) + (by + have hrra := (relativeRiskAversionLeOne_haraUtility hγ0 hγ1 hη).monotoneOn_mul_deriv + (du := fun c => (η + c) ^ (-γ)) + (fun c hc => hasDerivAt_haraUtility γ (by have := mem_Ioi.mp hc; linarith)) + exact hrra) + (fun y hy => (hara_marginal_pos hη (mem_Ioi.mp hy)).le) + hposv hposw hslackv hslackw hcons ha z + +/-- **Worst-state decline for shifted CRRA**: the decline condition is impatience. -/ +theorem hara_exists_exhaust_of_impatient {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + (hpos : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, 0 < P.consumptionFn z x) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a₀ : ℝ} (ha₀ : assetFloor < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc assetFloor a₀, P.policy (a, z₀) = assetFloor) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := + P.exists_exhaust_of_impatient hβR hiid (du := fun c => (η + c) ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_haraUtility γ (by linarith)) + (hara_marginal_antitone hγ0 hη) + (fun c hc => hara_marginal_pos hη hc) hpos hslack hz₀ ha₀ hle hzero + +/-! ### The supply floor + +The household saves something, so aggregate capital is positive. The shape is the CRRA one: the +cost of saving a little is priced at the margin where consumption is lowest, the gain at the +margin in the bad successor state, and the comparison is between two marginal utilities. With a +subsistence level both are read at `η + c`. -/ + +theorem hara_cost_of_saving {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) {m h R b : ℝ} (hh0 : 0 < h) + (hhm : h < m) (hmR : m ≤ R) (hb0 : 0 ≤ b) (hbh : b ≤ h) : + haraUtility γ η (R - b) - haraUtility γ η (R - h) ≤ (η + m - h) ^ (-γ) * h := by + have hstep := crra_cost_of_saving (γ := γ) (m := η + m) (h := h) (R := η + R) (b := b) hγ hh0 + (by linarith) (by linarith) hb0 hbh + have e1 : η + R - b = η + (R - b) := by ring + have e2 : η + R - h = η + (R - h) := by ring + rw [e1, e2] at hstep + simpa only [haraUtility] using hstep + +/-- **The continuation's gain, for shifted CRRA.** -/ +theorem hara_cont_sub_ge_gen {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + (z z₀ : Z) {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) (hxy : x ≤ y) : + P.transitionMatrix z z₀ * ((η + P.income z₀ + (1 + P.interest) * y) ^ (-γ) + * ((1 + P.interest) * (y - x))) + ≤ P.cont z y - P.cont z x := by + subst hfl + have hRh : 0 ≤ (1 + P.interest) * (y - x) := + mul_nonneg P.interest_gt_neg_one.le (sub_nonneg.mpr hxy) + refine P.cont_sub_ge_of_utility_bound hpc z z₀ hx hy hxy ?_ + intro c hc hcle + simp only [sub_zero] at hcle + rw [hu] + have hR : c + (1 + P.interest) * (y - x) + ≤ P.income z₀ + (1 + P.interest) * y := by nlinarith only [hcle] + have hstep := haraUtility_marginal_bound_pos (γ := γ) (η := η) + (R := P.income z₀ + (1 + P.interest) * y) hγ0 (by linarith) + (le_add_of_nonneg_right hRh) hR + simpa only [add_assoc, add_sub_cancel_left] using hstep + +/-- **Saving is bounded below**, for shifted CRRA, when the gain beats the cost. -/ +theorem hara_policy_ge_of_gain {γ η : ℝ} (hγ : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hfl : assetFloor = 0) + (hpc : P.PositiveConsumption) (z₁ : Z) {h : ℝ} (hh0 : 0 < h) (hhcap : h ≤ assetCap) + (hhinc : h < P.income z₁) + (hgain : (η + P.income z₁ - h) ^ (-γ) * h + < P.discount * (P.cont z₁ h - P.cont z₁ (h / 2))) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + h / 2 ≤ P.policy (a, z₁) := by + refine P.policy_ge_of_cost hfl hpc z₁ hh0 hhcap hhinc (fun R b hm hb0 hbh => ?_) hgain ha + rw [hu] + exact hara_cost_of_saving hγ hη hh0 hhinc hm hb0 hbh + +/-! ### The corner condition, in primitives + +The corner test of `policy_eq_zero_of_corner_at` is `β L < m`: the discounted slope of the value +function against the marginal value of consumption. Both sides have closed forms for shifted +CRRA, so the test becomes one inequality among the primitives. + +* The value function's slope is bounded by `slopeBoundU`, amplified by the return and the + discounting. Concavity prices `slopeBoundU` at the margin halfway down the minimum + consumption: `(η + minConsumption / 2) ^ (-γ)`. +* The marginal value at `(a, z₀)` for `a ≤ a₀` is at least `(η + income z₀ + R a₀) ^ (-γ)`. + +A sufficient condition for the corner to hold below `a₀` is + + `β · (η + minConsumption/2) ^ (-γ) · R / (1 - β R) < (η + income z₀ + R a₀) ^ (-γ)`, + +which is checkable by `norm_num` at any calibration. -/ + +/-- The secant slope is at most the marginal utility at the left end. -/ +theorem haraUtility_marginal_bound_above {γ η : ℝ} (hγ0 : 0 < γ) {c d : ℝ} (hd : 0 < η + d) + (hdc : d ≤ c) : + haraUtility γ η c - haraUtility γ η d ≤ (η + d) ^ (-γ) * (c - d) := by + have hstep := crra_marginal_bound_above (γ := γ) (c := η + c) (d := η + d) hγ0 hd + (by linarith) + have he : η + c - (η + d) = c - d := by ring + rw [he] at hstep + simpa only [haraUtility] using hstep + +/-- **The slope constant in closed form.** -/ +theorem slopeBoundU_le_hara {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) (hu : P.u = haraUtility γ η) : + P.slopeBoundU ≤ (η + P.minConsumption / 2) ^ (-γ) := by + have hmin := P.minConsumption_pos + simp only [IncomeFluctuation.slopeBoundU, slopeBound, hu] + rw [div_le_iff₀ (by linarith)] + have hstep := haraUtility_marginal_bound_above (γ := γ) (η := η) (c := P.minConsumption) + (d := P.minConsumption / 2) hγ0 (by linarith) (by linarith) + have he : P.minConsumption - P.minConsumption / 2 = P.minConsumption / 2 := by ring + rw [he] at hstep + exact hstep + +/-- The Lipschitz constant of the value function, in primitives. -/ +noncomputable def haraLipschitz (γ η : ℝ) : ℝ := + (η + P.minConsumption / 2) ^ (-γ) * (1 + P.interest) + / (1 - P.discount * (1 + P.interest)) + +theorem haraLipschitz_nonneg {γ η : ℝ} (hη : 0 ≤ η) + (hβR : P.discount * (1 + P.interest) < 1) : 0 ≤ P.haraLipschitz γ η := by + have hR := P.interest_gt_neg_one + have hmin := P.minConsumption_pos + exact div_nonneg (mul_nonneg (Real.rpow_nonneg (by linarith) _) hR.le) (by linarith) + +/-- **The value function is Lipschitz**, with the constant read off the primitives. -/ +theorem hara_valueFunction_lipschitz {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hβR : P.discount * (1 + P.interest) < 1) : + ∀ z : Z, ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| + ≤ P.haraLipschitz γ η * |x - y| := by + have hR := P.interest_gt_neg_one + have hne : (0 : ℝ) < 1 - P.discount * (1 + P.interest) := by linarith + refine P.valueFunction_lipschitz (P.haraLipschitz_nonneg hη hβR) ?_ + have hslope := P.slopeBoundU_le_hara hγ0 hη hu + have hkey : ((η + P.minConsumption / 2) ^ (-γ) + + P.discount * P.haraLipschitz γ η) * (1 + P.interest) = P.haraLipschitz γ η := by + simp only [haraLipschitz] + field_simp + ring + have hmul : (P.slopeBoundU + P.discount * P.haraLipschitz γ η) * (1 + P.interest) + ≤ ((η + P.minConsumption / 2) ^ (-γ) + P.discount * P.haraLipschitz γ η) + * (1 + P.interest) := + mul_le_mul_of_nonneg_right (by linarith) hR.le + rw [hkey] at hmul + exact hmul + +/-- **The corner condition from primitives.** One inequality, `norm_num`-checkable. -/ +theorem hara_policy_eq_zero_of_primitives {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 < η) + (hu : P.u = haraUtility γ η) (hfl : assetFloor = 0) + (hβR : P.discount * (1 + P.interest) < 1) (z₀ : Z) {a₀ : ℝ} + (ha₀cap : a₀ ≤ assetCap) + (hcond : P.discount * P.haraLipschitz γ η + < (η + P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) + {a : ℝ} (ha : a ∈ Icc assetFloor a₀) : P.policy (a, z₀) = assetFloor := by + subst hfl + have hR := P.interest_gt_neg_one + have hmem : a ∈ Icc (0 : ℝ) assetCap := ⟨ha.1, le_trans ha.2 ha₀cap⟩ + have hres : P.resources (a, z₀) - 0 ≤ P.income z₀ + (1 + P.interest) * a₀ := by + simp only [IncomeFluctuation.resources, max_eq_right ha.1, sub_zero] + have := mul_le_mul_of_nonneg_left ha.2 hR.le + linarith + have hres0 : (0 : ℝ) < P.resources (a, z₀) - 0 := by + have := P.assetFloor_lt_resources (a, z₀) + linarith + refine P.policy_eq_zero_of_corner_at (L := P.haraLipschitz γ η) + (m := (η + P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) + (P.hara_valueFunction_lipschitz hγ0 hη.le hu hβR) hmem ?_ hcond + intro c d hd hdc hc + have hd0 : (0 : ℝ) ≤ d := P.nonneg_of_mem_dom hd + have hbound := haraUtility_marginal_bound_pos (γ := γ) (η := η) + (R := P.resources (a, z₀) - 0) hγ0 (show (0 : ℝ) < η + d by linarith) hdc hc + rw [hu] + refine le_trans (mul_le_mul_of_nonneg_right ?_ (by linarith)) hbound + exact rpow_neg_antitone hγ0 (by linarith) (by linarith) + +section Measure + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- **Positive aggregate capital for shifted CRRA, from primitives.** -/ +theorem hara_le_aggregateCapital_of_gain {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) {z₁ z₀ : Z} {p₀ h : ℝ} + (hh0 : 0 < h) (hhcap : h ≤ assetCap) (hhinc : h < P.income z₁) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + (hgain : (η + P.income z₁ - h) ^ (-γ) * h + < P.discount * (P.transitionMatrix z₁ z₀ + * ((η + P.income z₀ + (1 + P.interest) * h) ^ (-γ) * ((1 + P.interest) * (h / 2))))) : + h / 2 * p₀ ≤ P.aggregateCapital μ := by + subst hfl + refine P.le_aggregateCapital hμ (by linarith) hp fun a ha => ?_ + refine P.hara_policy_ge_of_gain hγ0 hη hu rfl hpc z₁ hh0 hhcap hhinc ?_ ha + refine lt_of_lt_of_le hgain (mul_le_mul_of_nonneg_left ?_ P.discount.coe_nonneg) + have hhalf : h / 2 ∈ Icc (0 : ℝ) assetCap := ⟨by linarith, by linarith⟩ + have hfull : h ∈ Icc (0 : ℝ) assetCap := ⟨hh0.le, hhcap⟩ + have hgen := P.hara_cont_sub_ge_gen hγ0 hη hu rfl hpc z₁ z₀ hhalf hfull (by linarith) + rw [show h - h / 2 = h / 2 from by ring] at hgen + exact hgen + +/-- **The supply floor with the two powers bounded separately.** The gain condition compares +`(η + y₁ - t)^(-γ)`, the marginal cost of saving `t` in the high state, against +`(η + y₀ + Rt)^(-γ)`, the marginal value of having it in the low state. Bounding the first from +above and the second from below leaves ONE inequality among the bounds — and when a base sits on +the right side of one, its bound needs no root at all (`rpow_neg_le_one_of_one_le`, +`rpow_neg_le_inv_of_le_one`). + +This is what makes a supply floor a calibration check rather than a proof. -/ +theorem hara_le_aggregateCapital_of_bounds {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) (hfl : assetFloor = 0) (hpc : P.PositiveConsumption) + {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) {z₁ z₀ : Z} {p₀ t U L : ℝ} + (ht0 : 0 < t) (htcap : t ≤ assetCap) (htinc : t < P.income z₁) + (hp : ∀ z, p₀ ≤ P.transitionMatrix z z₁) + (hU : (η + P.income z₁ - t) ^ (-γ) ≤ U) + (hL : L ≤ (η + P.income z₀ + (1 + P.interest) * t) ^ (-γ)) + (hcond : U * t + < (P.discount : ℝ) * (P.transitionMatrix z₁ z₀ * (L * ((1 + P.interest) * (t / 2))))) : + t / 2 * p₀ ≤ P.aggregateCapital μ := by + refine P.hara_le_aggregateCapital_of_gain hγ0 hη hu hfl hpc hμ (z₀ := z₀) ht0 htcap htinc hp ?_ + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hπ : (0 : ℝ) ≤ P.transitionMatrix z₁ z₀ := P.transitionMatrix_nonneg _ _ + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hcost : (η + P.income z₁ - t) ^ (-γ) * t ≤ U * t := + mul_le_mul_of_nonneg_right hU ht0.le + have hgainL : (P.discount : ℝ) * (P.transitionMatrix z₁ z₀ * (L * ((1 + P.interest) * (t / 2)))) + ≤ (P.discount : ℝ) * (P.transitionMatrix z₁ z₀ + * ((η + P.income z₀ + (1 + P.interest) * t) ^ (-γ) * ((1 + P.interest) * (t / 2)))) := + mul_le_mul_of_nonneg_left + (mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_right hL (by positivity)) hπ) hβ + linarith + +end Measure + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationIterate.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationIterate.lean new file mode 100644 index 000000000..29616dade --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationIterate.lean @@ -0,0 +1,503 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEuler +public import LeanPool.AiyagariEquilibrium.Models.CRRAConstants + +/-! +# Positivity and monotonicity along the iteration + +Carroll and Kimball's induction step needs the continuation's consumption function to be +positive, concave and nondecreasing. Concavity is the induction hypothesis. This file proves +positivity and consumption monotonicity against arbitrary continuations, together with saving +bounds uniform over the iteration. Saving-policy monotonicity is shared with the fixed-point +case in `IncomeFluctuationMonotone`. + +The exchange arguments use `objROf`, the real objective against an arbitrary continuation. +Continuation value depends on chosen saving and the income state, never on current assets, +so shifting household plans between asset levels cancels the continuation terms. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### Positivity -/ + +/-- **Consumption never vanishes at the optimum, whatever the continuation.** With utility +unbounded below the reward at zero consumption is `⊥`, and the optimum is a real number. -/ +theorem consumptionFnOf_pos (hd : P.Unbounded) (v : (ℝ × Z) →ᵇ ℝ) {z : Z} {a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) : 0 < P.consumptionFnOf v z a := by + have hopt := P.policyOf_optimal v (a, z) + have hne : P.toExtended.reward ((a, z), P.policyOf v (a, z)) ≠ ⊥ := by + intro hbot + rw [ExtendedStochasticProgram.objectiveE, hbot, EReal.bot_add] at hopt + exact (EReal.bot_ne_coe _) hopt + have hmem := P.consumption_mem_dom_of_ne_bot (s := (a, z)) ha (P.policyOf_mem v (a, z)) hne + rw [hd] at hmem + exact hmem + +/-- **Consumption is positive at the optimum against every continuation with concave slices.** +The induction sees the iterates, not the value function, so this is what it needs. With utility +unbounded below it is automatic; for the bounded CES family it is earned from a marginal Inada +condition, +exactly as `PositiveConsumption` is. -/ +def PositiveConsumptionAll : Prop := + ∀ (v : (ℝ × Z) →ᵇ ℝ), ConcaveSlices assetFloor assetCap v → + ∀ (z : Z), ∀ a ∈ Icc assetFloor assetCap, 0 < P.consumptionFnOf v z a + +theorem positiveConsumptionAll_of_unbounded (hd : P.Unbounded) : P.PositiveConsumptionAll := + fun v _ _ _ ha => P.consumptionFnOf_pos hd v ha + +/-- **Positive consumption for free, when the cap is narrow relative to income.** If the whole +asset range is worth less than one period's minimum consumption, no feasible saving can exhaust +resources, so consumption is positive whatever the continuation. Utilities with FINITE marginal +value at zero — CARA, shifted CRRA — have no Inada condition to appeal to, and this is the +alternative: keep the household away from zero consumption by the budget rather than by the +preferences. -/ +theorem positiveConsumptionAll_of_rich (hrich : assetCap - assetFloor < P.minConsumption) : + P.PositiveConsumptionAll := by + intro v hv z a ha + have h1 : P.minConsumption ≤ P.resources (a, z) - assetFloor := + P.minConsumption_le_consumption_floor (a, z) + have h2 : P.policyOf v (a, z) ≤ assetCap := + (P.feasible_subset_region (P.policyOf_mem v (a, z))).2 + simp only [consumptionFnOf, consumption] + linarith + +/-! ### Monotonicity of the consumption function -/ + +/-! ### Saving stays strictly inside the feasible set + +Positive consumption already keeps the saving below resources; what is left is the asset CAP, +and that is the single quantitative input the induction still needs. -/ + +theorem policyOf_lt_maxSaving (hpc : P.PositiveConsumptionAll) {v : (ℝ × Z) →ᵇ ℝ} + (hv : ConcaveSlices assetFloor assetCap v) {a : ℝ} {z : Z} + (ha : a ∈ Icc assetFloor assetCap) (hcap : P.policyOf v (a, z) < assetCap) : + P.policyOf v (a, z) < P.maxSaving (a, z) := by + rw [P.maxSaving_eq] + refine lt_min hcap ?_ + have hc := hpc v hv z a ha + simp only [consumptionFnOf, consumption] at hc + linarith + +/-- The same, with positivity supplied only at the continuation in hand. Utilities with finite +marginal value at zero have no `PositiveConsumptionAll`, but they do have positivity against the +continuations the iteration produces. -/ +theorem policyOf_lt_maxSaving_of_pos {v : (ℝ × Z) →ᵇ ℝ} {a : ℝ} {z : Z} + (hc : 0 < P.consumptionFnOf v z a) (hcap : P.policyOf v (a, z) < assetCap) : + P.policyOf v (a, z) < P.maxSaving (a, z) := by + rw [P.maxSaving_eq] + refine lt_min hcap ?_ + simp only [consumptionFnOf, consumption] at hc + linarith + +/-! ### The oscillation of a continuation + +`2 ‖v‖` is the wrong bound on the continuation's spread: it is not translation-invariant, and +for CES — where utility is bounded and the level of `v` is large compared with its variation — +it is far too crude. `oscGap` is the right one, and what it bounds is the OSCILLATION. This +section carries that along the iteration. -/ + +/-- At a zero borrowing limit the spread is `CRRAConstants.oscGap`. -/ +theorem oscSpread_eq_oscGap {assetCap : ℝ} (Q : IncomeFluctuation Z 0 assetCap) : + Q.oscSpread = Q.oscGap := rfl + +theorem oscOn_zero : P.OscOn (0 : (ℝ × Z) →ᵇ ℝ) 0 := fun _ _ _ _ _ _ => by simp + +/-- **The Bellman operator adds the reward's spread and contracts the continuation's.** -/ +theorem oscOn_bellman {v : (ℝ × Z) →ᵇ ℝ} {G : ℝ} (h : P.OscOn v G) : + P.OscOn (P.toExtended.bellman v) + (P.u P.maxConsumption - P.u P.minConsumption + P.discount * G) := by + intro x hx y hy z z' + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hba : ∀ (t : ℝ) (w : Z), (P.toExtended.bellman v) (t, w) + = P.toExtended.bellmanFn v (t, w) := fun t w => rfl + -- the optimum at `(x, z)`, priced above + obtain ⟨-, hup⟩ := P.bellmanFn_eq_of_optimal (v := v) (s := (x, z)) hx + (P.policyOf_mem v (x, z)) (P.policyOf_optimal v (x, z)) + have hAmem : P.policyOf v (x, z) ∈ Icc assetFloor assetCap := + P.feasible_subset_region (P.policyOf_mem v (x, z)) + have hcle : P.u (P.consumptionFnOf v z x) ≤ P.u P.maxConsumption := + P.monotoneOn_u_dom (P.consumption_policyOf_mem_dom v hx) P.maxConsumption_mem_dom + (P.consumption_le_maxConsumption (s := (x, z)) hx (P.policyOf_mem v (x, z)).1) + -- saving the minimum at `(y, z')`, priced below + have hfl : assetFloor ∈ P.toExtended.feasible (y, z') := ⟨le_rfl, le_max_left _ _⟩ + have hmin : P.minConsumption ≤ P.consumption (y, z') assetFloor := + P.minConsumption_le_consumption_floor (y, z') + have hminpos : 0 < P.consumption (y, z') assetFloor := + lt_of_lt_of_le P.minConsumption_pos hmin + have hlow := P.lazyValue_le_dom v z' hy hfl (P.mem_dom_of_pos hminpos) + have hulow : P.u P.minConsumption ≤ P.u (P.consumption (y, z') assetFloor) := + P.monotoneOn_u_dom (P.mem_dom_of_pos P.minConsumption_pos) + (P.mem_dom_of_pos hminpos) hmin + simp only [lazyValue] at hlow + have hcont : P.contOf v z (P.policyOf v (x, z)) - P.contOf v z' assetFloor ≤ G := + P.contOf_sub_le_osc h hAmem ⟨le_rfl, P.assetFloor_le_assetCap⟩ z z' + have hscaled := mul_le_mul_of_nonneg_left hcont hβ + rw [hba x z, hba y z', hup] + simp only [contOf] at hscaled hlow ⊢ + have hcx : P.consumptionFnOf v z x = P.consumption (x, z) (P.policyOf v (x, z)) := rfl + rw [hcx] at hcle + have hres : P.resources (y, z') - assetFloor = P.consumption (y, z') assetFloor := rfl + rw [hres] at hlow + linarith + +/-- Every iterate from zero oscillates by at most `oscGap`. -/ +theorem oscOn_iterate (n : ℕ) : + P.OscOn ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) P.oscSpread := by + have hβ1 : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + have hfix : P.u P.maxConsumption - P.u P.minConsumption + (P.discount : ℝ) * P.oscSpread + = P.oscSpread := by + have hne : (1 : ℝ) - (P.discount : ℝ) ≠ 0 := by linarith + simp only [oscSpread] + field_simp + ring + induction n with + | zero => + intro x hx y hy z z' + simpa using P.oscSpread_nonneg + | succ k ih => + rw [Function.iterate_succ_apply'] + have := P.oscOn_bellman ih + rwa [hfix] at this + +/-! ### A uniform bound on saving, and hence on the cap + +The last input is quantitative: the household must not want to save all the way to the imposed +ceiling. `policy_le_of_marginal_bound` does this at the fixed point; the same deviation — save +half as far above the borrowing limit — works against any continuation, with `‖v‖` in place of +`‖V‖`. The iterates are uniformly bounded because the Bellman operator adds at most the reward +and contracts what it inherits, so one calibration inequality covers the whole iteration. -/ + +/-- The Bellman operator adds at most the reward and contracts what it inherits. -/ +theorem norm_bellman_le (v : (ℝ × Z) →ᵇ ℝ) : + ‖P.toExtended.bellman v‖ + ≤ max |P.toExtended.rewardMin| |P.toExtended.rewardMax| + (P.discount : ℝ) * ‖v‖ := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hM : (0 : ℝ) ≤ max |P.toExtended.rewardMin| |P.toExtended.rewardMax| := + le_max_of_le_left (abs_nonneg _) + refine (BoundedContinuousFunction.norm_le (by positivity)).mpr fun s => ?_ + have hb := P.toExtended.bellmanFn_bounds v s + have hval : (P.toExtended.bellman v) s = P.toExtended.bellmanFn v s := rfl + rw [hval, Real.norm_eq_abs, abs_le] + simp only [ExtendedStochasticProgram.loBound, ExtendedStochasticProgram.hiBound] at hb + have h1 : -|P.toExtended.rewardMin| ≤ P.toExtended.rewardMin := neg_abs_le _ + have h2 : P.toExtended.rewardMax ≤ |P.toExtended.rewardMax| := le_abs_self _ + have h3 : -max |P.toExtended.rewardMin| |P.toExtended.rewardMax| + ≤ -|P.toExtended.rewardMin| := by + simp only [neg_le_neg_iff]; exact le_max_left _ _ + have h4 : |P.toExtended.rewardMax| + ≤ max |P.toExtended.rewardMin| |P.toExtended.rewardMax| := le_max_right _ _ + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + rw [hdx] at hb + constructor <;> linarith [hb.1, hb.2] + +/-- **The iterates are uniformly bounded**, so one calibration covers them all. -/ +theorem norm_iterate_le (n : ℕ) : + ‖(P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)‖ + ≤ max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount) := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hβ1 : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + have hM : (0 : ℝ) ≤ max |P.toExtended.rewardMin| |P.toExtended.rewardMax| := + le_max_of_le_left (abs_nonneg _) + have hfix : max |P.toExtended.rewardMin| |P.toExtended.rewardMax| + + (P.discount : ℝ) * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| + / (1 - P.discount)) + = max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount) := by + have hne : (1 : ℝ) - (P.discount : ℝ) ≠ 0 := by linarith + field_simp + ring + induction n with + | zero => simpa using by positivity + | succ k ih => + rw [Function.iterate_succ_apply'] + refine le_trans (P.norm_bellman_le _) ?_ + rw [← hfix] + have := mul_le_mul_of_nonneg_left ih hβ + linarith + +/-! ### Carroll and Kimball, assembled along the iteration -/ + +/-- **Carroll and Kimball (1996) for CRRA.** The consumption function is concave. + +Everything is now proved except the asset cap: `hslack` says the household never wants to save +all the way to the imposed ceiling, at any stage of the iteration. That is the artefact the cap +always was — `crra_policy_lt_assetCap` discharges it at the fixed point by calibration, and +`policy_assetCap_of_patient` shows it is exactly `β (1 + r) < 1` that makes it true. -/ +theorem concaveOn_consumptionFnOf_iterates_of_crra {γ : ℝ} (hpc : P.PositiveConsumptionAll) + (hγ0 : 0 < γ) (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) + (hslack : ∀ n : ℕ, ∀ a ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) : + ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) + (P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z) := by + set T := P.toExtended.bellman with hTdef + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap (T^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := + P.concaveSlices_iterate_bellman concaveSlices_zero + · intro n + induction n with + | zero => exact P.concaveOn_consumptionFnOf_zero + | succ k ih => + intro z + have hnext : ∀ a ∈ Icc assetFloor assetCap, + P.policyOf (T (T^[k] (0 : (ℝ × Z) →ᵇ ℝ))) (a, z) < P.maxSaving (a, z) := by + intro a ha + have hs := hslack (k + 1) a ha z + rw [Function.iterate_succ_apply'] at hs + exact P.policyOf_lt_maxSaving hpc (P.concaveSlices_bellman (hslices k)) ha hs + rw [Function.iterate_succ_apply'] + exact P.concaveOn_consumptionFnOf_bellman hγ0 hβ hu z + (fun z' A hA => hpc _ (hslices k) z' A hA) ih + (fun z' x hx y hy hxy => P.consumptionFnOf_mono (hslices k) hx hy hxy) + (fun a ha => hpc _ (P.concaveSlices_bellman (hslices k)) z a ha) hnext + fun A hA z' => P.policyOf_lt_maxSaving hpc (hslices k) hA (hslack k A hA z') + +/-- **Carroll and Kimball for CRRA**, at the fixed point. -/ +theorem concaveOn_consumptionFn_of_crra {γ : ℝ} (hpc : P.PositiveConsumptionAll) (hγ0 : 0 < γ) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) + (hslack : ∀ n : ℕ, ∀ a ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := + P.concaveOn_consumptionFn_of_iterates + (P.concaveOn_consumptionFnOf_iterates_of_crra hpc hγ0 hβ hu hslack) z + +/-- **Carroll and Kimball (1996) for CRRA, from a calibration.** Beyond CRRA utility and +marginal utility unbounded at zero, the only hypothesis left is one inequality: the asset cap +must sit above the uniform bound on saving that the marginal bound `m` produces. Nothing about +the borrowing limit, and nothing about interiority. + +That inequality is the cap being an artefact, which is what it was always meant to be: by +`policy_assetCap_of_patient` it is exactly impatience that makes it true. -/ +theorem concaveOn_consumptionFn_of_marginal {γ m : ℝ} (hpc : P.PositiveConsumptionAll) + (hγ0 : 0 < γ) + (hβ : 0 < (P.discount : ℝ)) (hu : P.u = crraUtility γ) (hm : 0 < m) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.maxConsumption → m * (c - d) ≤ P.u c - P.u d) + (hcap : assetFloor + 4 * (P.discount : ℝ) + * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount)) / m + < assetCap) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap + ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := + P.concaveSlices_iterate_bellman concaveSlices_zero + refine P.concaveOn_consumptionFn_of_crra hpc hγ0 hβ hu (fun n a ha z' => ?_) z + have hb := P.policyOf_le_of_marginal_bound (hslices n) hm hmarg ha z' + have hn := P.norm_iterate_le n + have h4 : (0 : ℝ) ≤ 4 * (P.discount : ℝ) := by positivity + have hstep := mul_le_mul_of_nonneg_left hn h4 + have hmono : 4 * (P.discount : ℝ) * ‖(P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)‖ / m + ≤ 4 * (P.discount : ℝ) * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| + / (1 - P.discount)) / m := by + rw [div_le_div_iff₀ hm hm]; nlinarith [hstep, hm] + linarith + +/-! ### Positive consumption for the bounded family + +With utility unbounded below, consumption is positive because zero consumption is worth `⊥`. +With CES and `γ < 1` it is not: zero consumption is admissible and the floor has to be earned +from the MARGIN, which is what `ConsumptionFloor` does at the fixed point. The induction sees +the iterates, so the same argument is run against an arbitrary continuation — and it goes +through unchanged, because it only ever used concavity and boundedness of the continuation. -/ + +/-- **Positive consumption against every continuation, for the bounded family.** -/ +theorem positiveConsumptionAll_of_marginalInada (hu : MarginalInadaOn P.dom P.u) : + P.PositiveConsumptionAll := by + intro v hv z a ha + obtain ⟨δ, hδ, hspec⟩ := P.exists_consumptionFnOf_floor_of_marginalInada hu hv + exact lt_of_lt_of_le hδ (hspec z a ha) + +/-- **Carroll and Kimball for the BOUNDED CES family**, `0 < γ < 1`, where utility does not fall +to `-∞` and positivity of consumption has to be earned. -/ +theorem concaveOn_consumptionFn_of_bounded_crra {γ m : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) + (hβ : 0 < (P.discount : ℝ)) (hb : P.Bounded) (hu : P.u = crraUtility γ) (hm : 0 < m) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.maxConsumption → m * (c - d) ≤ P.u c - P.u d) + (hcap : assetFloor + 4 * (P.discount : ℝ) + * (max |P.toExtended.rewardMin| |P.toExtended.rewardMax| / (1 - P.discount)) / m + < assetCap) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + refine P.concaveOn_consumptionFn_of_marginal (P.positiveConsumptionAll_of_marginalInada ?_) + hγ0 hβ hu hm hmarg hcap z + rw [hb, hu] + exact marginalInadaOn_Ici_crraUtility hγ0 hγ1 + +/-! ### The multiplicative CES bound on saving + +`policyOf_le_of_marginal_bound` prices the deviation against the LEVEL of the continuation, and +for CES that is hopeless: at the `cesWitness` parameters it gives a bound about six times the +asset cap. The fix is the one `crra_policy_le_mul` makes at the fixed point — deviate to a +FRACTION `θ` of the saving rather than half of it, and price the extra consumption at the margin +where it lands. The bound then involves the continuation's OSCILLATION, not its level, and it is +proportional to consumption rather than constant. -/ + +/-- **The proportional CES bound on saving, against an arbitrary continuation.** -/ +theorem crra_policyOf_le_mul {γ θ G : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) (hθ1 : θ < 1) + (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumptionAll) {v : (ℝ × Z) →ᵇ ℝ} + (hv : ConcaveSlices assetFloor assetCap v) (hG : 0 ≤ G) (hosc : P.OscOn v G) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + P.policyOf v (a, z) - assetFloor + ≤ ((P.discount : ℝ) * G / θ) + * (P.consumptionFnOf v z a + + (1 - θ) * (P.policyOf v (a, z) - assetFloor)) ^ γ := by + simpa only [zero_add] using P.policyOf_le_mul_of_power_marginal (γ := γ) (η := 0) + hθ0 hθ1 hv hG hosc (fun c d _ hcd hpositive => by + simpa only [zero_add, hu] using + crra_marginal_bound hγ0 (by simpa only [zero_add] using hpositive) hcd (le_refl d)) ha z + (by simpa only [zero_add] using hpc v hv z a ha) + +/-- **Saving never reaches the asset cap, against an arbitrary continuation.** The hypothesis is +the one `crra_policy_lt_assetCap` carries at the fixed point, so the calibrations already proved +for the CES witnesses discharge it. -/ +theorem crra_policyOf_lt_assetCap {γ θ G : ℝ} (hγ0 : 0 < γ) (hθ0 : 0 < θ) + (hθ1 : θ < 1) (hu : P.u = crraUtility γ) (hpc : P.PositiveConsumptionAll) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices assetFloor assetCap v) (hG : 0 ≤ G) + (hosc : P.OscOn v G) + (hlt : ((P.discount : ℝ) * G / θ) + * (P.maxIncome + (1 + P.interest - θ) * assetCap - (1 - θ) * assetFloor) ^ γ + < assetCap - assetFloor) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + P.policyOf v (a, z) < assetCap := by + refine P.policyOf_lt_cap_of_power_bound (η := 0) hγ0.le hθ0.le hθ1.le + (show 0 ≤ (P.discount : ℝ) * G / θ from + div_nonneg (mul_nonneg P.discount.coe_nonneg hG) hθ0.le) ha z + (by simpa only [zero_add] using (hpc v hv z a ha).le) ?_ ?_ + · simpa only [zero_add] using P.crra_policyOf_le_mul hγ0 hθ0 hθ1 hu hpc hv hG hosc ha z + · simpa only [zero_add] using hlt + +/-- **Carroll and Kimball for the bounded CES family, at the witnesses' own calibration.** The +cap hypothesis is exactly `crra_policy_lt_assetCap`'s, with `oscSpread` for `oscGap` — the same +number at a zero borrowing limit. -/ +theorem concaveOn_consumptionFn_of_oscSpread {γ θ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ < 1) + (hθ0 : 0 < θ) (hθ1 : θ < 1) (hβ : 0 < (P.discount : ℝ)) (hb : P.Bounded) + (hu : P.u = crraUtility γ) + (hlt : ((P.discount : ℝ) * P.oscSpread / θ) + * (P.maxIncome + (1 + P.interest - θ) * assetCap - (1 - θ) * assetFloor) ^ γ + < assetCap - assetFloor) + (z : Z) : ConcaveOn ℝ (Icc assetFloor assetCap) (P.consumptionFn z) := by + have hpc : P.PositiveConsumptionAll := by + refine P.positiveConsumptionAll_of_marginalInada ?_ + rw [hb, hu] + exact marginalInadaOn_Ici_crraUtility hγ0 hγ1 + have hslices : ∀ n : ℕ, ConcaveSlices assetFloor assetCap + ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := + P.concaveSlices_iterate_bellman concaveSlices_zero + exact P.concaveOn_consumptionFn_of_iterates + (P.concaveOn_consumptionFnOf_iterates_of_crra hpc hγ0 hβ hu + (fun n a ha z' => P.crra_policyOf_lt_assetCap hγ0 hθ0 hθ1 hu hpc (hslices n) + P.oscSpread_nonneg (P.oscOn_iterate n) hlt ha z')) z + +/-! ### A slope bound from the OSCILLATION rather than the level + +`contSlopeConstOf` prices the continuation's slope by `‖v‖`, which is what the CRRA arguments +had to hand. For utilities whose marginal value at zero is FINITE — CARA, and shifted CRRA with +a subsistence level — the consumption floor has to beat that slope, and the level is far too +crude a bound to beat. The oscillation is the right measure and it is the one the iterates +control (`oscOn_iterate`). The shared slope and consumption-floor arguments in +`ConsumptionFloor` use this oscillation directly; norm bounds are specializations. -/ + +/-! ### A consumption floor from a FINITE marginal bound + +`exists_consumptionFnOf_floor_of_marginalInada` needs marginal utility to explode at zero, which +excludes every HARA member except pure CRRA. What the argument actually uses is that the +marginal value of consumption near zero beats the discounted slope of the continuation, and for +a finite marginal bound `M` that is an inequality to check rather than a triviality. -/ + +/-- **Positive consumption from a finite marginal bound**, against every continuation with +concave slices and oscillation at most `G`. -/ +theorem consumptionFnOf_pos_of_marginal {M G : ℝ} + (hM : MarginalBoundOn P.dom P.u M) (hG : 0 ≤ G) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices assetFloor assetCap v) (hosc : P.OscOn v G) + (hlt : (P.discount : ℝ) * P.oscSlopeConst G < M) + (z : Z) {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : 0 < P.consumptionFnOf v z a := by + obtain ⟨δ, hδ, hfloor⟩ := P.exists_consumptionFnOf_floor_of_marginal hM hG hv hosc hlt + exact lt_of_lt_of_le hδ (hfloor z a ha) + +/-! ### Single crossing in the continuation + +Two continuations at the SAME state, ordered by increasing differences. This is +`policy_le_of_cont_increasingDifferences` of `IncomeFluctuationRateMonotone` read against +arbitrary continuations rather than at the fixed point, and it is what carries Light's step 5 +along the iteration: the induction hypothesis is exactly increasing differences of the two +iterates. -/ + +/-- **Increasing differences between two continuations**, in the same economy. -/ +def ContOfIncreasingDifferences (v w : (ℝ × Z) →ᵇ ℝ) : Prop := + ∀ (z : Z) (x y : ℝ), x ∈ Icc assetFloor assetCap → y ∈ Icc assetFloor assetCap → x ≤ y → + P.contOf v z y - P.contOf v z x ≤ P.contOf w z y - P.contOf w z x + +/-- **Increasing differences slice by slice**, between two bounded continuous functions on the +state space. This is the property the induction of Light's step 5 carries along the iteration: +it mentions no economy, so it makes sense between the iterates of two DIFFERENT economies, and +it is closed under pointwise limits. -/ +def IncDiffSlices (assetFloor assetCap : ℝ) (v w : (ℝ × Z) →ᵇ ℝ) : Prop := + ∀ (z : Z) (x y : ℝ), x ∈ Icc assetFloor assetCap → y ∈ Icc assetFloor assetCap → x ≤ y → + v (y, z) - v (x, z) ≤ w (y, z) - w (x, z) + +omit [Fintype Z] [Nonempty Z] [DiscreteTopology Z] in +theorem incDiffSlices_zero (assetFloor assetCap : ℝ) : + IncDiffSlices (Z := Z) assetFloor assetCap 0 0 := by + intro z x y _ _ _ + simp + +/-- It suffices to have increasing differences slice by slice in the continuations themselves. -/ +theorem contOfIncreasingDifferences_of_slices {v w : (ℝ × Z) →ᵇ ℝ} + (h : IncDiffSlices assetFloor assetCap v w) : + P.ContOfIncreasingDifferences v w := by + intro z x y hx hy hxy + simp only [contOf, ← Finset.sum_sub_distrib, ← mul_sub] + exact Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (h z' x y hx hy hxy) (P.transitionMatrix_nonneg z z') + +/-- **Single crossing in the continuation.** A household whose continuation has increasing +differences over another's saves at least as much, at every state. No derivative: the exchange +argument turns the two optimality comparisons into a sandwich that increasing differences +closes. -/ +theorem policyOf_le_of_contOf_increasingDifferences {v w : (ℝ × Z) →ᵇ ℝ} + (hw : ConcaveSlices assetFloor assetCap w) (hid : P.ContOfIncreasingDifferences v w) + {a : ℝ} {z : Z} (ha : a ∈ Icc assetFloor assetCap) : + P.policyOf v (a, z) ≤ P.policyOf w (a, z) := by + by_contra hcon + rw [not_le] at hcon + set bV : ℝ := P.policyOf v (a, z) with hbV + set bW : ℝ := P.policyOf w (a, z) with hbW + have hbVm : bV ∈ P.toExtended.feasible (a, z) := P.policyOf_mem v (a, z) + have hbWm : bW ∈ P.toExtended.feasible (a, z) := P.policyOf_mem w (a, z) + have hcV : P.consumption (a, z) bV ∈ P.dom := P.consumption_policyOf_mem_dom v ha + have hcW : P.consumption (a, z) bW ∈ P.dom := P.consumption_policyOf_mem_dom w ha + have hoptV := P.objROf_le_of_mem v ha hbWm hcW + have hoptW := P.objROf_le_of_mem w ha hbVm hcV + have hidd := hid z bW bV (P.feasible_subset_region hbWm) (P.feasible_subset_region hbVm) hcon.le + have hβ0 : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + simp only [objROf] at hoptV hoptW + have hWeq : P.objROf w (a, z) bV = P.objROf w (a, z) bW := by + simp only [objROf] + nlinarith [hoptV, hoptW, mul_le_mul_of_nonneg_left hidd hβ0] + have hbell : P.toExtended.objectiveE w (a, z) bV + = ((P.toExtended.bellmanFn w (a, z) : ℝ) : EReal) := by + rw [P.objectiveE_eq_coe_of w ha hbVm hcV, hWeq, ← P.objectiveE_eq_coe_of w ha hbWm hcW] + exact P.policyOf_optimal w (a, z) + exact absurd (P.optimal_action_unique_of_concaveSlices hw ha hbVm hbWm hbell + (P.policyOf_optimal w (a, z))) (by intro h; rw [h] at hcon; exact lt_irrefl _ hcon) + +/-- Consumption is correspondingly ordered the other way. -/ +theorem consumptionFnOf_le_of_contOf_increasingDifferences {v w : (ℝ × Z) →ᵇ ℝ} + (hw : ConcaveSlices assetFloor assetCap w) (hid : P.ContOfIncreasingDifferences v w) + {a : ℝ} {z : Z} (ha : a ∈ Icc assetFloor assetCap) : + P.consumptionFnOf w z a ≤ P.consumptionFnOf v z a := by + simp only [consumptionFnOf, consumption] + have := P.policyOf_le_of_contOf_increasingDifferences hw hid (z := z) ha + linarith + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationLipschitz.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationLipschitz.lean new file mode 100644 index 000000000..2903b2419 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationLipschitz.lean @@ -0,0 +1,405 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationBounds +public import LeanPool.AiyagariEquilibrium.Analysis.DecreasingIncrements + +/-! +# The Bellman operator preserves Lipschitz continuity in assets + +The economics half of the Lipschitz argument. With `isClosed_forall_lipschitzOn` this makes +the value function Lipschitz in assets, which bounds the marginal value of wealth uniformly +and so lets a corner condition be checked without differentiating anything. + +## The action swap + +Comparing a richer state `x` with a poorer state `y ≤ x`, the resource shortfall is +`Δ = (1+r)(x-y)`. Given any action `a'` feasible at `x`, the action `max 0 (a' - Δ)` is +feasible at `y` and — away from the truncation — leaves consumption EXACTLY unchanged. So the +whole difference passes to the continuation value, where the hypothesis on `v` bounds it by +`L·Δ`, and the utility term contributes nothing. + +Where the truncation bites, the poorer household saves nothing and therefore consumes all its +resources. That is what keeps the utility term controllable: both consumptions are then at +least `minIncome`, where a concave `u` is Lipschitz with the explicit constant of +`slopeBound`. No appeal to the cutoff lemma is needed, and no derivative. + +Those two cases are where the constant `(K + βL)(1+r)` comes from, and solving +`(K + βL)(1+r) ≤ L` for `L` is exactly where impatience `β(1+r) < 1` is required. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The Lipschitz constant of `u` above the lowest possible consumption. -/ +noncomputable def slopeBoundU : ℝ := slopeBound P.u P.minConsumption + +theorem slopeBoundU_nonneg : 0 ≤ P.slopeBoundU := + slopeBound_nonneg P.monotoneOn_u P.minConsumption_pos + +theorem abs_u_sub_le {c d : ℝ} (hc : P.minConsumption ≤ c) (hd : P.minConsumption ≤ d) : + |P.u c - P.u d| ≤ P.slopeBoundU * |c - d| := + P.strictConcaveOn_u.concaveOn.abs_sub_le_slopeBound_mul P.monotoneOn_u P.minConsumption_pos hc hd + +/-- The expectation inherits a Lipschitz bound from the continuation value, since the two +states share the income draw and hence the transition weights. -/ +theorem abs_expect_sub_le_lipschitz {v : (ℝ × Z) →ᵇ ℝ} {L : ℝ} + (hv : ∀ z : Z, ∀ p ∈ Icc assetFloor assetCap, ∀ q ∈ Icc assetFloor assetCap, + |v (p, z) - v (q, z)| ≤ L * |p - q|) + (z : Z) {a b : ℝ} (ha : a ∈ Icc assetFloor assetCap) (hb : b ∈ Icc assetFloor assetCap) + (x y : ℝ) : + |P.toExtended.expect v ((x, z), a) - P.toExtended.expect v ((y, z), b)| ≤ L * |a - b| := by + have hp : ∀ (w : ℝ) (c : ℝ), P.toExtended.expect v ((w, z), c) + = ∑ z' : Z, P.transitionMatrix z z' * v (c, z') := fun _ _ => rfl + rw [hp, hp, ← Finset.sum_sub_distrib] + calc |∑ z' : Z, (P.transitionMatrix z z' * v (a, z') - P.transitionMatrix z z' * v (b, z'))| + ≤ ∑ z' : Z, |P.transitionMatrix z z' * v (a, z') - P.transitionMatrix z z' * v (b, z')| := + Finset.abs_sum_le_sum_abs _ _ + _ ≤ ∑ z' : Z, P.transitionMatrix z z' * (L * |a - b|) := by + refine Finset.sum_le_sum fun z' _ => ?_ + rw [← mul_sub, abs_mul, abs_of_nonneg (P.transitionMatrix_nonneg z z')] + exact mul_le_mul_of_nonneg_left (hv z' a ha b hb) (P.transitionMatrix_nonneg z z') + _ = L * |a - b| := by rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + +/-- **The richer state's value exceeds the poorer's by at most the swap cost.** -/ +theorem bellmanFn_sub_le_of_lipschitz {v : (ℝ × Z) →ᵇ ℝ} {L : ℝ} (hL : 0 ≤ L) + (hv : ∀ z : Z, ∀ p ∈ Icc assetFloor assetCap, ∀ q ∈ Icc assetFloor assetCap, + |v (p, z) - v (q, z)| ≤ L * |p - q|) + (z : Z) {x y : ℝ} (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : y ≤ x) : + P.toExtended.bellmanFn v (x, z) - P.toExtended.bellmanFn v (y, z) + ≤ (P.slopeBoundU + P.discount * L) * ((1 + P.interest) * (x - y)) := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hK := P.slopeBoundU_nonneg + set Δ : ℝ := (1 + P.interest) * (x - y) with hΔdef + have hΔ0 : 0 ≤ Δ := mul_nonneg P.interest_gt_neg_one.le (by linarith) + have hres : P.resources (x, z) = P.resources (y, z) + Δ := by + simp only [resources, hΔdef] + rw [max_eq_right hx.1, max_eq_right hy.1]; ring + rw [sub_le_iff_le_add'] + refine P.toExtended.bellmanFn_le v fun a ha => ?_ + rcases eq_or_ne (P.toExtended.reward ((x, z), a)) ⊥ with hbot | hne + · rw [ExtendedStochasticProgram.objectiveE, hbot, EReal.bot_add]; exact bot_le + -- consumption is positive at the richer state, so the objective is real there + have hc : P.consumption (x, z) a ∈ P.dom := P.consumption_mem_dom_of_ne_bot hx ha hne + have ha0 : assetFloor ≤ a := ha.1 + have hacap : a ≤ assetCap := le_trans ha.2 (P.maxSaving_le_assetCap _) + have haR : a ≤ P.resources (x, z) := le_trans ha.2 (P.maxSaving_le_resources _) + set b : ℝ := max assetFloor (a - Δ) with hbdef + have hb0 : assetFloor ≤ b := le_max_left _ _ + have hba : b ≤ a := by rw [hbdef]; exact max_le ha0 (by linarith) + have hbmem : b ∈ P.toExtended.feasible (y, z) := by + rw [P.feasible_eq, P.maxSaving_eq] + refine ⟨hb0, le_min (le_trans hba hacap) ?_⟩ + rw [hbdef] + exact max_le (P.assetFloor_lt_resources _).le (by linarith [hres]) + -- consumption at the poorer state under the swapped action + have hcy : P.consumption (y, z) b = P.resources (y, z) - b := rfl + have hcymem : P.consumption (y, z) b ∈ P.dom := by + rcases le_total (a - assetFloor) Δ with h | h + · refine P.mem_dom_of_pos ?_ + rw [hcy, hbdef, max_eq_left (by linarith)] + linarith [P.minConsumption_le_consumption_floor (y, z), P.minConsumption_pos] + · refine P.dom_upward hc (le_of_eq ?_) + rw [hcy, hbdef, max_eq_right (by linarith)] + simp only [consumption]; linarith [hres] + -- both objectives are real; compare them + have hrx := P.reward_eq_coe_dom hx ha hc + have hry := P.reward_eq_coe_dom hy hbmem hcymem + have hexp := P.abs_expect_sub_le_lipschitz hv z (a := a) (b := b) + ⟨ha0, hacap⟩ ⟨hb0, le_trans hba hacap⟩ x y + have hgapa : |a - b| ≤ Δ := by + rw [hbdef] + rcases le_total (a - assetFloor) Δ with h | h + · rw [max_eq_left (by linarith), abs_of_nonneg (by linarith)]; exact h + · rw [max_eq_right (by linarith)] + rw [show a - (a - Δ) = Δ by ring, abs_of_nonneg hΔ0] + -- the utility term: equal consumptions off the truncation, both above `minIncome` on it + have hutil : P.u (P.consumption (x, z) a) - P.u (P.consumption (y, z) b) + ≤ P.slopeBoundU * Δ := by + rcases le_total Δ (a - assetFloor) with h | h + · -- no truncation: consumption is unchanged + have : P.consumption (y, z) b = P.consumption (x, z) a := by + rw [hcy, hbdef, max_eq_right (by linarith)] + simp only [consumption]; linarith [hres] + rw [this, sub_self] + positivity + · -- truncation: the poorer household borrows to the limit and eats the rest + have hb0' : b = assetFloor := by rw [hbdef, max_eq_left (by linarith)] + have hcyval : P.consumption (y, z) b = P.resources (y, z) - assetFloor := by + rw [hcy, hb0'] + have h1 : P.minConsumption ≤ P.consumption (y, z) b := by + rw [hcyval]; exact P.minConsumption_le_consumption_floor _ + have h2 : P.minConsumption ≤ P.consumption (x, z) a := by + simp only [consumption] + rw [hcyval] at h1 + linarith [hres] + have := P.abs_u_sub_le h2 h1 + rw [abs_le] at this + refine le_trans this.2 ?_ + refine mul_le_mul_of_nonneg_left ?_ hK + rw [abs_le] + constructor + · simp only [consumption, hb0']; linarith + · simp only [consumption, hb0']; linarith + -- assemble + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + have hstep : P.toExtended.objectiveE v (x, z) a + ≤ P.toExtended.objectiveE v (y, z) b + + (((P.slopeBoundU + P.discount * L) * Δ : ℝ) : EReal) := by + simp only [ExtendedStochasticProgram.objectiveE, hrx, hry, hdx] + rw [← EReal.coe_add, add_assoc, ← EReal.coe_add, ← EReal.coe_add, EReal.coe_le_coe_iff] + rw [abs_le] at hexp + have h1 : (P.discount : ℝ) + * (P.toExtended.expect v ((x, z), a) - P.toExtended.expect v ((y, z), b)) + ≤ P.discount * (L * |a - b|) := mul_le_mul_of_nonneg_left hexp.2 hβ + have h2 : (P.discount : ℝ) * (L * |a - b|) ≤ P.discount * (L * Δ) := + mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left hgapa hL) hβ + nlinarith [hutil, h1, h2] + refine le_trans hstep ?_ + rw [EReal.coe_add] + exact add_le_add (P.toExtended.le_bellmanFn v hbmem) (le_refl _) + +/-- **The poorer state's value falls short by at most the swap cost.** The reverse swap needs +no utility bound at all: where it truncates, the richer household consumes strictly more, so +the utility term has the helpful sign. -/ +theorem bellmanFn_sub_le_of_lipschitz' {v : (ℝ × Z) →ᵇ ℝ} {L : ℝ} (hL : 0 ≤ L) + (hv : ∀ z : Z, ∀ p ∈ Icc assetFloor assetCap, ∀ q ∈ Icc assetFloor assetCap, + |v (p, z) - v (q, z)| ≤ L * |p - q|) + (z : Z) {x y : ℝ} (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : y ≤ x) : + P.toExtended.bellmanFn v (y, z) - P.toExtended.bellmanFn v (x, z) + ≤ P.discount * L * ((1 + P.interest) * (x - y)) := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + set Δ : ℝ := (1 + P.interest) * (x - y) with hΔdef + have hΔ0 : 0 ≤ Δ := mul_nonneg P.interest_gt_neg_one.le (by linarith) + have hres : P.resources (x, z) = P.resources (y, z) + Δ := by + simp only [resources, hΔdef] + rw [max_eq_right hx.1, max_eq_right hy.1]; ring + rw [sub_le_iff_le_add'] + refine P.toExtended.bellmanFn_le v fun b hb => ?_ + rcases eq_or_ne (P.toExtended.reward ((y, z), b)) ⊥ with hbot | hne + · rw [ExtendedStochasticProgram.objectiveE, hbot, EReal.bot_add]; exact bot_le + have hcy : P.consumption (y, z) b ∈ P.dom := P.consumption_mem_dom_of_ne_bot hy hb hne + have hb0 : assetFloor ≤ b := hb.1 + have hbcap : b ≤ assetCap := le_trans hb.2 (P.maxSaving_le_assetCap _) + have hbR : b ≤ P.resources (y, z) := le_trans hb.2 (P.maxSaving_le_resources _) + set a : ℝ := min (b + Δ) (P.maxSaving (x, z)) with hadef + have ha0 : assetFloor ≤ a := le_min (by linarith) (P.assetFloor_le_maxSaving _) + have hamem : a ∈ P.toExtended.feasible (x, z) := ⟨ha0, min_le_right _ _⟩ + have hacap : a ≤ assetCap := le_trans (min_le_right _ _) (P.maxSaving_le_assetCap _) + -- consumption at the richer state is at least what the poorer household had + have hcx : P.consumption (y, z) b ≤ P.consumption (x, z) a := by + simp only [consumption, hadef] + rcases le_total (b + Δ) (P.maxSaving (x, z)) with h | h + · rw [min_eq_left h]; linarith [hres] + · rw [min_eq_right h] + have hms : P.maxSaving (x, z) = min assetCap (P.resources (x, z)) := P.maxSaving_eq _ + rcases le_total (P.resources (x, z)) assetCap with hcap | hcap + · -- no truncation is possible here + rw [hms, min_eq_right hcap] at h ⊢ + linarith [hres] + · rw [hms, min_eq_left hcap] at h ⊢ + linarith [hres] + have hcxmem : P.consumption (x, z) a ∈ P.dom := P.dom_upward hcy hcx + have hgapa : |b - a| ≤ Δ := by + have hlow : b - Δ ≤ a := by + rw [hadef] + refine le_min (by linarith) ?_ + have hmono : P.maxSaving (y, z) ≤ P.maxSaving (x, z) := by + rw [P.maxSaving_eq, P.maxSaving_eq] + exact min_le_min le_rfl (by linarith [hres]) + linarith [hb.2] + have hhigh : a ≤ b + Δ := min_le_left _ _ + rw [abs_le] + constructor <;> linarith + have hrx := P.reward_eq_coe_dom hx hamem hcxmem + have hry := P.reward_eq_coe_dom hy hb hcy + have hexp := P.abs_expect_sub_le_lipschitz hv z (a := b) (b := a) + ⟨hb0, hbcap⟩ ⟨ha0, hacap⟩ y x + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + have hstep : P.toExtended.objectiveE v (y, z) b + ≤ P.toExtended.objectiveE v (x, z) a + (((P.discount * L) * Δ : ℝ) : EReal) := by + simp only [ExtendedStochasticProgram.objectiveE, hrx, hry, hdx] + rw [← EReal.coe_add, add_assoc, ← EReal.coe_add, ← EReal.coe_add, EReal.coe_le_coe_iff] + rw [abs_le] at hexp + have hu' : P.u (P.consumption (y, z) b) ≤ P.u (P.consumption (x, z) a) := + P.monotoneOn_u_dom hcy hcxmem hcx + have h1 : (P.discount : ℝ) + * (P.toExtended.expect v ((y, z), b) - P.toExtended.expect v ((x, z), a)) + ≤ P.discount * (L * |b - a|) := mul_le_mul_of_nonneg_left hexp.2 hβ + have h2 : (P.discount : ℝ) * (L * |b - a|) ≤ P.discount * (L * Δ) := + mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left hgapa hL) hβ + nlinarith [hu', h1, h2] + refine le_trans hstep ?_ + rw [EReal.coe_add] + exact add_le_add (P.toExtended.le_bellmanFn v hamem) (le_refl _) + +/-- **The Bellman operator preserves Lipschitz continuity in assets.** -/ +theorem abs_bellmanFn_sub_le_of_lipschitz {v : (ℝ × Z) →ᵇ ℝ} {L : ℝ} (hL : 0 ≤ L) + (hv : ∀ z : Z, ∀ p ∈ Icc assetFloor assetCap, ∀ q ∈ Icc assetFloor assetCap, + |v (p, z) - v (q, z)| ≤ L * |p - q|) + (hbig : (P.slopeBoundU + P.discount * L) * (1 + P.interest) ≤ L) + (z : Z) {x y : ℝ} (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) : + |P.toExtended.bellmanFn v (x, z) - P.toExtended.bellmanFn v (y, z)| ≤ L * |x - y| := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hK := P.slopeBoundU_nonneg + have hone : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have key : ∀ p q : ℝ, p ∈ Icc assetFloor assetCap → q ∈ Icc assetFloor assetCap → q ≤ p → + |P.toExtended.bellmanFn v (p, z) - P.toExtended.bellmanFn v (q, z)| ≤ L * |p - q| := by + intro p q hp hq hqp + have h1 := P.bellmanFn_sub_le_of_lipschitz hL hv z hp hq hqp + have h2 := P.bellmanFn_sub_le_of_lipschitz' hL hv z hp hq hqp + have hgap : (P.discount : ℝ) * L * ((1 + P.interest) * (p - q)) + ≤ (P.slopeBoundU + P.discount * L) * ((1 + P.interest) * (p - q)) := by + refine mul_le_mul_of_nonneg_right (by linarith) (by nlinarith) + have hfinal : (P.slopeBoundU + P.discount * L) * ((1 + P.interest) * (p - q)) + ≤ L * (p - q) := by + have := mul_le_mul_of_nonneg_right hbig (by linarith : (0 : ℝ) ≤ p - q) + nlinarith + rw [abs_le, abs_of_nonneg (by linarith : (0 : ℝ) ≤ p - q)] + constructor <;> linarith + rcases le_total y x with h | h + · exact key x y hx hy h + · rw [abs_sub_comm, abs_sub_comm x y]; exact key y x hy hx h + +/-- **The value function is Lipschitz in assets**, with an explicit constant. Combining the +preservation proof with the closed class. Any `L` with `(K + βL)(1+r) ≤ L` works, and such an +`L` exists exactly when `β(1+r) < 1`. -/ +theorem valueFunction_lipschitz {L : ℝ} (hL : 0 ≤ L) + (hbig : (P.slopeBoundU + P.discount * L) * (1 + P.interest) ≤ L) : + ∀ z : Z, ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| ≤ L * |x - y| := + P.toExtended.blackwell.forall_lipschitzOn_valueFunction P.toExtended.discount_lt_one hL + (g := fun (z : Z) (a : ℝ) => (a, z)) + (fun v hvv z x hx y hy => by + simpa only [ExtendedStochasticProgram.bellman_apply] using + P.abs_bellmanFn_sub_le_of_lipschitz hL (fun z' p hp q hq => hvv z' p hp q hq) hbig z hx hy) + +/-! ### The corner + +With the marginal value of wealth bounded above by `L`, saving nothing beats saving anything +as soon as `β L` falls below a lower bound on the marginal utility of consumption. Both sides +of that comparison are now explicit constants, and no derivative appears in either. -/ + +/-- **The corner condition, state by state.** The marginal-utility bound is required only up +to THIS state's resources, so the threshold is local: a state with few resources has high +marginal utility and the constraint binds there, while a richer state may fail the test. + +That is what lets the borrowing constraint bind at a bad income draw without binding +everywhere, which the global version could not express. -/ +theorem policy_eq_zero_of_corner_at {L m : ℝ} + (hlip : ∀ z : Z, ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| ≤ L * |x - y|) + {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.resources s - assetFloor → + m * (c - d) ≤ P.u c - P.u d) + (hcond : P.discount * L < m) : + P.policy s = assetFloor := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + set V := P.toExtended.valueFunction with hV + have hmem0 : assetFloor ∈ P.toExtended.feasible s := ⟨le_rfl, le_max_left _ _⟩ + have hc0 : 0 < P.consumption s assetFloor := by + have := P.minConsumption_le_consumption_floor s + have := P.minConsumption_pos + simp only [consumption]; linarith + have hrw0 := P.reward_eq_coe hs hmem0 hc0 + have hRmax : P.resources s - assetFloor ≤ P.maxConsumption := + P.consumption_le_maxConsumption hs (le_refl assetFloor) + -- nothing feasible beats borrowing to the limit + have hdom : ∀ a ∈ P.toExtended.feasible s, + P.toExtended.objectiveE V s a + ≤ ((P.u (P.consumption s assetFloor) + + P.discount * P.toExtended.expect V (s, assetFloor) : ℝ) : EReal) := by + intro a ha + rcases eq_or_ne (P.toExtended.reward (s, a)) ⊥ with hbot | hne + · rw [ExtendedStochasticProgram.objectiveE, hbot, EReal.bot_add]; exact bot_le + have hca : P.consumption s a ∈ P.dom := P.consumption_mem_dom_of_ne_bot hs ha hne + have ha0 : assetFloor ≤ a := ha.1 + have hacap : a ≤ assetCap := le_trans ha.2 (P.maxSaving_le_assetCap _) + have hrwa := P.reward_eq_coe_dom hs ha hca + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + -- the utility gain from consuming instead of saving + have hu : m * (a - assetFloor) + ≤ P.u (P.consumption s assetFloor) - P.u (P.consumption s a) := by + have hcc : P.consumption s a ≤ P.consumption s assetFloor := by + simp only [consumption]; linarith + have := hmarg (P.consumption s assetFloor) (P.consumption s a) hca hcc + (by simp only [consumption]; exact le_rfl) + have hdiff : P.consumption s assetFloor - P.consumption s a = a - assetFloor := by + simp only [consumption]; ring + rwa [hdiff] at this + -- the continuation loss from not saving + have hexp := P.abs_expect_sub_le_lipschitz hlip s.2 (a := a) (b := assetFloor) + ⟨ha0, hacap⟩ ⟨le_rfl, P.assetFloor_le_assetCap⟩ s.1 s.1 + rw [abs_le] at hexp + simp only [ExtendedStochasticProgram.objectiveE, hrwa, hdx, ← EReal.coe_add, + EReal.coe_le_coe_iff] + have hprod : (P.discount : ℝ) * (P.toExtended.expect V ((s.1, s.2), a) + - P.toExtended.expect V ((s.1, s.2), assetFloor)) + ≤ P.discount * (L * |a - assetFloor|) := + mul_le_mul_of_nonneg_left hexp.2 hβ + rw [abs_of_nonneg (by linarith : (0:ℝ) ≤ a - assetFloor)] at hprod + have hpp : (P.discount : ℝ) * (L * (a - assetFloor)) ≤ m * (a - assetFloor) := by + rcases eq_or_lt_of_le ha0 with h | h + · rw [← h]; simp + · nlinarith + have hfix : ∀ c : ℝ, P.toExtended.expect V ((s.1, s.2), c) + = P.toExtended.expect V (s, c) := fun _ => rfl + rw [hfix, hfix] at hprod + linarith + -- so borrowing to the limit attains the maximum, and the maximiser is unique + have hle : P.toExtended.bellmanFn V s + ≤ P.u (P.consumption s assetFloor) + + P.discount * P.toExtended.expect V (s, assetFloor) := + P.toExtended.bellmanFn_le V hdom + have hdx : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + have hge : ((P.u (P.consumption s assetFloor) + + P.discount * P.toExtended.expect V (s, assetFloor) : ℝ) : EReal) + ≤ ((P.toExtended.bellmanFn V s : ℝ) : EReal) := by + have h := P.toExtended.le_bellmanFn V hmem0 + rwa [ExtendedStochasticProgram.objectiveE, hrw0, hdx, ← EReal.coe_add] at h + rw [EReal.coe_le_coe_iff] at hge + refine (P.eq_policy_of_optimal hs hmem0 ?_).symm + rw [ExtendedStochasticProgram.objectiveE, hrw0, hdx, ← EReal.coe_add, EReal.coe_eq_coe_iff] + linarith + +/-- The global corner condition, as a corollary. -/ +theorem policy_eq_zero_of_corner {L m : ℝ} + (hlip : ∀ z : Z, ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| ≤ L * |x - y|) + (hmarg : ∀ c d : ℝ, d ∈ P.dom → d ≤ c → c ≤ P.maxConsumption → m * (c - d) ≤ P.u c - P.u d) + (hcond : P.discount * L < m) {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) : + P.policy s = assetFloor := + P.policy_eq_zero_of_corner_at hlip hs + (fun c d hd hdc hc => hmarg c d hd hdc + (le_trans hc (P.consumption_le_maxConsumption hs (le_refl assetFloor)))) + hcond + +/-- For `u c = -1/c`, the marginal-utility bound up to `R` is `1/R²`. So the state-dependent +threshold reads `β L < 1 / resources²`: the constraint binds exactly where resources are small +enough, and `resources = income + (1+r)·assets` rises with BOTH the income draw and the asset +position. -/ +theorem crra_marginal {R : ℝ} (hR : 0 < R) (c d : ℝ) (hd : 0 < d) (hdc : d ≤ c) (hcR : c ≤ R) : + (1 / R ^ 2) * (c - d) ≤ (-c⁻¹) - (-d⁻¹) := by + have hc : 0 < c := lt_of_lt_of_le hd hdc + have hkey : -c⁻¹ - -d⁻¹ = (c - d) / (c * d) := by field_simp; ring + rw [show (1 : ℝ) / R ^ 2 * (c - d) = (c - d) / R ^ 2 by ring, hkey, + div_le_div_iff₀ (by positivity) (by positivity)] + nlinarith [mul_nonneg (sub_nonneg.mpr hdc) (sub_nonneg.mpr (show c * d ≤ R ^ 2 by nlinarith))] + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationMonotone.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationMonotone.lean new file mode 100644 index 000000000..5319e7c2d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationMonotone.lean @@ -0,0 +1,299 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuation +public import LeanPool.AiyagariEquilibrium.Analysis.DecreasingIncrements + +/-! +# The optimal policy is increasing in assets + +A household that starts richer saves at least as much, holding the income state fixed. + +The argument is the classical monotone-comparative-statics one, but it needs neither Topkis +nor any derivative. Two observations do all the work. + +First, the continuation value `β E[V(a', z')]` depends on the chosen saving `a'` and on the +*current* income state, but not on current assets. So when comparing two asset levels with +the same income state, the continuation terms cancel, and increasing differences of the +objective reduces to increasing differences of `u` composed with the budget — which is +exactly `ConcaveOn.sub_le_sub_of_shift`, concavity of `u`. + +Second, the usual Topkis conclusion (the argmax is an increasing *correspondence*) is not +needed, because uniqueness of the optimal action is already proved. If the richer household +saved strictly less, the exchange argument shows the poorer household's choice is *also* +optimal for the richer one, and uniqueness collapses the two. + +The feasible set grows with assets, which is what makes both exchanges legal. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### The budget grows with assets -/ + +theorem resources_mono {a a' : ℝ} {z : Z} (h : a ≤ a') : + P.resources (a, z) ≤ P.resources (a', z) := by + have hm : max assetFloor a ≤ max assetFloor a' := max_le_max le_rfl h + have hi := P.interest_gt_neg_one + simp only [resources] + nlinarith + +theorem maxSaving_mono {a a' : ℝ} {z : Z} (h : a ≤ a') : + P.maxSaving (a, z) ≤ P.maxSaving (a', z) := + max_le_max le_rfl (min_le_min le_rfl (P.resources_mono h)) + +theorem feasible_mono {a a' : ℝ} {z : Z} (h : a ≤ a') : + P.toExtended.feasible (a, z) ⊆ P.toExtended.feasible (a', z) := by + simp only [P.feasible_eq] + exact Icc_subset_Icc le_rfl (P.maxSaving_mono h) + +/-! ### The objective in the reals -/ + +/-- The continuation value of saving `x` when the current income state is `z`. It does not +depend on current assets, which is what makes the comparison below work. -/ +noncomputable def cont (z : Z) (x : ℝ) : ℝ := + ∑ z', P.transitionMatrix z z' * P.toExtended.valueFunction (x, z') + +/-- The objective, as a real number. Valid wherever consumption is positive. -/ +noncomputable def objR (s : ℝ × Z) (x : ℝ) : ℝ := + P.u (P.consumption s x) + P.discount * P.cont s.2 x + +theorem objectiveE_eq_coe {s : ℝ × Z} {x : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (hx : x ∈ P.toExtended.feasible s) (hc : P.consumption s x ∈ P.dom) : + P.toExtended.objectiveE P.toExtended.valueFunction s x = ((P.objR s x : ℝ) : EReal) := by + rw [ExtendedStochasticProgram.objectiveE, P.reward_eq_coe_dom hs hx hc, ← EReal.coe_add] + rfl + +/-- Any feasible action with positive consumption is worth at most the optimum. -/ +theorem objR_le_of_mem {s : ℝ × Z} {x : ℝ} (hs : s.1 ∈ Icc assetFloor assetCap) + (hx : x ∈ P.toExtended.feasible s) (hc : P.consumption s x ∈ P.dom) : + P.objR s x ≤ P.objR s (P.policy s) := by + have hpol : ((P.objR s (P.policy s) : ℝ) : EReal) + = ((P.toExtended.bellmanFn P.toExtended.valueFunction s : ℝ) : EReal) := by + rw [← P.objectiveE_eq_coe hs (P.policy_mem s) (P.consumption_policy_mem_dom hs)] + exact P.policy_optimal s + have h1 := P.toExtended.le_bellmanFn P.toExtended.valueFunction hx + rw [P.objectiveE_eq_coe hs hx hc, ← hpol] at h1 + exact_mod_cast h1 + +/-! ### Monotonicity -/ + +/-- The continuation value of saving `x`, read against `v`. -/ +noncomputable def contOf (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (x : ℝ) : ℝ := + ∑ z' : Z, P.transitionMatrix z z' * v (x, z') + +/-- The objective, as a real number, read against `v`. -/ +noncomputable def objROf (v : (ℝ × Z) →ᵇ ℝ) (s : ℝ × Z) (x : ℝ) : ℝ := + P.u (P.consumption s x) + P.discount * P.contOf v s.2 x + +theorem concaveOn_contOf {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) (z : Z) : + ConcaveOn ℝ (Icc assetFloor assetCap) (P.contOf v z) := by + refine ⟨convex_Icc _ _, fun x hx y hy θ φ hθ hφ hθφ => ?_⟩ + simp only [contOf, smul_eq_mul, Finset.mul_sum, ← Finset.sum_add_distrib] + refine Finset.sum_le_sum fun z' _ => ?_ + have hz' := (hv z').2 hx hy hθ hφ hθφ + simp only [smul_eq_mul] at hz' + nlinarith [mul_le_mul_of_nonneg_left hz' (P.transitionMatrix_nonneg z z')] + +theorem abs_contOf_le (v : (ℝ × Z) →ᵇ ℝ) (z : Z) (x : ℝ) : |P.contOf v z x| ≤ ‖v‖ := by + exact P.toExtended.abs_expect_le v ((assetFloor, z), x) + +/-- The objective in the reals agrees with the extended objective where consumption is +positive. -/ +theorem objectiveE_eq_coe_of (v : (ℝ × Z) →ᵇ ℝ) {a : ℝ} {z : Z} {x : ℝ} + (ha : a ∈ Icc assetFloor assetCap) (hx : x ∈ P.toExtended.feasible (a, z)) + (hcx : P.consumption (a, z) x ∈ P.dom) : + P.toExtended.objectiveE v (a, z) x = ((P.objROf v (a, z) x : ℝ) : EReal) := by + rw [ExtendedStochasticProgram.objectiveE, P.reward_eq_coe_dom (s := (a, z)) ha hx hcx, + ← EReal.coe_add] + rfl + +/-- **Any feasible action with positive consumption is worth at most the optimum**, whatever +the continuation. -/ +theorem objROf_le_of_mem (v : (ℝ × Z) →ᵇ ℝ) {a : ℝ} {z : Z} {x : ℝ} + (ha : a ∈ Icc assetFloor assetCap) (hx : x ∈ P.toExtended.feasible (a, z)) + (hcx : P.consumption (a, z) x ∈ P.dom) : + P.objROf v (a, z) x ≤ P.objROf v (a, z) (P.policyOf v (a, z)) := by + have hc : P.consumption (a, z) (P.policyOf v (a, z)) ∈ P.dom := + (P.bellmanFn_eq_of_optimal ha (P.policyOf_mem v (a, z)) + (P.policyOf_optimal v (a, z))).1 + have hle := P.toExtended.le_bellmanFn v hx + rw [P.objectiveE_eq_coe_of v ha hx hcx, EReal.coe_le_coe_iff] at hle + have heq := P.policyOf_optimal v (a, z) + rw [P.objectiveE_eq_coe_of v ha (P.policyOf_mem v (a, z)) hc, + EReal.coe_eq_coe_iff] at heq + exact hle.trans_eq heq.symm + +/-! ### Monotonicity of the saving policy -/ + +/-- **The optimal policy is increasing in assets, whatever the continuation.** The exchange +argument uses concavity and uniqueness against `v`. -/ +theorem policyOf_mono {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ConcaveOn ℝ (Icc assetFloor assetCap) fun a => v (a, z)) {a a' : ℝ} {z : Z} + (ha : a ∈ Icc assetFloor assetCap) (ha' : a' ∈ Icc assetFloor assetCap) (hle : a ≤ a') : + P.policyOf v (a, z) ≤ P.policyOf v (a', z) := by + by_contra hcon + rw [not_le] at hcon + have hR : P.resources (a, z) ≤ P.resources (a', z) := P.resources_mono hle + have hys : P.policyOf v (a, z) ∈ P.toExtended.feasible (a, z) := P.policyOf_mem v _ + have hys' : P.policyOf v (a, z) ∈ P.toExtended.feasible (a', z) := P.feasible_mono hle hys + have hy's' : P.policyOf v (a', z) ∈ P.toExtended.feasible (a', z) := P.policyOf_mem v _ + have hy's : P.policyOf v (a', z) ∈ P.toExtended.feasible (a, z) := by + rw [P.feasible_eq] at hys ⊢ + rw [P.feasible_eq] at hy's' + exact ⟨hy's'.1, le_trans hcon.le hys.2⟩ + have hcsy : P.consumption (a, z) (P.policyOf v (a, z)) ∈ P.dom := + (P.bellmanFn_eq_of_optimal ha (P.policyOf_mem v _) (P.policyOf_optimal v _)).1 + have hcs'y' : P.consumption (a', z) (P.policyOf v (a', z)) ∈ P.dom := + (P.bellmanFn_eq_of_optimal ha' (P.policyOf_mem v _) (P.policyOf_optimal v _)).1 + have hcsy' : P.consumption (a, z) (P.policyOf v (a', z)) ∈ P.dom := by + refine P.dom_upward hcsy ?_ + simp only [consumption]; linarith + have hcs'y : P.consumption (a', z) (P.policyOf v (a, z)) ∈ P.dom := by + refine P.dom_upward hcsy ?_ + simp only [consumption]; linarith + -- increasing differences, from concavity of `u` alone + have hshift := P.strictConcaveOn_u_dom.concaveOn.sub_le_sub_of_shift + (c₁ := P.resources (a, z) - P.policyOf v (a, z)) + (c₂ := P.resources (a, z) - P.policyOf v (a', z)) + (Δ := P.resources (a', z) - P.resources (a, z)) + (by simpa only [consumption] using hcsy) + (by + have he : P.resources (a, z) - P.policyOf v (a', z) + + (P.resources (a', z) - P.resources (a, z)) + = P.consumption (a', z) (P.policyOf v (a', z)) := by simp only [consumption]; ring + rw [he]; exact hcs'y') + (by linarith) (by linarith) + have hopt_s := P.objROf_le_of_mem v ha hy's hcsy' + have hge : P.objROf v (a', z) (P.policyOf v (a', z)) + ≤ P.objROf v (a', z) (P.policyOf v (a, z)) := by + simp only [objROf, consumption] at hopt_s ⊢ + have e₁ : P.resources (a, z) - P.policyOf v (a', z) + + (P.resources (a', z) - P.resources (a, z)) + = P.resources (a', z) - P.policyOf v (a', z) := by ring + have e₂ : P.resources (a, z) - P.policyOf v (a, z) + + (P.resources (a', z) - P.resources (a, z)) + = P.resources (a', z) - P.policyOf v (a, z) := by ring + rw [e₁, e₂] at hshift + linarith + have hopt' : P.objROf v (a', z) (P.policyOf v (a, z)) + = P.objROf v (a', z) (P.policyOf v (a', z)) := + le_antisymm (P.objROf_le_of_mem v ha' hys' hcs'y) hge + have hbell : P.toExtended.objectiveE v (a', z) (P.policyOf v (a, z)) + = ((P.toExtended.bellmanFn v (a', z) : ℝ) : EReal) := by + rw [P.objectiveE_eq_coe_of v ha' hys' hcs'y, hopt', + ← P.objectiveE_eq_coe_of v ha' hy's' hcs'y'] + exact P.policyOf_optimal v _ + exact absurd (P.optimal_action_unique_of_concave hv (s := (a', z)) ha' hys' hy's' + hbell (P.policyOf_optimal v _)) (by linarith) + +/-- **The optimal policy is increasing in assets.** Holding the income state fixed, a +household with more assets saves at least as much. -/ +theorem policy_mono {a a' : ℝ} {z : Z} (ha : a ∈ Icc assetFloor assetCap) + (ha' : a' ∈ Icc assetFloor assetCap) (hle : a ≤ a') : + P.policy (a, z) ≤ P.policy (a', z) := by + simpa only [P.policyOf_valueFunction] using + P.policyOf_mono P.concaveOn_valueFunction ha ha' hle + +/-- `v` varies by at most `G` over the asset region. -/ +def OscOn (_P : IncomeFluctuation Z assetFloor assetCap) (v : (ℝ × Z) →ᵇ ℝ) (G : ℝ) : Prop := + ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, ∀ z z' : Z, + v (x, z) - v (y, z') ≤ G + +theorem OscOn.mono {v : (ℝ × Z) →ᵇ ℝ} {G G' : ℝ} (h : P.OscOn v G) (hG : G ≤ G') : + P.OscOn v G' := fun x hx y hy z z' => (h x hx y hy z z').trans hG + +/-- The continuation inherits the bound, even across different income states. -/ +theorem contOf_sub_le_osc {v : (ℝ × Z) →ᵇ ℝ} {G : ℝ} (h : P.OscOn v G) {x y : ℝ} + (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) (z z' : Z) : + P.contOf v z x - P.contOf v z' y ≤ G := by + classical + obtain ⟨z₁, -, hz₁⟩ := Finset.exists_max_image Finset.univ (fun w : Z => v (x, w)) + ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + obtain ⟨z₂, -, hz₂⟩ := Finset.exists_min_image Finset.univ (fun w : Z => v (y, w)) + ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + have h1 : P.contOf v z x ≤ v (x, z₁) := by + have hle : ∑ w : Z, P.transitionMatrix z w * v (x, w) + ≤ ∑ w : Z, P.transitionMatrix z w * v (x, z₁) := + Finset.sum_le_sum fun w _ => + mul_le_mul_of_nonneg_left (hz₁ w (Finset.mem_univ _)) (P.transitionMatrix_nonneg z w) + rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] at hle + exact hle + have h2 : v (y, z₂) ≤ P.contOf v z' y := by + have hle : ∑ w : Z, P.transitionMatrix z' w * v (y, z₂) + ≤ ∑ w : Z, P.transitionMatrix z' w * v (y, w) := + Finset.sum_le_sum fun w _ => + mul_le_mul_of_nonneg_left (hz₂ w (Finset.mem_univ _)) (P.transitionMatrix_nonneg z' w) + rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] at hle + exact hle + linarith [h x hx y hy z₁ z₂] + +/-- A bounded continuation oscillates by at most twice its norm. -/ +theorem oscOn_two_norm (v : (ℝ × Z) →ᵇ ℝ) : P.OscOn v (2 * ‖v‖) := by + intro x _ y _ z z' + have hx := abs_le.mp (show |v (x, z)| ≤ ‖v‖ by + simpa only [Real.norm_eq_abs] using v.norm_coe_le_norm (x, z)) + have hy := abs_le.mp (show |v (y, z')| ≤ ‖v‖ by + simpa only [Real.norm_eq_abs] using v.norm_coe_le_norm (y, z')) + linarith + +theorem valueFunction_le_oscBound (s : ℝ × Z) : + P.toExtended.valueFunction s ≤ P.u P.maxConsumption / (1 - P.discount) := by + have hβ : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + refine P.toExtended.valueFunction_le_const ?_ s + have hne : (1 : ℝ) - P.discount ≠ 0 := by linarith + have key : P.u P.maxConsumption + + (P.discount : ℝ) * (P.u P.maxConsumption / (1 - P.discount)) + = P.u P.maxConsumption / (1 - P.discount) := by field_simp; ring + rw [show P.toExtended.rewardMax = P.u P.maxConsumption from rfl, + show ((P.toExtended.discount : ℝ)) = (P.discount : ℝ) from rfl] + exact le_of_eq key + +theorem oscBound_le_valueFunction (s : ℝ × Z) : + P.u P.minConsumption / (1 - P.discount) ≤ P.toExtended.valueFunction s := by + have hβ : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + refine P.toExtended.const_le_valueFunction ?_ s + have hne : (1 : ℝ) - P.discount ≠ 0 := by linarith + have key : P.u P.minConsumption / (1 - P.discount) + = P.u P.minConsumption + (P.discount : ℝ) * (P.u P.minConsumption / (1 - P.discount)) := by + field_simp; ring + rw [show P.toExtended.rewardMin = P.u P.minConsumption from rfl, + show ((P.toExtended.discount : ℝ)) = (P.discount : ℝ) from rfl] + exact le_of_eq key + +/-- The spread the iteration can produce: the reward's spread, amplified by `(1 - β)⁻¹`. At a +zero borrowing limit this is definitionally `CRRAConstants.oscGap`, so the witnesses' bounds on +that transfer unchanged. -/ +noncomputable def oscSpread : ℝ := + (P.u P.maxConsumption - P.u P.minConsumption) / (1 - P.discount) + +theorem oscSpread_nonneg : 0 ≤ P.oscSpread := by + have hβ : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + refine div_nonneg (sub_nonneg.mpr ?_) (by linarith) + exact P.monotoneOn_u_dom (P.mem_dom_of_pos P.minConsumption_pos) + (P.mem_dom_of_pos P.maxConsumption_pos) P.minConsumption_le_maxConsumption + +/-- The fixed-point value function satisfies the oscillation premise used by saving bounds. -/ +theorem oscOn_valueFunction : P.OscOn P.toExtended.valueFunction P.oscSpread := by + intro x _ y _ z z' + dsimp [oscSpread] + rw [sub_div] + exact sub_le_sub (P.valueFunction_le_oscBound (x, z)) (P.oscBound_le_valueFunction (y, z')) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRate.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRate.lean new file mode 100644 index 000000000..d4d013ff6 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRate.lean @@ -0,0 +1,334 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRegion +public import LeanPool.AiyagariEquilibrium.Distribution.Feller + +/-! +# Varying the interest rate + +The cross-rate estimates so far take two programmes plus a hypothesis for each field they +must share -- income, maximum income, minimum income, the transition matrix, the discount +factor, the utility function. Six hypotheses is a signal that the wrong object is being +quantified over: in the comparative statics only the INTEREST RATE varies. + +`withRate` makes that structural. Every field except `interest` is inherited, so every +sharing hypothesis becomes `rfl` and disappears from the statements, and +`fun r => P.withRate r _` is the family the equilibrium theorem wants. + +The other content here is the step that turns the two argument gaps -- in consumption and in +the continuation value -- into a comparison of objectives. It is stated with the gaps as +hypotheses, so the analytic work (uniform continuity) and the arithmetic (extended reals) +stay separate. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The same household problem at a different interest rate. -/ +def withRate (r : ℝ) (hr : P.RateOK r) : IncomeFluctuation Z assetFloor assetCap := + { P with interest := r, interest_gt_neg_one := hr.1, minConsumption_le_floor := hr.2 } + +@[simp] theorem withRate_interest (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).interest = r := rfl +@[simp] theorem withRate_income (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).income = P.income := rfl +@[simp] theorem withRate_maxIncome (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).maxIncome = P.maxIncome := rfl +@[simp] theorem withRate_minIncome (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).minIncome = P.minIncome := rfl +@[simp] theorem withRate_minConsumption (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).minConsumption = P.minConsumption := rfl +@[simp] theorem withRate_u (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).u = P.u := rfl +@[simp] theorem withRate_discount (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).discount = P.discount := rfl +@[simp] theorem withRate_transitionMatrix (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).transitionMatrix = P.transitionMatrix := rfl +@[simp] theorem withRate_region (r : ℝ) (hr : P.RateOK r) : + (P.withRate r hr).region = P.region := rfl +@[simp] theorem withRate_loBound (r : ℝ) (hr : P.RateOK r) (v : (ℝ × Z) →ᵇ ℝ) : + (P.withRate r hr).toExtended.loBound v = P.toExtended.loBound v := rfl + +/-- **From argument gaps to an objective comparison.** + +The reward is real at both actions because consumption is positive at both -- which is what +the cutoff delivers -- so the extended-real objective collapses to a real one and the +comparison is ordinary arithmetic. Keeping the two gaps as hypotheses separates the analytic +work from the arithmetic. -/ +theorem objectiveE_le_of_gaps (v : (ℝ × Z) →ᵇ ℝ) {r r' : ℝ} (hr : P.RateOK r) (hr' : P.RateOK r') + {s : ℝ × Z} (hs : s ∈ P.region) {a a' : ℝ} + (ha : a ∈ (P.withRate r hr).toExtended.feasible s) + (ha' : a' ∈ (P.withRate r' hr').toExtended.feasible s) + (hcA : 0 < (P.withRate r hr).consumption s a) + (hcB : 0 < (P.withRate r' hr').consumption s a') + {e₁ e₂ : ℝ} + (hu : P.u ((P.withRate r hr).consumption s a) + - P.u ((P.withRate r' hr').consumption s a') ≤ e₁) + (hv : (P.withRate r hr).toExtended.expect v (s, a) + - (P.withRate r' hr').toExtended.expect v (s, a') ≤ e₂) : + (P.withRate r hr).toExtended.objectiveE v s a + ≤ (P.withRate r' hr').toExtended.objectiveE v s a' + + ((e₁ + P.discount * e₂ : ℝ) : EReal) := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hrwA := (P.withRate r hr).reward_eq_coe hs ha hcA + have hrwB := (P.withRate r' hr').reward_eq_coe hs ha' hcB + have hdA : ((P.withRate r hr).toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + have hdB : ((P.withRate r' hr').toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + simp only [ExtendedStochasticProgram.objectiveE, hrwA, hrwB, hdA, hdB] + rw [← EReal.coe_add, add_assoc, ← EReal.coe_add, ← EReal.coe_add, EReal.coe_le_coe_iff] + have hu' : (P.withRate r hr).u = P.u := rfl + have hu'' : (P.withRate r' hr').u = P.u := rfl + rw [hu', hu''] at * + nlinarith [hu, hv, hβ] + +/-! ### The sup-norm estimate + +Everything assembled. Choosing the rate gap small enough makes the Bellman operator move by +less than any prescribed amount, uniformly over the region. + +Three separate smallness requirements are imposed on the rate gap, and the `+ 1` in each +constant is only to keep the divisions safe when the asset cap is zero: + +* the saving gap must fall under the continuation value's modulus, +* the consumption gap must fall under utility's modulus, +* the consumption gap must be at most `δ/2`, so that consumption at the SECOND rate stays + positive given that the cutoff bounds it at the first. Without this the reward at the + second rate could be `⊥` and the comparison would be vacuous. + +Actions below the cutoff need no comparison at all: they are already worth less than +`loBound v`, which is itself below the other operator's value. -/ + +theorem exists_rate_modulus (hd : P.Unbounded) (v : (ℝ × Z) →ᵇ ℝ) {rlo rhi : ℝ} + (hrlo : 0 < 1 + rlo) + {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (r r' : ℝ) (hr : P.RateOK r) (hr' : P.RateOK r'), + rlo ≤ r → r ≤ rhi → rlo ≤ r' → r' ≤ rhi → |r - r'| < η → + ∀ s ∈ P.region, + (P.withRate r hr).toExtended.bellmanFn v s + ≤ (P.withRate r' hr').toExtended.bellmanFn v s + ε := by + classical + have hcap := P.assetFloor_le_assetCap + have hmaxI : 0 ≤ P.maxIncome := le_trans P.minIncome_pos.le P.minIncome_le_maxIncome + obtain ⟨δ, hδ, hδspec⟩ := P.exists_cutoff hd v + have hB : 0 ≤ P.stateBoundCross rlo := P.stateBoundCross_nonneg hrlo + set K₁ : ℝ := P.stateBoundCross rlo + 1 with hK₁def + set K₂ : ℝ := 2 * P.stateBoundCross rlo + 1 with hK₂def + have hK₁ : 1 ≤ K₁ := by rw [hK₁def]; linarith + have hK₂ : 1 ≤ K₂ := by rw [hK₂def]; linarith + obtain ⟨η₁, hη₁, hmu⟩ := + P.exists_modulus_u (lo := δ / 2) (hi := P.maxIncome + (1 + rhi) * assetCap - assetFloor) + (by linarith) (half_pos hε) + obtain ⟨η₂, hη₂, hmv⟩ := exists_modulus_v (assetCap := assetCap) v (half_pos hε) + refine ⟨min (min (η₂ / K₁) (η₁ / K₂)) (δ / (2 * K₂)), by positivity, ?_⟩ + intro r r' hr hr' hlo hhi hlo' hhi' hclose s hs + set A := P.withRate r hr with hA + set B := P.withRate r' hr' with hB + refine A.toExtended.bellmanFn_le v fun a ha => ?_ + -- actions below the cutoff need no comparison + rcases le_or_gt ((P.toExtended.loBound v : ℝ) : EReal) + (A.toExtended.objectiveE v s a) with hcut | hbelow + · -- the matched action at the other rate + have hafe := ha + rw [A.feasible_eq_image] at hafe + obtain ⟨θ, hθ, rfl⟩ := hafe + set a' : ℝ := (assetFloor + θ * B.savingRoom s) with ha'def + have ha' : a' ∈ B.toExtended.feasible s := by rw [B.feasible_eq_image]; exact ⟨θ, hθ, rfl⟩ + -- consumption at the first rate is bounded below by the cutoff + have hcA : δ ≤ A.consumption s ((assetFloor + θ * A.savingRoom s)) := + A.le_consumption_of_cutoff v hδspec hs ha hcut + -- the two consumptions are close, so the second is positive too + have hgapc : |A.consumption s ((assetFloor + θ * A.savingRoom s)) - B.consumption s a'| + ≤ |r - r'| * K₂ := by + have hclamp := abs_clamped_consumption_sub_le (P := A) (Q := B) rfl rfl hrlo hlo hlo' hθ s + have hA' : min A.maxConsumption (A.consumption s ((assetFloor + θ * A.savingRoom s))) + = A.consumption s ((assetFloor + θ * A.savingRoom s)) := + min_eq_right (A.consumption_le_maxConsumption hs + (le_add_of_nonneg_right (mul_nonneg hθ.1 (A.savingRoom_nonneg s)))) + have hB' : min B.maxConsumption (B.consumption s a') = B.consumption s a' := + min_eq_right (B.consumption_le_maxConsumption hs + (le_add_of_nonneg_right (mul_nonneg hθ.1 (B.savingRoom_nonneg s)))) + rw [hA', hB'] at hclamp + rw [show A.stateBoundCross rlo = P.stateBoundCross rlo from rfl] at hclamp + refine le_trans hclamp ?_ + have hint : |A.interest - B.interest| = |r - r'| := rfl + rw [hint] + refine mul_le_mul_of_nonneg_left ?_ (abs_nonneg _) + rw [hK₂def] + linarith + have hsmall2 : |r - r'| * K₂ ≤ δ / 2 := by + have h := lt_of_lt_of_le hclose (min_le_right _ _) + rw [lt_div_iff₀ (by linarith)] at h + nlinarith [h] + have hcB : δ / 2 ≤ B.consumption s a' := by + rw [abs_le] at hgapc; linarith [hgapc.1, hgapc.2] + -- utility moves little + have hupper : ∀ {X : IncomeFluctuation Z assetFloor assetCap} {b : ℝ}, + X.consumption s b ≤ X.maxConsumption → X.maxIncome = P.maxIncome → + X.interest ≤ rhi → + X.consumption s b ≤ P.maxIncome + (1 + rhi) * assetCap - assetFloor := by + intro X b hb hmi hint + have : X.maxConsumption = X.maxIncome + (1 + X.interest) * assetCap - assetFloor := rfl + rw [this, hmi] at hb + nlinarith [P.assetCap_nonneg] + have hgu : |P.u (A.consumption s ((assetFloor + θ * A.savingRoom s))) + - P.u (B.consumption s a')| < ε / 2 := by + have hbA := A.consumption_le_maxConsumption hs + (le_add_of_nonneg_right (mul_nonneg hθ.1 (A.savingRoom_nonneg s))) + have hbB := B.consumption_le_maxConsumption hs + (le_add_of_nonneg_right (mul_nonneg hθ.1 (B.savingRoom_nonneg s))) + refine hmu _ ⟨by linarith, hupper hbA rfl hhi⟩ _ ⟨by linarith, hupper hbB rfl hhi'⟩ ?_ + have h := lt_of_lt_of_le (lt_of_lt_of_le hclose (min_le_left _ _)) (min_le_right _ _) + rw [lt_div_iff₀ (by linarith)] at h + rw [abs_le] at hgapc + rw [abs_lt]; constructor <;> linarith [hgapc.1, hgapc.2] + -- the continuation value moves little + have hgapa : |(assetFloor + θ * A.savingRoom s) - a'| < η₂ := by + have hms := abs_maxSaving_sub_le (P := A) (Q := B) rfl rfl hrlo hlo hlo' s + have hint : |A.interest - B.interest| = |r - r'| := rfl + rw [hint] at hms + have h := lt_of_lt_of_le (lt_of_lt_of_le hclose (min_le_left _ _)) (min_le_left _ _) + rw [lt_div_iff₀ (by linarith)] at h + rw [ha'def, show assetFloor + θ * A.savingRoom s - (assetFloor + θ * B.savingRoom s) + = θ * (A.maxSaving s - B.maxSaving s) by simp only [savingRoom]; ring, + abs_mul, abs_of_nonneg hθ.1] + calc θ * |A.maxSaving s - B.maxSaving s| ≤ 1 * (|r - r'| * P.stateBoundCross rlo) := by + refine mul_le_mul hθ.2 hms (abs_nonneg _) (by norm_num) + _ ≤ |r - r'| * K₁ := by rw [one_mul, hK₁def]; nlinarith [abs_nonneg (r - r')] + _ < η₂ := h + have hgv : |A.toExtended.expect v (s, (assetFloor + θ * A.savingRoom s)) + - B.toExtended.expect v (s, a')| ≤ ε / 2 := by + have hmem : ∀ x : ℝ, x ∈ A.toExtended.feasible s → x ∈ Icc assetFloor assetCap := fun x hx => + A.feasible_subset_region hx + have h1 : (assetFloor + θ * A.savingRoom s) ∈ Icc assetFloor assetCap := hmem _ ha + have h2 : a' ∈ Icc assetFloor assetCap := B.feasible_subset_region ha' + have hpA : ∀ z' : Z, A.toExtended.prob z' (s, (assetFloor + θ * A.savingRoom s)) + = P.transitionMatrix s.2 z' := fun _ => rfl + have hpB : ∀ z' : Z, B.toExtended.prob z' (s, a') = P.transitionMatrix s.2 z' := fun _ => rfl + have htA : ∀ z' : Z, A.toExtended.transition z' (s, (assetFloor + θ * A.savingRoom s)) + = ((assetFloor + θ * A.savingRoom s), z') := fun _ => rfl + have htB : ∀ z' : Z, B.toExtended.transition z' (s, a') = (a', z') := fun _ => rfl + simp only [ExtendedStochasticProgram.expect, hpA, hpB, htA, htB, ← Finset.sum_sub_distrib, + ← mul_sub] + calc |∑ z' : Z, P.transitionMatrix s.2 z' + * (v ((assetFloor + θ * A.savingRoom s), z') - v (a', z'))| + ≤ ∑ z' : Z, |P.transitionMatrix s.2 z' + * (v ((assetFloor + θ * A.savingRoom s), z') - v (a', z'))| := + Finset.abs_sum_le_sum_abs _ _ + _ ≤ ∑ z' : Z, P.transitionMatrix s.2 z' * (ε / 2) := by + refine Finset.sum_le_sum fun z' _ => ?_ + rw [abs_mul, abs_of_nonneg (P.transitionMatrix_nonneg s.2 z')] + exact mul_le_mul_of_nonneg_left (hmv z' _ h1 _ h2 hgapa).le + (P.transitionMatrix_nonneg s.2 z') + _ = ε / 2 := by rw [← Finset.sum_mul, P.transitionMatrix_sum, one_mul] + -- combine + have hobj := P.objectiveE_le_of_gaps v hr hr' hs ha ha' + (by linarith) (by linarith) + (e₁ := ε / 2) (e₂ := ε / 2) + (by rw [abs_lt] at hgu; linarith [hgu.2]) + (by rw [abs_le] at hgv; linarith [hgv.2]) + refine le_trans hobj ?_ + have hbB := B.toExtended.le_bellmanFn v ha' + have hβ1 : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + have hβ0 : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + calc B.toExtended.objectiveE v s a' + ((ε / 2 + P.discount * (ε / 2) : ℝ) : EReal) + ≤ ((B.toExtended.bellmanFn v s : ℝ) : EReal) + + ((ε / 2 + P.discount * (ε / 2) : ℝ) : EReal) := by gcongr + _ ≤ ((B.toExtended.bellmanFn v s + ε : ℝ) : EReal) := by + rw [← EReal.coe_add, EReal.coe_le_coe_iff]; nlinarith + · -- below the cutoff: already worth less than the other operator's value + refine le_trans hbelow.le ?_ + rw [EReal.coe_le_coe_iff] + have := (B.toExtended.bellmanFn_bounds v s).1 + have hlb : B.toExtended.loBound v = P.toExtended.loBound v := rfl + rw [hlb] at this + linarith + +/-- **The value function moves continuously with the interest rate, on the region.** + +Stated AT a reference rate rather than between two arbitrary rates, and that is not a +weakening -- it is what makes the argument work. The modulus produced by +`exists_rate_modulus` depends on the continuation value fed to the operator, so comparing two +moving rates would need the family of value functions to be equicontinuous. Fixing the +reference rate makes the continuation value a single fixed function, and continuity at every +point is continuity. -/ +theorem exists_valueFunction_modulus (hd : P.Unbounded) {rlo rhi : ℝ} (hrlo : 0 < 1 + rlo) + {r₀ : ℝ} + (hr₀ : P.RateOK r₀) (hlo₀ : rlo ≤ r₀) (hhi₀ : r₀ ≤ rhi) {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ (r : ℝ) (hr : P.RateOK r), rlo ≤ r → r ≤ rhi → |r - r₀| < η → + ∀ s ∈ P.region, + |(P.withRate r hr).toExtended.valueFunction s + - (P.withRate r₀ hr₀).toExtended.valueFunction s| ≤ ε := by + have hβ1 : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + set V₀ := (P.withRate r₀ hr₀).toExtended.valueFunction with hV₀ + obtain ⟨η, hη, hmod⟩ := + P.exists_rate_modulus hd V₀ (rlo := rlo) (rhi := rhi) hrlo (ε := ε * (1 - P.discount)) + (by positivity) + refine ⟨η, hη, fun r hr hlo hhi hclose s hs => ?_⟩ + -- the operator gap at the fixed continuation value, both directions + have hfix : ∀ t : ℝ × Z, (P.withRate r₀ hr₀).toExtended.bellmanFn V₀ t = V₀ t := fun t => by + rw [hV₀, ← (P.withRate r₀ hr₀).toExtended.bellman_apply, + (P.withRate r₀ hr₀).toExtended.bellman_valueFunction] + have hD : ∀ t : ℝ × Z, t ∈ (P.withRate r hr).region → + |(P.withRate r hr).toExtended.bellmanFn V₀ t - V₀ t| ≤ ε * (1 - P.discount) := by + intro t ht + have h1 := hmod r r₀ hr hr₀ hlo hhi hlo₀ hhi₀ hclose t ht + have h2 := hmod r₀ r hr₀ hr hlo₀ hhi₀ hlo hhi (by rwa [abs_sub_comm]) t ht + rw [hfix t] at h1 h2 + rw [abs_le] + constructor <;> linarith + -- and the bridge turns it into a bound on the value functions + have hbr := abs_valueFunction_sub_le_region (P := P.withRate r hr) (Q := P.withRate r₀ hr₀) + hD hs + have hdisc : ((P.withRate r hr).discount : ℝ) = (P.discount : ℝ) := rfl + rw [hdisc] at hbr + refine le_trans hbr ?_ + rw [div_le_iff₀ (by linarith)] + +/-! ### What policy continuity is for + +The distribution side consumes the policy only through the Markov operator, and only through +its SUP norm: the closed-graph argument for `r ↦ μ r` needs + + ‖markovOp_r h - markovOp_r₀ h‖ → 0 + +because `∫ markovOp_r h dμ_r` splits into `∫ (markovOp_r h - markovOp_r₀ h) dμ_r`, bounded by +that norm, plus a term weak convergence handles. Pointwise convergence of the policy would +not do. + +The lemma below is the interface: it reduces the operator gap to the gap in the SUCCESSOR +STATES, which is where the policy enters. Combined with uniform continuity of `h` on the +compact state space, uniform convergence of the policy is exactly what is needed, and +nothing more. -/ + +theorem abs_markovFn_sub_le {A B : IncomeFluctuation Z assetFloor assetCap} + (hAB : A.transitionMatrix = B.transitionMatrix) (h : A.State →ᵇ ℝ) {ε : ℝ} + (hclose : ∀ s : A.State, ∀ z' : Z, |h (A.nextState s z') - h (B.nextState s z')| ≤ ε) + (s : A.State) : |A.markovFn h s - B.markovFn h s| ≤ ε := by + have hprob : ∀ z' : Z, B.prob s z' = A.prob s z' := fun z' => by + simp only [IncomeFluctuation.prob, hAB] + simp only [IncomeFluctuation.markovFn, hprob, ← Finset.sum_sub_distrib, ← mul_sub] + calc |∑ z' : Z, A.prob s z' * (h (A.nextState s z') - h (B.nextState s z'))| + ≤ ∑ z' : Z, |A.prob s z' * (h (A.nextState s z') - h (B.nextState s z'))| := + Finset.abs_sum_le_sum_abs _ _ + _ ≤ ∑ z' : Z, A.prob s z' * ε := by + refine Finset.sum_le_sum fun z' _ => ?_ + rw [abs_mul, abs_of_nonneg (A.prob_nonneg s z')] + exact mul_le_mul_of_nonneg_left (hclose s z') (A.prob_nonneg s z') + _ = ε := by rw [← Finset.sum_mul, A.prob_sum, one_mul] + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateMonotone.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateMonotone.lean new file mode 100644 index 000000000..04a09d8e4 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateMonotone.lean @@ -0,0 +1,213 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationConsumption +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRate + +/-! +# Light (2018) Theorem 1: saving rises with the interest rate + +Numbered Light references use the October 31, 2018 author draft, printed page 13: +https://www.researchgate.net/publication/314238962 + +This is the last link in the uniqueness chain. `CapitalSupplyMonotone` turns a uniformly larger +policy into larger aggregate capital (Theorem 2), and `Equilibrium.Uniqueness` turns monotone +capital supply into a unique equilibrium rate (Theorem 3). What is still needed is that the +policy IS uniformly larger at the higher rate, which is Theorem 1. + +## The comparison is at a rescaled asset level + +Light's device is that two problems at different rates share a budget set. Cash on hand at +`(a, R₁)` is `y + R₁ a`, and at `(t a, R₂)` with `t = R₁ / R₂` it is `y + R₂ t a = y + R₁ a` — +the same. The saving cap is the same too, being `min assetCap (cash on hand)`. So the two +maximisations run over the SAME interval with the SAME current-consumption map, and differ only +in the continuation. + +That reduces the comparison to single crossing, and single crossing here is derivative-free. +`policy_le_of_cont_increasingDifferences` is the exchange argument of `policy_mono` run in the +other direction: there, one objective at two states; here, two objectives at one state. The +hypothesis is that the continuation has INCREASING DIFFERENCES across the two rates — the +integrated form of Light's `f'(·, R₂) ≥ f'(·, R₁)`, needing no derivative and closed under +pointwise limits. + +The theorem then chains + + `g(a, R₁) ≤ g(t a, R₂) ≤ g(a, R₂)`, + +the first step single crossing and the second `policy_mono`, since `t ≤ 1`. + +## The hypothesis `hid`, and where it is discharged + +The hypothesis `hid` is the property Light propagates through the operator, and propagating it +is his step 5. That step is not derivative-free and cannot be made so: it runs on the envelope +condition `(Tf)'(a, R) = R u'(σ_f(a, R))` as an EQUALITY, and the derivative-free substitute is a +sandwich between two utility increments which is strictly loose. The Clausen–Strub lemma in +`Analysis.DifferentiableSandwich` is exactly the statement that the sandwich closes only in the +derivative limit. + +It is now PROVED, in `IncomeFluctuationRateStep`: `contIncreasingDifferences_withRate`. The +envelope equality it needs is `hasDerivAt_bellman`, Clausen–Strub's lazy agent, and the other +input is the Carroll–Kimball concavity of the consumption function. Theorem 1 with `hid` +discharged is `policy_mono_withRate'`. + +Worth recording precisely: his step 5 applies Lemma 4 to `σ_f(·, R₂)` and needs it CONCAVE. That +is Light's Lemma 5, which is the Carroll and Kimball theorem — the hypothesis `hT` of +`IncomeFluctuationCarrollKimball`. So the residual hypothesis here and the residual hypothesis +there are not two gaps but one: concavity of the consumption function. Closing it closes both. +Toda's necessity result concerns concavity uniformly across return and income laws under its +regularity hypotheses with borrowing permitted. It does not exclude concavity outside HARA +for this capped model; see `Analysis.HARA` for the source. + +The pivotal inequality that step 5 turns on, `mul_marginal_le_of_scale`, is already proved. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +-- Light's rescaling `a ↦ ((1+r₁)/(1+r₂))·a` maps the asset region into itself only when the +-- borrowing limit is at zero (or non-positive), so this file is stated at a zero limit. +variable {assetCap : ℝ} +variable (P Q : IncomeFluctuation Z 0 assetCap) + +/-- **Increasing differences of the continuation across two rates**: the integrated form of +Light's `f'(·, R₂) ≥ f'(·, R₁)`. No derivative, and closed under pointwise limits. -/ +def ContIncreasingDifferences : Prop := + ∀ (z : Z) (x y : ℝ), x ∈ Icc (0 : ℝ) assetCap → y ∈ Icc (0 : ℝ) assetCap → x ≤ y → + P.cont z y - P.cont z x ≤ Q.cont z y - Q.cont z x + +/-- It suffices to have increasing differences slice by slice in the value function, when the +two economies share an income process. -/ +theorem contIncreasingDifferences_of_valueFunction + (hP : P.transitionMatrix = Q.transitionMatrix) + (hv : ∀ (z : Z) (x y : ℝ), x ∈ Icc (0 : ℝ) assetCap → y ∈ Icc (0 : ℝ) assetCap → x ≤ y → + P.toExtended.valueFunction (y, z) - P.toExtended.valueFunction (x, z) + ≤ Q.toExtended.valueFunction (y, z) - Q.toExtended.valueFunction (x, z)) : + ContIncreasingDifferences P Q := by + intro z x y hx hy hxy + simp only [cont, ← Finset.sum_sub_distrib, ← mul_sub] + refine Finset.sum_le_sum fun z' _ => ?_ + rw [show P.transitionMatrix z z' = Q.transitionMatrix z z' from by rw [hP]] + exact mul_le_mul_of_nonneg_left (hv z' x y hx hy hxy) (Q.transitionMatrix_nonneg z z') + +/-- **Single crossing across rates.** Two problems whose budget sets coincide and which differ +only in the continuation are ordered by the continuation: increasing differences make the second +save at least as much. + +This is the exchange argument of `policy_mono`, turned round — there one objective at two +states, here two objectives at one state. -/ +theorem policy_le_of_cont_increasingDifferences + (hu : P.u = Q.u) (hdom : P.dom = Q.dom) (hβ : (P.discount : ℝ) = (Q.discount : ℝ)) + (hid : ContIncreasingDifferences P Q) + {a₁ a₂ : ℝ} {z : Z} (h₁ : a₁ ∈ Icc (0 : ℝ) assetCap) (h₂ : a₂ ∈ Icc (0 : ℝ) assetCap) + (hres : P.resources (a₁, z) = Q.resources (a₂, z)) : + P.policy (a₁, z) ≤ Q.policy (a₂, z) := by + by_contra hcon + rw [not_le] at hcon + set bP : ℝ := P.policy (a₁, z) with hbP + set bQ : ℝ := Q.policy (a₂, z) with hbQ + -- the two budget sets coincide + have hms : P.maxSaving (a₁, z) = Q.maxSaving (a₂, z) := by simp only [maxSaving, hres] + have hcons : ∀ x : ℝ, P.consumption (a₁, z) x = Q.consumption (a₂, z) x := by + intro x; simp only [consumption, hres] + have hbPm : bP ∈ P.toExtended.feasible (a₁, z) := P.policy_mem _ + have hbQm : bQ ∈ Q.toExtended.feasible (a₂, z) := Q.policy_mem _ + rw [P.feasible_eq] at hbPm + rw [Q.feasible_eq] at hbQm + have hbPQ : bP ∈ Q.toExtended.feasible (a₂, z) := by + rw [Q.feasible_eq, ← hms]; exact hbPm + have hbQP : bQ ∈ P.toExtended.feasible (a₁, z) := by + rw [P.feasible_eq, hms]; exact hbQm + -- all four consumptions are positive + have hcP : P.consumption (a₁, z) bP ∈ P.dom := P.consumption_policy_mem_dom h₁ + have hcQ : Q.consumption (a₂, z) bQ ∈ Q.dom := Q.consumption_policy_mem_dom h₂ + have hcPQ : P.consumption (a₁, z) bQ ∈ P.dom := by rw [hcons, hdom]; exact hcQ + have hcQP : Q.consumption (a₂, z) bP ∈ Q.dom := by rw [← hcons, ← hdom]; exact hcP + -- optimality of each against the other's choice + have hoptP := P.objR_le_of_mem h₁ hbQP hcPQ + have hoptQ := Q.objR_le_of_mem h₂ hbPQ hcQP + simp only [objR, hcons, hu, hβ] at hoptP + simp only [objR] at hoptQ + -- increasing differences on the pair `bQ < bP` + have hreg : bP ∈ Icc (0 : ℝ) assetCap := P.policy_mem_region _ + have hreg' : bQ ∈ Icc (0 : ℝ) assetCap := Q.policy_mem_region _ + have hidd := hid z bQ bP hreg' hreg hcon.le + -- both optimality comparisons are therefore equalities + have hβ0 : (0 : ℝ) ≤ Q.discount := Q.discount.coe_nonneg + have hQeq : Q.objR (a₂, z) bP = Q.objR (a₂, z) bQ := by + simp only [objR] + nlinarith [hoptP, hoptQ, mul_le_mul_of_nonneg_left hidd hβ0] + -- so the first economy's choice is optimal in the second, and uniqueness closes it + have hbell : Q.toExtended.objectiveE Q.toExtended.valueFunction (a₂, z) bP + = ((Q.toExtended.bellmanFn Q.toExtended.valueFunction (a₂, z) : ℝ) : EReal) := by + rw [Q.objectiveE_eq_coe h₂ hbPQ hcQP, hQeq, ← Q.objectiveE_eq_coe h₂ (Q.policy_mem _) hcQ] + exact Q.policy_optimal _ + exact absurd (Q.eq_policy_of_optimal h₂ hbPQ hbell) (by rw [← hbQ]; linarith) + +/-! ### The rescaled asset level -/ + +/-- The asset level at the higher rate that gives the same cash on hand. -/ +noncomputable def rateScale : ℝ := (1 + P.interest) / (1 + Q.interest) + +theorem rateScale_pos : 0 < rateScale P Q := + div_pos P.interest_gt_neg_one Q.interest_gt_neg_one + +theorem rateScale_le_one (hr : P.interest ≤ Q.interest) : rateScale P Q ≤ 1 := by + rw [rateScale, div_le_one Q.interest_gt_neg_one]; linarith + +theorem resources_rateScale (hinc : P.income = Q.income) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) + (z : Z) : + P.resources (a, z) = Q.resources (rateScale P Q * a, z) := by + have hscale : (0 : ℝ) ≤ rateScale P Q * a := mul_nonneg (rateScale_pos P Q).le ha.1 + have hmax : max 0 (rateScale P Q * a) = rateScale P Q * a := max_eq_right hscale + simp only [resources, hinc, max_eq_right ha.1, hmax] + simp only [rateScale] + have hne : (1 : ℝ) + Q.interest ≠ 0 := Q.interest_gt_neg_one.ne' + field_simp + +theorem rateScale_mem (hr : P.interest ≤ Q.interest) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + rateScale P Q * a ∈ Icc (0 : ℝ) assetCap := by + have h1 := rateScale_pos P Q + have h2 := rateScale_le_one P Q hr + exact ⟨mul_nonneg h1.le ha.1, by nlinarith [ha.1, ha.2]⟩ + +/-! ### Theorem 1 -/ + +/-- **Light (2018) Theorem 1.** A household facing a higher interest rate saves at least as much, +at every asset level and every income state. + +The hypothesis `hid` is Light's step 5; it is discharged in `IncomeFluctuationRateStep` by +`contIncreasingDifferences_withRate`, which needs the envelope condition together with concavity +of the consumption function. See `policy_mono_withRate'` for the form without it. -/ +theorem policy_mono_interest (hu : P.u = Q.u) (hdom : P.dom = Q.dom) + (hβ : (P.discount : ℝ) = (Q.discount : ℝ)) + (hinc : P.income = Q.income) (hr : P.interest ≤ Q.interest) + (hid : ContIncreasingDifferences P Q) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + P.policy (a, z) ≤ Q.policy (a, z) := by + have hmem := rateScale_mem P Q hr ha + refine le_trans (policy_le_of_cont_increasingDifferences P Q hu hdom hβ hid ha hmem + (resources_rateScale P Q hinc ha z)) ?_ + refine Q.policy_mono hmem ha ?_ + nlinarith [rateScale_le_one P Q hr, ha.1, rateScale_pos P Q] + +/-- **Theorem 1 for a rate family**, in the form `CapitalSupplyMonotone` consumes: the policy of +`P.withRate r` rises with `r`. -/ +theorem policy_mono_withRate {r₁ r₂ : ℝ} (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hid : ContIncreasingDifferences (P.withRate r₁ h₁) (P.withRate r₂ h₂)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := + policy_mono_interest (P.withRate r₁ h₁) (P.withRate r₂ h₂) rfl rfl rfl rfl hr hid ha z + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateStep.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateStep.lean new file mode 100644 index 000000000..e6bc44b9c --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRateStep.lean @@ -0,0 +1,670 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationIterate +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRateMonotone + +/-! +# Light (2018), step 5: increasing differences propagate across rates + +`IncomeFluctuationRateMonotone` reduces Theorem 1 to one hypothesis, `ContIncreasingDifferences`, +and records that propagating it needs two things this development did not have: the envelope +condition as an EQUALITY, and concavity of the consumption function. Both are now proved +(`hasDerivAt_bellman`, `concaveOn_consumptionFnOf_bellman`), so the step is available. + +## The identity the whole thing rests on + +Two rates share a budget set: cash on hand at `(a, R₁)` equals cash on hand at `(t a, R₂)` for +`t = R₁ / R₂`, and the saving cap is the same because it is `min assetCap (cash on hand)`. So the +two maximisations are literally the same maximisation, and `lazyValue_withRate` says so. Hence +`bellmanFn_withRate`, `policyOf_withRate` and `consumptionFnOf_withRate`: the whole solution at +`(a, R₁)` is the solution at `(t a, R₂)`. + +That turns the difference `(T_{R₂} f)(a) - (T_{R₁} f)(a)` into `(T_{R₂} f)(a) - (T_{R₂} f)(t a)`, +a statement about ONE economy, and increasing differences across rates becomes monotonicity of +that difference in `a`. The envelope makes its derivative `R₂ u'(c_w(a)) - R₁ u'(c_v(t a))`, +where `v` and `w` are the two continuations, and two moves sign it: single crossing in the +continuation at the scaled asset level, then `mul_marginal_le_of_scale` — concavity of the +consumption function for one factor, relative risk aversion at most one for the other. + +Doing it in that order matters. Applying the pivotal inequality directly to `c_v` would need +Carroll–Kimball for economy two's choice against economy ONE's continuation, a hybrid object no +induction produces; going through `c_v(t a) ≥ c_w(t a)` first puts the pivotal inequality on +`c_w`, economy two's own, which is exactly what `concaveOn_consumptionFn_of_iterates` delivers. + +## What is left + +`contIncreasingDifferences_withRate` discharges the `hid` of `policy_mono_interest`, so Light's +Theorem 1 holds outright. Its hypotheses are the standing ones of this development: utility +differentiable with decreasing, non-negative marginal utility and relative risk aversion at most +one; consumption positive at every optimum; the asset cap slack along the iteration; and the +Carroll–Kimball concavity of the iterates' consumption functions. `CESConcaveConsumption` shows +these hold together at concrete CES calibrations. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-! ### The two rates solve the same problem at rescaled assets -/ + +variable {r₁ r₂ : ℝ} + +/-- **The objective at `(a, r₁)` is the objective at `(t a, r₂)`**, action for action. -/ +theorem lazyValue_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (v : (ℝ × Z) →ᵇ ℝ) (z : Z) + (x : ℝ) {a : ℝ} (ha : 0 ≤ a) : + (P.withRate r₁ h₁).lazyValue v z x a + = (P.withRate r₂ h₂).lazyValue v z x ((1 + r₁) / (1 + r₂) * a) := by + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have hscale : (0 : ℝ) ≤ (1 + r₁) / (1 + r₂) * a := by + have : (0 : ℝ) < 1 + r₁ := h₁.1 + positivity + have harg : P.income z + (1 + r₂) * ((1 + r₁) / (1 + r₂) * a) + = P.income z + (1 + r₁) * a := by field_simp + simp only [lazyValue, resources, IncomeFluctuation.withRate_income, + IncomeFluctuation.withRate_interest, IncomeFluctuation.withRate_discount, + IncomeFluctuation.withRate_transitionMatrix, IncomeFluctuation.withRate_u, + max_eq_right ha, max_eq_right hscale, harg] + +/-- The feasible sets coincide. -/ +theorem feasible_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (z : Z) {a : ℝ} (ha : 0 ≤ a) : + (P.withRate r₁ h₁).toExtended.feasible (a, z) + = (P.withRate r₂ h₂).toExtended.feasible ((1 + r₁) / (1 + r₂) * a, z) := by + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have hscale : (0 : ℝ) ≤ (1 + r₁) / (1 + r₂) * a := by + have : (0 : ℝ) < 1 + r₁ := h₁.1 + positivity + have hres : (P.withRate r₁ h₁).resources (a, z) + = (P.withRate r₂ h₂).resources ((1 + r₁) / (1 + r₂) * a, z) := by + have harg : P.income z + (1 + r₂) * ((1 + r₁) / (1 + r₂) * a) + = P.income z + (1 + r₁) * a := by field_simp + simp only [resources, IncomeFluctuation.withRate_income, + IncomeFluctuation.withRate_interest, max_eq_right ha, max_eq_right hscale, harg] + rw [(P.withRate r₁ h₁).feasible_eq, (P.withRate r₂ h₂).feasible_eq, + (P.withRate r₁ h₁).maxSaving_eq, (P.withRate r₂ h₂).maxSaving_eq, hres] + +/-- Consumption at a given action coincides. -/ +theorem consumption_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (z : Z) (x : ℝ) {a : ℝ} + (ha : 0 ≤ a) : + (P.withRate r₁ h₁).consumption (a, z) x + = (P.withRate r₂ h₂).consumption ((1 + r₁) / (1 + r₂) * a, z) x := by + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have hscale : (0 : ℝ) ≤ (1 + r₁) / (1 + r₂) * a := by + have : (0 : ℝ) < 1 + r₁ := h₁.1 + positivity + have harg : P.income z + (1 + r₂) * ((1 + r₁) / (1 + r₂) * a) + = P.income z + (1 + r₁) * a := by field_simp + simp only [consumption, resources, IncomeFluctuation.withRate_income, + IncomeFluctuation.withRate_interest, max_eq_right ha, max_eq_right hscale, harg] + +/-- **The whole solution at `(a, r₁)` is the solution at `(t a, r₂)`.** -/ +theorem bellmanFn_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (v : (ℝ × Z) →ᵇ ℝ) (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + (P.withRate r₁ h₁).toExtended.bellmanFn v (a, z) + = (P.withRate r₂ h₂).toExtended.bellmanFn v ((1 + r₁) / (1 + r₂) * a, z) := by + have hR₁ : (0 : ℝ) < 1 + r₁ := h₁.1 + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have ht1 : (1 + r₁) / (1 + r₂) ≤ 1 := by rw [div_le_one hR₂]; linarith + have hscale : (1 + r₁) / (1 + r₂) * a ∈ Icc (0 : ℝ) assetCap := by + refine ⟨mul_nonneg (le_of_lt (div_pos hR₁ hR₂)) ha.1, ?_⟩ + nlinarith [ha.1, ha.2, div_pos hR₁ hR₂] + refine le_antisymm ?_ ?_ + · set b := (P.withRate r₁ h₁).policyOf v (a, z) with hb + have heq := (P.withRate r₁ h₁).lazyValue_eq_dom v z ha + ((P.withRate r₁ h₁).policyOf_mem v (a, z)) + ((P.withRate r₁ h₁).consumption_policyOf_mem_dom v ha) + ((P.withRate r₁ h₁).policyOf_optimal v (a, z)) + have hfe : b ∈ (P.withRate r₂ h₂).toExtended.feasible ((1 + r₁) / (1 + r₂) * a, z) := by + rw [← P.feasible_withRate h₁ h₂ z ha.1] + exact (P.withRate r₁ h₁).policyOf_mem v (a, z) + have hcd : (P.withRate r₂ h₂).consumption ((1 + r₁) / (1 + r₂) * a, z) b + ∈ (P.withRate r₂ h₂).dom := by + rw [← P.consumption_withRate h₁ h₂ z b ha.1] + exact (P.withRate r₁ h₁).consumption_policyOf_mem_dom v ha + have hle := (P.withRate r₂ h₂).lazyValue_le_dom v z hscale hfe hcd + rw [← P.lazyValue_withRate h₁ h₂ v z b ha.1, heq] at hle + exact hle + · set b := (P.withRate r₂ h₂).policyOf v ((1 + r₁) / (1 + r₂) * a, z) with hb + have heq := (P.withRate r₂ h₂).lazyValue_eq_dom v z hscale + ((P.withRate r₂ h₂).policyOf_mem v _) + ((P.withRate r₂ h₂).consumption_policyOf_mem_dom v hscale) + ((P.withRate r₂ h₂).policyOf_optimal v _) + have hfe : b ∈ (P.withRate r₁ h₁).toExtended.feasible (a, z) := by + rw [P.feasible_withRate h₁ h₂ z ha.1] + exact (P.withRate r₂ h₂).policyOf_mem v _ + have hcd : (P.withRate r₁ h₁).consumption (a, z) b ∈ (P.withRate r₁ h₁).dom := by + rw [P.consumption_withRate h₁ h₂ z b ha.1] + exact (P.withRate r₂ h₂).consumption_policyOf_mem_dom v hscale + have hle := (P.withRate r₁ h₁).lazyValue_le_dom v z ha hfe hcd + rw [P.lazyValue_withRate h₁ h₂ v z b ha.1, heq] at hle + exact hle + +/-- **The saving chosen at `(a, r₁)` is the saving chosen at `(t a, r₂)`.** -/ +theorem policyOf_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices (0 : ℝ) assetCap v) (z : Z) {a : ℝ} + (ha : a ∈ Icc (0 : ℝ) assetCap) : + (P.withRate r₁ h₁).policyOf v (a, z) + = (P.withRate r₂ h₂).policyOf v ((1 + r₁) / (1 + r₂) * a, z) := by + have hR₁ : (0 : ℝ) < 1 + r₁ := h₁.1 + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have ht1 : (1 + r₁) / (1 + r₂) ≤ 1 := by rw [div_le_one hR₂]; linarith + have hscale : (1 + r₁) / (1 + r₂) * a ∈ Icc (0 : ℝ) assetCap := by + refine ⟨mul_nonneg (le_of_lt (div_pos hR₁ hR₂)) ha.1, ?_⟩ + nlinarith [ha.1, ha.2, div_pos hR₁ hR₂] + set b := (P.withRate r₁ h₁).policyOf v (a, z) with hb + have hfe : b ∈ (P.withRate r₂ h₂).toExtended.feasible ((1 + r₁) / (1 + r₂) * a, z) := by + rw [← P.feasible_withRate h₁ h₂ z ha.1] + exact (P.withRate r₁ h₁).policyOf_mem v (a, z) + have hcd : (P.withRate r₂ h₂).consumption ((1 + r₁) / (1 + r₂) * a, z) b + ∈ (P.withRate r₂ h₂).dom := by + rw [← P.consumption_withRate h₁ h₂ z b ha.1] + exact (P.withRate r₁ h₁).consumption_policyOf_mem_dom v ha + refine (P.withRate r₂ h₂).optimal_action_unique_of_concaveSlices hv + (s := ((1 + r₁) / (1 + r₂) * a, z)) hscale hfe + ((P.withRate r₂ h₂).policyOf_mem v _) ?_ ((P.withRate r₂ h₂).policyOf_optimal v _) + rw [(P.withRate r₂ h₂).objectiveE_eq_coe_of v hscale hfe hcd, + ← P.bellmanFn_withRate h₁ h₂ hr v z ha] + have hobj : (P.withRate r₂ h₂).objROf v ((1 + r₁) / (1 + r₂) * a, z) b + = (P.withRate r₁ h₁).lazyValue v z b a := by + change (P.withRate r₂ h₂).lazyValue v z b ((1 + r₁) / (1 + r₂) * a) = _ + rw [P.lazyValue_withRate h₁ h₂ v z b ha.1] + rw [hobj] + exact congrArg _ ((P.withRate r₁ h₁).lazyValue_eq_dom v z ha + ((P.withRate r₁ h₁).policyOf_mem v (a, z)) + ((P.withRate r₁ h₁).consumption_policyOf_mem_dom v ha) + ((P.withRate r₁ h₁).policyOf_optimal v (a, z))) + +/-- **Consumption too.** This is the function `mul_marginal_le_of_scale` is applied to. -/ +theorem consumptionFnOf_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {v : (ℝ × Z) →ᵇ ℝ} (hv : ConcaveSlices (0 : ℝ) assetCap v) (z : Z) {a : ℝ} + (ha : a ∈ Icc (0 : ℝ) assetCap) : + (P.withRate r₁ h₁).consumptionFnOf v z a + = (P.withRate r₂ h₂).consumptionFnOf v z ((1 + r₁) / (1 + r₂) * a) := by + simp only [consumptionFnOf] + rw [P.policyOf_withRate h₁ h₂ hr hv z ha, P.consumption_withRate h₁ h₂ z _ ha.1] + +/-! ### The derivative assembly + +With the budget coincidence in hand the whole comparison lives in ONE economy: the difference to +be signed is + + `F a = (T w)(a) - (T v)(t a)`, `t = R₁ / R₂ ≤ 1`. + +`hasDerivAt_bellman` — the Clausen–Strub lazy-agent envelope — differentiates both terms, giving + + `F' a = R₂ u'(c_w(a)) - t R₂ u'(c_v(t a)) = R₂ u'(c_w(a)) - R₁ u'(c_v(t a))`. + +The second term is signed in two moves. Single crossing in the continuation puts +`c_w(t a) ≤ c_v(t a)`, so `u'(c_v(t a)) ≤ u'(c_w(t a))`; and then `mul_marginal_le_of_scale`, +Light's pivotal inequality, carries `R₁ u'(c_w(t a)) ≤ R₂ u'(c_w(a))`. Note which consumption +function the pivotal inequality is applied to: `c_w`, economy TWO's own, whose concavity is what +Carroll–Kimball delivers. Routing the comparison through `c_w` rather than through `c_v` is what +keeps the argument inside one economy's Carroll–Kimball theorem — `c_v` is the hybrid object +(economy two's choice against economy one's continuation) and no concavity of it is needed. + +Note what the envelope is asked for: the state interior and the CHOICE strictly below the saving +cap. Nothing is asked at the borrowing constraint, so the argument survives where it binds. -/ + +/-- **The monotone difference, in one economy, from the pivotal inequality as a hypothesis.** If +`t ≤ 1`, the continuations `v` and `w` have increasing differences, and `t R u'(c_w(t a)) ≤ +R u'(c_w(a))` at every interior `a`, then `a ↦ (T w)(a) - (T v)(t a)` is nondecreasing. -/ +theorem monotoneOn_bellman_sub_scaled_of_piv {assetCap : ℝ} (Q : IncomeFluctuation Z 0 assetCap) + {t : ℝ} (ht0 : 0 < t) (ht1 : t ≤ 1) {v w : (ℝ × Z) →ᵇ ℝ} {z : Z} {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt Q.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) + (hwc : ConcaveSlices (0 : ℝ) assetCap w) (hid : Q.ContOfIncreasingDifferences v w) + (hbv : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + fun x => (Q.toExtended.bellman v) (x, z)) + (hbw : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + fun x => (Q.toExtended.bellman w) (x, z)) + (hintv : ∀ x ∈ Icc (0 : ℝ) assetCap, Q.policyOf v (x, z) < Q.maxSaving (x, z)) + (hintw : ∀ x ∈ Icc (0 : ℝ) assetCap, Q.policyOf w (x, z) < Q.maxSaving (x, z)) + (hposv : ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < Q.consumptionFnOf v z x) + (hposw : ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < Q.consumptionFnOf w z x) + (hpiv : ∀ a ∈ Ioo (0 : ℝ) assetCap, + t * (1 + Q.interest) * du (Q.consumptionFnOf w z (t * a)) + ≤ (1 + Q.interest) * du (Q.consumptionFnOf w z a)) : + MonotoneOn (fun a => (Q.toExtended.bellman w) (a, z) - (Q.toExtended.bellman v) (t * a, z)) + (Icc (0 : ℝ) assetCap) := by + have hR : (0 : ℝ) < 1 + Q.interest := Q.interest_gt_neg_one + -- the derivative, and its sign, at every interior point + have key : ∀ a ∈ Ioo (0 : ℝ) assetCap, ∃ D : ℝ, 0 ≤ D ∧ + HasDerivAt (fun x => (Q.toExtended.bellman w) (x, z) - (Q.toExtended.bellman v) (t * x, z)) + D a := by + intro a ha + have ha0 : (0 : ℝ) < a := ha.1 + have hamem : a ∈ Icc (0 : ℝ) assetCap := ⟨ha.1.le, ha.2.le⟩ + have hta0 : (0 : ℝ) < t * a := mul_pos ht0 ha0 + have htale : t * a ≤ a := by nlinarith + have htamem : t * a ∈ Icc (0 : ℝ) assetCap := ⟨hta0.le, le_trans htale hamem.2⟩ + -- the two envelope derivatives + have hd1 : HasDerivAt (fun x => (Q.toExtended.bellman w) (x, z)) + ((1 + Q.interest) * du (Q.consumptionFnOf w z a)) a := + Q.hasDerivAt_bellman ha0 ha.2 hbw (hintw a hamem) (hposw a hamem) + (hderiv _ (hposw a hamem)) + have hbase : HasDerivAt (fun x => (Q.toExtended.bellman v) (x, z)) + ((1 + Q.interest) * du (Q.consumptionFnOf v z (t * a))) (t * a) := + Q.hasDerivAt_bellman hta0 (lt_of_le_of_lt htale ha.2) hbv (hintv _ htamem) + (hposv _ htamem) (hderiv _ (hposv _ htamem)) + have hlin : HasDerivAt (fun x : ℝ => t * x) t a := by + simpa using (hasDerivAt_id a).const_mul t + have hd2 : HasDerivAt (fun x : ℝ => (Q.toExtended.bellman v) (t * x, z)) + ((1 + Q.interest) * du (Q.consumptionFnOf v z (t * a)) * t) a := hbase.comp a hlin + refine ⟨_, ?_, hd1.sub hd2⟩ + have hpiv := hpiv a ha + -- single crossing in the continuation, at the SCALED asset level + have hcross : Q.consumptionFnOf w z (t * a) ≤ Q.consumptionFnOf v z (t * a) := + Q.consumptionFnOf_le_of_contOf_increasingDifferences hwc hid htamem + have hdu := hanti (mem_Ioi.mpr (hposw _ htamem)) (mem_Ioi.mpr (hposv _ htamem)) hcross + have ht1' : (0 : ℝ) < t * (1 + Q.interest) := by positivity + nlinarith [hpiv, hdu, hR, ht1'] + refine monotoneOn_of_deriv_nonneg (convex_Icc _ _) ?_ ?_ ?_ + · refine ContinuousOn.sub ?_ ?_ + · exact ((Q.toExtended.bellman w).continuous.comp + (continuous_id.prodMk continuous_const)).continuousOn + · exact ((Q.toExtended.bellman v).continuous.comp + ((continuous_const.mul continuous_id).prodMk continuous_const)).continuousOn + · rw [interior_Icc] + intro x hx + obtain ⟨D, _, hD⟩ := key x hx + exact hD.differentiableAt.differentiableWithinAt + · rw [interior_Icc] + intro x hx + obtain ⟨D, hD0, hD⟩ := key x hx + rw [hD.deriv] + exact hD0 + +/-- **The monotone difference, in one economy.** If `t ≤ 1` and the continuations `v` and `w` have +increasing differences, then `a ↦ (T w)(a) - (T v)(t a)` is nondecreasing. The pivotal inequality +is supplied by `mul_marginal_le_of_scale`: relative risk aversion at most one and a concave, +increasing consumption function. -/ +theorem monotoneOn_bellman_sub_scaled {assetCap : ℝ} (Q : IncomeFluctuation Z 0 assetCap) + {t : ℝ} (ht0 : 0 < t) (ht1 : t ≤ 1) {v w : (ℝ × Z) →ᵇ ℝ} {z : Z} {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt Q.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hrra : MonotoneOn (fun y => y * du y) (Ioi (0 : ℝ))) + (hdunn : ∀ y ∈ Ioi (0 : ℝ), 0 ≤ du y) + (hwc : ConcaveSlices (0 : ℝ) assetCap w) (hid : Q.ContOfIncreasingDifferences v w) + (hbv : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + fun x => (Q.toExtended.bellman v) (x, z)) + (hbw : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + fun x => (Q.toExtended.bellman w) (x, z)) + (hintv : ∀ x ∈ Icc (0 : ℝ) assetCap, Q.policyOf v (x, z) < Q.maxSaving (x, z)) + (hintw : ∀ x ∈ Icc (0 : ℝ) assetCap, Q.policyOf w (x, z) < Q.maxSaving (x, z)) + (hposv : ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < Q.consumptionFnOf v z x) + (hposw : ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < Q.consumptionFnOf w z x) + (hcw : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) (Q.consumptionFnOf w z)) + (hmw : MonotoneOn (Q.consumptionFnOf w z) (Icc (0 : ℝ) assetCap)) : + MonotoneOn (fun a => (Q.toExtended.bellman w) (a, z) - (Q.toExtended.bellman v) (t * a, z)) + (Icc (0 : ℝ) assetCap) := by + refine Q.monotoneOn_bellman_sub_scaled_of_piv ht0 ht1 hderiv hanti hwc hid hbv hbw hintv hintw + hposv hposw ?_ + intro a ha + have hR : 0 < 1 + Q.interest := Q.interest_gt_neg_one + simpa only [mul_div_cancel_right₀ _ hR.ne'] using + mul_marginal_le_of_ratio hcw (hposw 0 ⟨le_rfl, Q.assetFloor_le_assetCap⟩) + hmw hrra hdunn (mul_pos ht0 hR) (mul_le_of_le_one_left hR.le ht1) ha + +/-! ### The step, across rates + +Reading the difference back through the budget coincidence turns the previous theorem into the +statement Light's induction propagates: the Bellman images of two continuations with increasing +differences again have increasing differences, at the two rates. -/ + +omit [Fintype Z] [Nonempty Z] [DiscreteTopology Z] in +theorem incDiffSlices_refl (v : (ℝ × Z) →ᵇ ℝ) : IncDiffSlices (0 : ℝ) assetCap v v := + fun _ _ _ _ _ _ => le_rfl + +/-- **Light (2018), step 5, from the pivotal inequality as a hypothesis.** -/ +theorem incDiffSlices_bellman_withRate_of_piv (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) + (hr : r₁ ≤ r₂) {v w : (ℝ × Z) →ᵇ ℝ} {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) + (hvc : ConcaveSlices (0 : ℝ) assetCap v) (hwc : ConcaveSlices (0 : ℝ) assetCap w) + (hposv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + 0 < (P.withRate r₂ h₂).consumptionFnOf v z a) + (hposw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + 0 < (P.withRate r₂ h₂).consumptionFnOf w z a) + (hslackv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r₂ h₂).policyOf v (a, z) < assetCap) + (hslackw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r₂ h₂).policyOf w (a, z) < assetCap) + (hpiv : ∀ z : Z, ∀ a ∈ Ioo (0 : ℝ) assetCap, + (1 + r₁) * du ((P.withRate r₂ h₂).consumptionFnOf w z ((1 + r₁) / (1 + r₂) * a)) + ≤ (1 + r₂) * du ((P.withRate r₂ h₂).consumptionFnOf w z a)) + (hid : IncDiffSlices (0 : ℝ) assetCap v w) : + IncDiffSlices (0 : ℝ) assetCap ((P.withRate r₁ h₁).toExtended.bellman v) + ((P.withRate r₂ h₂).toExtended.bellman w) := by + have hR₁ : (0 : ℝ) < 1 + r₁ := h₁.1 + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + intro z x y hx hy hxy + have hmono := (P.withRate r₂ h₂).monotoneOn_bellman_sub_scaled_of_piv + (t := (1 + r₁) / (1 + r₂)) (div_pos hR₁ hR₂) (by rw [div_le_one hR₂]; linarith) + (v := v) (w := w) (z := z) (du := du) hderiv hanti hwc + ((P.withRate r₂ h₂).contOfIncreasingDifferences_of_slices hid) + ((P.withRate r₂ h₂).concaveSlices_bellman hvc z) + ((P.withRate r₂ h₂).concaveSlices_bellman hwc z) + (fun a ha => (P.withRate r₂ h₂).policyOf_lt_maxSaving_of_pos (hposv a ha z) + (hslackv a ha z)) + (fun a ha => (P.withRate r₂ h₂).policyOf_lt_maxSaving_of_pos (hposw a ha z) + (hslackw a ha z)) + (fun a ha => hposv a ha z) (fun a ha => hposw a ha z) + (fun a ha => by + rw [IncomeFluctuation.withRate_interest, + show (1 + r₁) / (1 + r₂) * (1 + r₂) = 1 + r₁ from div_mul_cancel₀ _ hR₂.ne'] + exact hpiv z a ha) + have hkey := hmono hx hy hxy + have hcoin : ∀ a ∈ Icc (0 : ℝ) assetCap, + ((P.withRate r₁ h₁).toExtended.bellman v) (a, z) + = ((P.withRate r₂ h₂).toExtended.bellman v) ((1 + r₁) / (1 + r₂) * a, z) := by + intro a ha + rw [(P.withRate r₁ h₁).toExtended.bellman_apply, + (P.withRate r₂ h₂).toExtended.bellman_apply] + exact P.bellmanFn_withRate h₁ h₂ hr v z ha + rw [hcoin x hx, hcoin y hy] + linarith + +/-- **Light (2018) Theorem 1 from the pivotal inequality, the induction started anywhere.** -/ +theorem contIncreasingDifferences_withRate_of_piv (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) + (hr : r₁ ≤ r₂) {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (v₀ : (ℝ × Z) →ᵇ ℝ) + (hv₀ : ConcaveSlices (0 : ℝ) assetCap v₀) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf (((P.withRate r₁ h₁).toExtended.bellman)^[n] v₀) z a) + (hposw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] v₀) (a, z) < assetCap) + (hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) (a, z) < assetCap) + (hpiv : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Ioo (0 : ℝ) assetCap, + (1 + r₁) * du ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) z ((1 + r₁) / (1 + r₂) * a)) + ≤ (1 + r₂) * du ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) z a)) : + ContIncreasingDifferences (P.withRate r₁ h₁) (P.withRate r₂ h₂) := by + have hvs : ∀ n : ℕ, ConcaveSlices (0 : ℝ) assetCap + (((P.withRate r₁ h₁).toExtended.bellman)^[n] v₀) := + (P.withRate r₁ h₁).concaveSlices_iterate_bellman hv₀ + have hws : ∀ n : ℕ, ConcaveSlices (0 : ℝ) assetCap + (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) := + (P.withRate r₂ h₂).concaveSlices_iterate_bellman hv₀ + have hstep : ∀ n : ℕ, IncDiffSlices (0 : ℝ) assetCap + (((P.withRate r₁ h₁).toExtended.bellman)^[n] v₀) + (((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) := by + intro n + induction n with + | zero => exact incDiffSlices_refl v₀ + | succ k ih => + rw [Function.iterate_succ_apply', Function.iterate_succ_apply'] + exact P.incDiffSlices_bellman_withRate_of_piv h₁ h₂ hr hderiv hanti (hvs k) (hws k) + (hposv k) (hposw k) (hslackv k) (hslackw k) (hpiv k) ih + refine contIncreasingDifferences_of_valueFunction _ _ rfl ?_ + intro z x y hx hy hxy + have hlim₁ : Tendsto (fun n => ((P.withRate r₁ h₁).toExtended.bellman)^[n] v₀) atTop + (𝓝 (P.withRate r₁ h₁).toExtended.valueFunction) := + (P.withRate r₁ h₁).toExtended.tendsto_iterate_valueFunction v₀ + have hlim₂ : Tendsto (fun n => ((P.withRate r₂ h₂).toExtended.bellman)^[n] v₀) atTop + (𝓝 (P.withRate r₂ h₂).toExtended.valueFunction) := + (P.withRate r₂ h₂).toExtended.tendsto_iterate_valueFunction v₀ + have hpt : ∀ (F : ℕ → (ℝ × Z) →ᵇ ℝ) (G : (ℝ × Z) →ᵇ ℝ), Tendsto F atTop (𝓝 G) → + ∀ p : ℝ × Z, Tendsto (fun n => F n p) atTop (𝓝 (G p)) := fun F G hF p => + (BoundedContinuousFunction.tendsto_iff_tendstoUniformly.mp hF).tendsto_at p + have hev₁ := hpt _ _ hlim₁ + have hev₂ := hpt _ _ hlim₂ + exact le_of_tendsto_of_tendsto ((hev₁ (y, z)).sub (hev₁ (x, z))) + ((hev₂ (y, z)).sub (hev₂ (x, z))) (Eventually.of_forall fun n => hstep n z x y hx hy hxy) + +/-- **Light (2018), step 5.** Increasing differences survive the Bellman operator. -/ +theorem incDiffSlices_bellman_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {v w : (ℝ × Z) →ᵇ ℝ} {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hrra : MonotoneOn (fun y => y * du y) (Ioi (0 : ℝ))) + (hdunn : ∀ y ∈ Ioi (0 : ℝ), 0 ≤ du y) + (hvc : ConcaveSlices (0 : ℝ) assetCap v) (hwc : ConcaveSlices (0 : ℝ) assetCap w) + (hposv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + 0 < (P.withRate r₂ h₂).consumptionFnOf v z a) + (hposw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + 0 < (P.withRate r₂ h₂).consumptionFnOf w z a) + (hslackv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r₂ h₂).policyOf v (a, z) < assetCap) + (hslackw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r₂ h₂).policyOf w (a, z) < assetCap) + (hcw : ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) ((P.withRate r₂ h₂).consumptionFnOf w z)) + (hid : IncDiffSlices (0 : ℝ) assetCap v w) : + IncDiffSlices (0 : ℝ) assetCap ((P.withRate r₁ h₁).toExtended.bellman v) + ((P.withRate r₂ h₂).toExtended.bellman w) := by + refine P.incDiffSlices_bellman_withRate_of_piv h₁ h₂ hr hderiv hanti hvc hwc + hposv hposw hslackv hslackw ?_ hid + intro z a ha + exact mul_marginal_le_of_ratio (hcw z) + (hposw 0 ⟨le_rfl, P.assetFloor_le_assetCap⟩ z) + (fun x hx y hy hxy => (P.withRate r₂ h₂).consumptionFnOf_mono hwc hx hy hxy) + hrra hdunn h₁.1 (add_le_add le_rfl hr) ha + +/-! ### The induction, and the limit + +`IncDiffSlices` mentions no economy and is closed under pointwise limits, so the step iterates +from the common start `0` and passes to the two value functions. That discharges the hypothesis +`hid` of `policy_mono_interest`, and with it Light's Theorem 1. -/ + +/-- **Light (2018) Theorem 1, with step 5 discharged.** The value functions at two rates have +increasing differences in assets, so the continuations do. -/ +theorem contIncreasingDifferences_withRate (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hrra : MonotoneOn (fun y => y * du y) (Ioi (0 : ℝ))) + (hdunn : ∀ y ∈ Ioi (0 : ℝ), 0 ≤ du y) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hposw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z)) : + ContIncreasingDifferences (P.withRate r₁ h₁) (P.withRate r₂ h₂) := by + refine P.contIncreasingDifferences_withRate_of_piv h₁ h₂ hr hderiv hanti 0 + concaveSlices_zero hposv hposw hslackv hslackw ?_ + intro n z a ha + exact mul_marginal_le_of_ratio (hcons n z) + (hposw n 0 ⟨le_rfl, P.assetFloor_le_assetCap⟩ z) + (fun x hx y hy hxy => (P.withRate r₂ h₂).consumptionFnOf_mono + ((P.withRate r₂ h₂).concaveSlices_iterate_bellman concaveSlices_zero n) hx hy hxy) + hrra hdunn h₁.1 (add_le_add le_rfl hr) ha + +/-- **Light (2018) Theorem 1, unconditional on the increasing-differences hypothesis.** A +household facing a higher interest rate saves at least as much, at every asset level and every +income state. -/ +theorem policy_mono_withRate' (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hrra : MonotoneOn (fun y => y * du y) (Ioi (0 : ℝ))) + (hdunn : ∀ y ∈ Ioi (0 : ℝ), 0 ≤ du y) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hposw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := + P.policy_mono_withRate h₁ h₂ hr + (P.contIncreasingDifferences_withRate h₁ h₂ hr hderiv hanti hrra hdunn hposv hposw hslackv + hslackw hcons) ha z + +/-! ### CRRA, with the marginal-utility hypotheses discharged + +For `u = crraUtility γ` marginal utility is `c ^ (-γ)`, which is decreasing and non-negative for +every `γ > 0`, and `c · u'(c) = c ^ (1 - γ)` is nondecreasing exactly when `γ ≤ 1` — relative +risk aversion at most one, the hypothesis Light needs and the one this development has carried +since `RelativeRiskAversion`. -/ + +theorem policy_mono_withRate_crra {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ ≤ 1) (hu : P.u = crraUtility γ) + (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hposw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := by + have hpow : ∀ c : ℝ, 0 < c → c * c ^ (-γ) = c ^ (1 - γ) := by + intro c hc + rw [show (1 : ℝ) - γ = 1 + -γ by ring, Real.rpow_add hc, Real.rpow_one] + refine P.policy_mono_withRate' h₁ h₂ hr (du := fun c => c ^ (-γ)) ?_ ?_ ?_ ?_ + hposv hposw hslackv hslackw hcons ha z + · intro c hc + rw [hu] + exact hasDerivAt_crraUtility γ hc + · intro x hx y hy hxy + exact Real.rpow_le_rpow_of_nonpos hx hxy (by linarith) + · intro x hx y hy hxy + simp only [] + rw [hpow x hx, hpow y hy] + exact Real.rpow_le_rpow hx.le hxy (by linarith) + · intro y hy + exact Real.rpow_nonneg hy.le _ + +/-! ### Beyond relative risk aversion one + +The relative-risk-aversion specializations use `γ ≤ 1` in one place: the pivotal inequality +`t R u'(c_w(t a)) ≤ R u'(c_w(a))` applied to economy two's consumption function against the +CURRENT continuation `w`. Two changes make that inequality a hypothesis one can hope to meet +when `γ > 1`. + +First, run the induction from economy two's own value function rather than from `0`. The +economy-two iterates are then constant — `T₂ V₂ = V₂` — so the pivotal inequality is asked of +economy two's TRUE consumption function alone, not of every iterate (whose consumption functions +start at "eat everything", where no such inequality holds). + +Second, `mul_rpow_neg_le_of_scale` gives the inequality for `u' = c^{-γ}`, any `γ`, wherever +`(1 - c₂(0)/c₂(t a))(ρ - 1) ≤ ρ^{1/γ} - 1` with `ρ = R₂/R₁` — a condition on the range of the +consumption function, roughly `c₂(a) ≤ (γ/(γ-1)) c₂(0)` for rates close together. It is a +restriction on wealth, not on preferences: for a small enough asset cap, or on an invariant +interval, Light's Theorem 1 holds for every `γ`. -/ + +/-- **Light (2018) Theorem 1 from the pivotal inequality on economy two's OWN consumption +function.** The induction is started at economy two's value function, which its Bellman +operator fixes, so the only consumption function the inequality is asked of is the true one at +the higher rate. -/ +theorem policy_mono_withRate_of_piv (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) + (hposv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < + (P.withRate r₂ h₂).consumptionFnOf (((P.withRate r₁ h₁).toExtended.bellman)^[n] + (P.withRate r₂ h₂).toExtended.valueFunction) z a) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (P.withRate r₂ h₂).toExtended.valueFunction) + (a, z) < assetCap) + (hposw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, 0 < (P.withRate r₂ h₂).consumptionFn z a) + (hslackw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policy (a, z) < assetCap) + (hpiv : ∀ z : Z, ∀ a ∈ Ioo (0 : ℝ) assetCap, + (1 + r₁) * du ((P.withRate r₂ h₂).consumptionFn z ((1 + r₁) / (1 + r₂) * a)) + ≤ (1 + r₂) * du ((P.withRate r₂ h₂).consumptionFn z a)) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := by + have hfix : ∀ n : ℕ, ((P.withRate r₂ h₂).toExtended.bellman)^[n] + (P.withRate r₂ h₂).toExtended.valueFunction = (P.withRate r₂ h₂).toExtended.valueFunction := + fun n => Function.iterate_fixed (P.withRate r₂ h₂).toExtended.bellman_valueFunction n + refine P.policy_mono_withRate h₁ h₂ hr ?_ ha z + refine P.contIncreasingDifferences_withRate_of_piv h₁ h₂ hr hderiv hanti + (P.withRate r₂ h₂).toExtended.valueFunction (P.withRate r₂ h₂).concaveSlices_valueFunction + hposv (fun n a ha' z' => by rw [hfix n]; exact hposw a ha' z') hslackv + (fun n a ha' z' => by rw [hfix n]; exact hslackw a ha' z') + (fun n z' a ha' => by rw [hfix n]; exact hpiv z' a ha') + +/-- **Light (2018) Theorem 1 for CRRA with ANY `γ > 0`, on a range condition.** Saving rises with +the rate at every state provided economy two's consumption function satisfies +`(1 - c₂(0,z)/c₂(t a, z)) (ρ - 1) ≤ ρ^{1/γ} - 1` for `ρ = R₂/R₁`, `t = 1/ρ`, at every interior +`a` — for rates close together, consumption at most about `γ/(γ-1)` times its value at zero +assets. Concavity of that consumption function is Carroll--Kimball at the fixed point +(`concaveOn_consumptionFn_of_crra`). -/ +theorem policy_mono_withRate_crra_of_range {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hβ : 0 < (P.discount : ℝ)) (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hpc₂ : (P.withRate r₂ h₂).PositiveConsumptionAll) + (hslackIt : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (hslackv : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (P.withRate r₂ h₂).toExtended.valueFunction) + (a, z) < assetCap) + (hslackw : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, (P.withRate r₂ h₂).policy (a, z) < assetCap) + (hrange : ∀ z : Z, ∀ a ∈ Ioo (0 : ℝ) assetCap, + (1 - (P.withRate r₂ h₂).consumptionFn z 0 + / (P.withRate r₂ h₂).consumptionFn z ((1 + r₁) / (1 + r₂) * a)) + * ((1 + r₂) / (1 + r₁) - 1) ≤ ((1 + r₂) / (1 + r₁)) ^ (1 / γ) - 1) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := by + have hR₁ : (0 : ℝ) < 1 + r₁ := h₁.1 + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have hcap : (0 : ℝ) ≤ assetCap := P.assetCap_nonneg + have hzero : (0 : ℝ) ∈ Icc (0 : ℝ) assetCap := ⟨le_rfl, hcap⟩ + have hconc : ∀ n : ℕ, ConcaveSlices (0 : ℝ) assetCap + (((P.withRate r₁ h₁).toExtended.bellman)^[n] + (P.withRate r₂ h₂).toExtended.valueFunction) := by + intro n + induction n with + | zero => exact (P.withRate r₂ h₂).concaveSlices_valueFunction + | succ k ih => + rw [Function.iterate_succ_apply'] + exact (P.withRate r₁ h₁).concaveSlices_bellman ih + refine P.policy_mono_withRate_of_piv h₁ h₂ hr (du := fun c => c ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_crraUtility γ hc) + (fun x hx y _ hxy => Real.rpow_le_rpow_of_nonpos hx hxy (by linarith)) + (fun n a' ha' z' => hpc₂ _ (hconc n) z' a' ha') hslackv + (fun a' ha' z' => hpc₂ _ (P.withRate r₂ h₂).concaveSlices_valueFunction z' a' ha') + hslackw ?_ ha z + intro z' a' ha' + have hcc : ConcaveOn ℝ (Icc (0 : ℝ) assetCap) ((P.withRate r₂ h₂).consumptionFn z') := + (P.withRate r₂ h₂).concaveOn_consumptionFn_of_crra hpc₂ hγ0 hβ hu hslackIt z' + have hmono : MonotoneOn ((P.withRate r₂ h₂).consumptionFn z') (Icc (0 : ℝ) assetCap) := + fun x hx y hy hxy => (P.withRate r₂ h₂).consumptionFn_mono hx hy hxy + have hc0 : 0 < (P.withRate r₂ h₂).consumptionFn z' 0 := + hpc₂ _ (P.withRate r₂ h₂).concaveSlices_valueFunction z' 0 hzero + have hta : (1 + r₁) / (1 + r₂) * a' ∈ Icc (0 : ℝ) assetCap := by + have ht1 : (1 + r₁) / (1 + r₂) ≤ 1 := by rw [div_le_one hR₂]; linarith + have ht0 : 0 < (1 + r₁) / (1 + r₂) := div_pos hR₁ hR₂ + constructor + · exact mul_nonneg ht0.le ha'.1.le + · nlinarith [ha'.2] + have e1 : (1 + r₁) * (a' / (1 + r₂)) = (1 + r₁) / (1 + r₂) * a' := by field_simp + have e2 : (1 + r₂) / (1 + r₁) * (1 + r₁) * (a' / (1 + r₂)) = a' := by field_simp + have e3 : (1 + r₂) / (1 + r₁) * (1 + r₁) = 1 + r₂ := div_mul_cancel₀ _ hR₁.ne' + have hpiv := mul_rpow_neg_le_of_scale hγ0 hcc hzero hc0 hmono (α₁ := 1 + r₁) + (ρ := (1 + r₂) / (1 + r₁)) (x := a' / (1 + r₂)) hR₁ (by rw [le_div_iff₀ hR₁]; linarith) + (div_pos ha'.1 hR₂) (by rw [e1]; exact hta) (by rw [e2]; exact ⟨ha'.1.le, ha'.2.le⟩) + (by rw [e1]; exact hrange z' a' ha') + rw [e1, e2, e3] at hpiv + exact hpiv + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRegion.lean b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRegion.lean new file mode 100644 index 000000000..af5f1aabd --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/IncomeFluctuationRegion.lean @@ -0,0 +1,288 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationCrossRate + +/-! +# The contraction, run on the compact region alone + +The value function is a bounded continuous function on all of `ℝ × Z`, but the model only +ever lives on `[0, assetCap] × Z`, which is forward invariant: every feasible choice lands +back in it (`feasible_subset_region`). So the Bellman operator evaluated at a state of the +region never consults the continuation value anywhere else. + +That has a consequence worth exploiting. The whole contraction estimate can be run with +suprema taken over the REGION rather than globally: + + sup_region |V_P - V_Q| ≤ (1 - β)⁻¹ * sup_region |bellman_P V_Q - V_Q| + +so comparative statics in the interest rate never needs a global sup-norm estimate. Since +the agent distribution also lives on the region, this is all the equilibrium argument +requires. It needs no second programme on the subtype, and no transport: it is a direct +estimate on the objects already built. + +The mechanism is that the reward cancels. Two continuation values fed to the same programme +give objectives differing only in the discounted expectation, and the expectation samples +the next asset level, which feasibility confines to the region. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The region the model lives on. -/ +def region (_P : IncomeFluctuation Z assetFloor assetCap) : Set (ℝ × Z) := + {s | s.1 ∈ Icc assetFloor assetCap} + +theorem isCompact_region : IsCompact P.region := by + have himg : P.region = (Icc assetFloor assetCap) ×ˢ (univ : Set Z) := by + ext s; simp [region, Set.mem_prod] + rw [himg] + exact isCompact_Icc.prod isCompact_univ + +theorem region_nonempty : P.region.Nonempty := + ⟨(assetFloor, Classical.ofNonempty), by simp [region, P.assetFloor_le_assetCap]⟩ + +/-- **The expectation only samples the region.** This is forward invariance in the form the +estimate needs. -/ +theorem abs_expect_sub_le {v w : (ℝ × Z) →ᵇ ℝ} {c : ℝ} + (hvw : ∀ t : ℝ × Z, t ∈ P.region → |v t - w t| ≤ c) {s : ℝ × Z} {a : ℝ} + (ha : a ∈ P.toExtended.feasible s) : + |P.toExtended.expect v (s, a) - P.toExtended.expect w (s, a)| ≤ c := by + have htr : ∀ z' : Z, P.toExtended.transition z' (s, a) = (a, z') := fun _ => rfl + have hreg : ∀ z' : Z, P.toExtended.transition z' (s, a) ∈ P.region := fun z' => by + rw [htr z']; exact P.feasible_subset_region ha + simp only [ExtendedStochasticProgram.expect, ← Finset.sum_sub_distrib, ← mul_sub] + calc |∑ z', P.toExtended.prob z' (s, a) + * (v (P.toExtended.transition z' (s, a)) - w (P.toExtended.transition z' (s, a)))| + ≤ ∑ z', |P.toExtended.prob z' (s, a) + * (v (P.toExtended.transition z' (s, a)) - w (P.toExtended.transition z' (s, a)))| := + Finset.abs_sum_le_sum_abs _ _ + _ ≤ ∑ z' : Z, P.toExtended.prob z' (s, a) * c := by + refine Finset.sum_le_sum fun z' _ => ?_ + rw [abs_mul, abs_of_nonneg (P.toExtended.prob_nonneg z' _)] + exact mul_le_mul_of_nonneg_left (hvw _ (hreg z')) (P.toExtended.prob_nonneg z' _) + _ = c := by rw [← Finset.sum_mul, P.toExtended.prob_sum, one_mul] + +/-- **The operator contracts, measured on the region alone.** The reward cancels, so the two +objectives differ only in the discounted expectation, and the expectation samples the next +asset level, which feasibility confines to the region. -/ +theorem abs_bellmanFn_sub_le_region {v w : (ℝ × Z) →ᵇ ℝ} {c : ℝ} + (hvw : ∀ t : ℝ × Z, t ∈ P.region → |v t - w t| ≤ c) (s : ℝ × Z) : + |P.toExtended.bellmanFn v s - P.toExtended.bellmanFn w s| ≤ P.discount * c := by + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have key : ∀ v w : (ℝ × Z) →ᵇ ℝ, (∀ t : ℝ × Z, t ∈ P.region → |v t - w t| ≤ c) → + P.toExtended.bellmanFn v s ≤ P.toExtended.bellmanFn w s + P.discount * c := by + intro v w hvw + refine P.toExtended.bellmanFn_le v fun a ha => ?_ + have hstep : P.toExtended.objectiveE v s a + ≤ P.toExtended.objectiveE w s a + ((P.discount * c : ℝ) : EReal) := by + simp only [ExtendedStochasticProgram.objectiveE] + rw [add_assoc, ← EReal.coe_add] + refine add_le_add (le_refl _) ?_ + rw [EReal.coe_le_coe_iff] + have hexp := P.abs_expect_sub_le hvw ha + rw [abs_le] at hexp + -- `toExtended.discount` and `discount` are defeq but distinct atoms to `linarith` + rw [show (P.toExtended.discount : ℝ) = (P.discount : ℝ) from rfl] + nlinarith [hexp.2, hβ] + refine le_trans hstep ?_ + rw [EReal.coe_add] + exact add_le_add (P.toExtended.le_bellmanFn w ha) (le_refl _) + have hc : 0 ≤ c := + le_trans (abs_nonneg _) + (hvw (assetFloor, Classical.ofNonempty) (by simp [region, P.assetFloor_le_assetCap])) + have k1 := key v w hvw + have k2 := key w v fun t ht => by rw [abs_sub_comm]; exact hvw t ht + rw [abs_le] + constructor <;> linarith + +/-- **The bridge.** Two value functions differ on the region by at most the operator gap on +the region, amplified by `(1 - β)⁻¹`. Every norm here is taken over the COMPACT REGION, so +comparative statics in the interest rate never needs a global sup-norm estimate. + +Since the agent distribution also lives on the region, this is all the equilibrium argument +requires. Note it needs no second programme on the compact state space and no transport -- +it is a direct estimate on the objects already built. -/ +theorem abs_valueFunction_sub_le_region {P Q : IncomeFluctuation Z assetFloor assetCap} {D : ℝ} + (hD : ∀ t : ℝ × Z, t ∈ P.region → + |P.toExtended.bellmanFn Q.toExtended.valueFunction t + - Q.toExtended.valueFunction t| ≤ D) + {s : ℝ × Z} (hs : s ∈ P.region) : + |P.toExtended.valueFunction s - Q.toExtended.valueFunction s| + ≤ D / (1 - P.discount) := by + have hβ1 : (P.discount : ℝ) < 1 := by exact_mod_cast P.discount_lt_one + have hβ0 : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + set f : ℝ × Z → ℝ := + fun t => |P.toExtended.valueFunction t - Q.toExtended.valueFunction t| with hf + have hcont : ContinuousOn f P.region := + ((P.toExtended.valueFunction.continuous.sub + Q.toExtended.valueFunction.continuous).abs).continuousOn + obtain ⟨t, htmem, htmax⟩ := + P.isCompact_region.exists_isMaxOn P.region_nonempty hcont + have hCle : ∀ u : ℝ × Z, u ∈ P.region → f u ≤ f t := fun u hu => htmax hu + -- the contraction, measured on the region + have hstep : f t ≤ P.discount * f t + D := by + have h1 : P.toExtended.valueFunction t + = P.toExtended.bellmanFn P.toExtended.valueFunction t := by + rw [← P.toExtended.bellman_apply, P.toExtended.bellman_valueFunction] + have h2 := P.abs_bellmanFn_sub_le_region + (v := P.toExtended.valueFunction) (w := Q.toExtended.valueFunction) (c := f t) + (fun u hu => hCle u hu) t + have h3 := hD t htmem + have htri : |P.toExtended.bellmanFn P.toExtended.valueFunction t + - Q.toExtended.valueFunction t| + ≤ |P.toExtended.bellmanFn P.toExtended.valueFunction t + - P.toExtended.bellmanFn Q.toExtended.valueFunction t| + + |P.toExtended.bellmanFn Q.toExtended.valueFunction t + - Q.toExtended.valueFunction t| := abs_sub_le _ _ _ + have h4 : f t = |P.toExtended.bellmanFn P.toExtended.valueFunction t + - Q.toExtended.valueFunction t| := by rw [hf]; simp only [← h1] + calc f t = |P.toExtended.bellmanFn P.toExtended.valueFunction t + - Q.toExtended.valueFunction t| := h4 + _ ≤ |P.toExtended.bellmanFn P.toExtended.valueFunction t + - P.toExtended.bellmanFn Q.toExtended.valueFunction t| + + |P.toExtended.bellmanFn Q.toExtended.valueFunction t + - Q.toExtended.valueFunction t| := htri + _ ≤ P.discount * f t + D := add_le_add h2 h3 + have hC : f t ≤ D / (1 - P.discount) := by + rw [le_div_iff₀ (by linarith)] + nlinarith [hstep] + exact le_trans (hCle s hs) hC + +/-! ### The cutoff: actions worth keeping consume a bounded amount + +The pointwise comparison the sup-norm estimate needs is false at actions that consume almost +nothing, because `u` falls to `-∞` there and two nearby rates can give utilities an arbitrary +distance apart. Such actions are dominated: saving nothing is always feasible and is worth at +least `loBound v = u minIncome - β‖v‖`, which the framework already places below the +supremum (`le_maxE`). + +So it is enough to compare actions worth at least `loBound v`, and this section shows those +actions consume at least a fixed `δ > 0`. The `δ` depends on `u` and on `‖v‖` only — not on +the state and not on the interest rate, which is what the parametric argument needs. -/ + +theorem abs_expect_le (v : (ℝ × Z) →ᵇ ℝ) (p : (ℝ × Z) × ℝ) : + |P.toExtended.expect v p| ≤ ‖v‖ := by + exact P.toExtended.abs_expect_le v p + +/-- **Actions worth keeping consume a bounded amount**, for a cutoff `δ` given in advance. + +Separating the choice of `δ` from its use is what makes the bound uniform across interest +rates: the hypothesis on `δ` mentions only `u`, `loBound v` and the discount factor, none of +which move with the rate, so a single `δ` serves every rate. -/ +theorem le_consumption_of_cutoff (v : (ℝ × Z) →ᵇ ℝ) {δ : ℝ} + (hspec : ∀ c ∈ P.dom, c < δ → P.u c < P.toExtended.loBound v - P.discount * ‖v‖) + {s : ℝ × Z} (hs : s ∈ P.region) {a : ℝ} (ha : a ∈ P.toExtended.feasible s) + (hL : ((P.toExtended.loBound v : ℝ) : EReal) ≤ P.toExtended.objectiveE v s a) : + δ ≤ P.consumption s a := by + set R : ℝ := P.toExtended.loBound v - P.discount * ‖v‖ with hR + by_contra hlt + rw [not_le] at hlt + -- a reward of `⊥` would make the whole objective `⊥`, which is below the cutoff + have hne : P.toExtended.reward (s, a) ≠ ⊥ := by + intro hbot + rw [ExtendedStochasticProgram.objectiveE, hbot, EReal.bot_add] at hL + exact EReal.coe_ne_bot _ (le_bot_iff.mp hL) + have hc : P.consumption s a ∈ P.dom := P.consumption_mem_dom_of_ne_bot hs ha hne + -- so the reward is real, and the objective is a real inequality + have hrw := P.reward_eq_coe_dom hs ha hc + rw [ExtendedStochasticProgram.objectiveE, hrw, ← EReal.coe_add, EReal.coe_le_coe_iff] at hL + have hexp := P.abs_expect_le v (s, a) + rw [abs_le] at hexp + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hdisc : (P.toExtended.discount : ℝ) = (P.discount : ℝ) := rfl + rw [hdisc] at hL + have hge : R ≤ P.u (P.consumption s a) := by + rw [hR] + nlinarith [hexp.2, hL] + exact absurd (hspec _ hc hlt) (not_lt.mpr hge) + +/-- A cutoff exists, and its defining property mentions only data that does not move with +the interest rate. -/ +theorem exists_cutoff (hd : P.Unbounded) (v : (ℝ × Z) →ᵇ ℝ) : + ∃ δ > 0, ∀ c ∈ P.dom, c < δ → + P.u c < P.toExtended.loBound v - P.discount * ‖v‖ := by + set R : ℝ := P.toExtended.loBound v - P.discount * ‖v‖ with hR + have hev : ∀ᶠ c in 𝓝[>] (0 : ℝ), P.u c < R := P.tendsto_atBot_u hd (eventually_lt_atBot R) + obtain ⟨δ, hδ, hsub⟩ := (nhdsGT_basis (0 : ℝ)).eventually_iff.mp hev + refine ⟨δ, hδ, fun c hc hcδ => hsub ⟨?_, hcδ⟩⟩ + rw [hd] at hc + exact hc + +/-- **Actions worth keeping consume a bounded amount.** -/ +theorem exists_cutoff_consumption (hd : P.Unbounded) (v : (ℝ × Z) →ᵇ ℝ) : + ∃ δ > 0, ∀ s : ℝ × Z, s ∈ P.region → ∀ a ∈ P.toExtended.feasible s, + ((P.toExtended.loBound v : ℝ) : EReal) ≤ P.toExtended.objectiveE v s a → + δ ≤ P.consumption s a := by + obtain ⟨δ, hδ, hspec⟩ := P.exists_cutoff hd v + exact ⟨δ, hδ, fun s hs a ha hL => P.le_consumption_of_cutoff v hspec hs ha hL⟩ + +/-! ### Uniform continuity + +The two moduli the sup-norm estimate consumes. Both are uniform in the state, which is what +matters; neither is uniform in the interest rate, and neither needs to be, since the rate +enters only through the ARGUMENTS fed to `u` and to the continuation value, and those gaps +are already controlled by the cross-rate lemmas. + +Note `Z` carries only a discrete topology, with no metric, so `ℝ × Z` is not a metric space +and uniform continuity of the continuation value cannot be taken there directly. It is taken +in the asset coordinate for each income state separately and the finitely many moduli +combined, which is legitimate exactly because `Z` is a `Fintype`. -/ + +/-- **Uniform continuity of utility**, away from zero consumption. The interval is compact and +sits inside `Ioi 0`, where `u` is assumed continuous; the cutoff is what puts consumption +there. -/ +theorem exists_modulus_u {lo hi ε : ℝ} (hlo : 0 < lo) (hε : 0 < ε) : + ∃ η > 0, ∀ c₁ ∈ Icc lo hi, ∀ c₂ ∈ Icc lo hi, |c₁ - c₂| < η → |P.u c₁ - P.u c₂| < ε := by + have hsub : Icc lo hi ⊆ Ioi 0 := fun c hc => lt_of_lt_of_le hlo hc.1 + have hcont : ContinuousOn P.u (Icc lo hi) := P.continuousOn_u.mono hsub + have huc : UniformContinuousOn P.u (Icc lo hi) := + isCompact_Icc.uniformContinuousOn_of_continuous hcont + obtain ⟨η, hη, hspec⟩ := Metric.uniformContinuousOn_iff.mp huc ε hε + refine ⟨η, hη, fun c₁ h₁ c₂ h₂ hd => ?_⟩ + have := hspec c₁ h₁ c₂ h₂ (by rwa [Real.dist_eq]) + rwa [Real.dist_eq] at this + +omit [Fintype Z] [DiscreteTopology Z] in +/-- **Uniform continuity of the continuation value** in the asset coordinate, uniformly over +income states. The finiteness of `Z` is used in the PROOF, to combine the per-state moduli, +though it does not appear in the statement. -/ +theorem exists_modulus_v [Finite Z] (v : (ℝ × Z) →ᵇ ℝ) {ε : ℝ} (hε : 0 < ε) : + ∃ η > 0, ∀ z' : Z, ∀ a₁ ∈ Icc assetFloor assetCap, ∀ a₂ ∈ Icc assetFloor assetCap, + |a₁ - a₂| < η → |v (a₁, z') - v (a₂, z')| < ε := by + let _ := Fintype.ofFinite Z + -- one modulus per income state + have hslice : ∀ z' : Z, ∃ η > 0, ∀ a₁ ∈ Icc assetFloor assetCap, ∀ a₂ ∈ Icc assetFloor assetCap, + |a₁ - a₂| < η → |v (a₁, z') - v (a₂, z')| < ε := by + intro z' + have hcont : ContinuousOn (fun a : ℝ => v (a, z')) (Icc assetFloor assetCap) := + (v.continuous.comp (continuous_id.prodMk continuous_const)).continuousOn + have huc : UniformContinuousOn (fun a : ℝ => v (a, z')) (Icc assetFloor assetCap) := + isCompact_Icc.uniformContinuousOn_of_continuous hcont + obtain ⟨η, hη, hspec⟩ := Metric.uniformContinuousOn_iff.mp huc ε hε + refine ⟨η, hη, fun a₁ h₁ a₂ h₂ hd => ?_⟩ + have := hspec a₁ h₁ a₂ h₂ (by rwa [Real.dist_eq]) + rwa [Real.dist_eq] at this + choose η hη hspec using hslice + -- `Z` is finite, so the moduli have a positive minimum + have hne : (Finset.univ : Finset Z).Nonempty := ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + refine ⟨Finset.univ.inf' hne η, ?_, fun z' a₁ h₁ a₂ h₂ hd => ?_⟩ + · exact (Finset.lt_inf'_iff _).2 fun z' _ => hη z' + · exact hspec z' a₁ h₁ a₂ h₂ + (lt_of_lt_of_le hd (Finset.inf'_le _ (Finset.mem_univ z'))) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/LargeBetaLight.lean b/LeanPool/AiyagariEquilibrium/Models/LargeBetaLight.lean new file mode 100644 index 000000000..6c842ea0c --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/LargeBetaLight.lean @@ -0,0 +1,214 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.MinimalMPC +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRateStep +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationHARA + +/-! +# Light's Theorem 1 at large `β` + +`policy_mono_withRate_hara` — a household facing a higher rate saves at least as much — needs +the saving cap slack along BOTH iterations, and in particular for the higher-rate economy +choosing against the lower-rate economy's iterates. `Calibrated` supplies that from an oscillation +bound uniform in the rate, which at Aiyagari's `β = 0.96` is off by orders of magnitude. + +This file supplies it from the minimal MPC instead, and the price is that the two rates have to +be CLOSE: within `1 - Þ₁` of each other, where `Þ₁ = (βR₁)^(1/γ)` is the patience factor at the +lower rate. That is not a defect of the estimate but of the comparison — a household facing a +much higher rate against an old continuation can save more than its own cap — and it costs +nothing, because monotonicity on overlapping short intervals is monotonicity on their union +(`monotoneOn_of_local`). + +## The hybrid bound + +Economy 2 choosing against economy 1's iterate `v` at assets `a` is economy 1 choosing against +`v` at `t a`, `t = R₁/R₂ ≤ 1` (`consumptionFnOf_withRate`). Consumption rises with assets, so +economy 2's consumption at `a` is at least economy 1's at `a` itself, which the minimal MPC bounds +by `κ₁ (y + R₁ a)`. Saving is then at most `(1-κ₁) y + (R₂ - κ₁ R₁) a`, and `R₂ - κ₁ R₁ = Þ₁ + +(r₂ - r₁)`: the coefficient on assets exceeds `Þ₁` by exactly the rate gap. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +/-! ### Local monotonicity is monotonicity -/ + +/-- **Monotone on short steps, monotone on the interval.** If `f x ≤ f y` whenever `x ≤ y` are at +most `ε` apart, then `f` is monotone on the interval: walk from `x` to `y` in steps of `ε`. -/ +theorem monotoneOn_of_local {f : ℝ → ℝ} {lo hi ε : ℝ} (hε : 0 < ε) + (h : ∀ x ∈ Icc lo hi, ∀ y ∈ Icc lo hi, x ≤ y → y - x ≤ ε → f x ≤ f y) : + MonotoneOn f (Icc lo hi) := by + have key : ∀ n : ℕ, ∀ x ∈ Icc lo hi, ∀ y ∈ Icc lo hi, x ≤ y → y ≤ x + n * ε → f x ≤ f y := by + intro n + induction n with + | zero => + intro x hx y hy hxy hle + simp only [Nat.cast_zero, zero_mul, add_zero] at hle + exact h x hx y hy hxy (by linarith) + | succ k ih => + intro x hx y hy hxy hle + by_cases hshort : y - x ≤ ε + · exact h x hx y hy hxy hshort + · push Not at hshort + set m : ℝ := y - ε with hm + have hxm : x ≤ m := by rw [hm]; linarith + have hmy : m ≤ y := by rw [hm]; linarith + have hmmem : m ∈ Icc lo hi := ⟨le_trans hx.1 hxm, le_trans hmy hy.2⟩ + have hmle : m ≤ x + k * ε := by + rw [hm]; push_cast at hle; linarith + exact le_trans (ih x hx m hmmem hxm hmle) (h m hmmem y hy hmy (by rw [hm]; linarith)) + intro x hx y hy hxy + obtain ⟨n, hn⟩ := exists_nat_ge ((y - x) / ε) + refine key n x hx y hy hxy ?_ + have := (div_le_iff₀ hε).mp hn + linarith + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] + +section AnyFloor + +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- Every iterate from `0` has concave slices. -/ +theorem concaveSlices_iterate_zero (n : ℕ) : + ConcaveSlices assetFloor assetCap ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + induction n with + | zero => exact concaveSlices_zero + | succ k ih => + rw [Function.iterate_succ_apply'] + exact P.concaveSlices_bellman ih + +end AnyFloor + +variable {assetCap : ℝ} (P : IncomeFluctuation Z 0 assetCap) + +/-! ### The hybrid cap slack -/ + +/-- **Cap slack for the higher-rate economy against the lower-rate economy's iterates**, from the +minimal MPC at the lower rate. The condition is one inequality in which the rate gap appears +explicitly. -/ +theorem crra_policyOf_withRate_lt_cap {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + {r₁ r₂ : ℝ} (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hκ₁ : 0 < (P.withRate r₁ h₁).minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hpos₁ : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < + (P.withRate r₁ h₁).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hthr₁ : (1 - (P.withRate r₁ h₁).minMPC γ) * (P.maxIncome + (1 + r₁) * assetCap) < assetCap) + (hgap : (1 - (P.withRate r₁ h₁).minMPC γ) * P.maxIncome + + ((1 - (P.withRate r₁ h₁).minMPC γ) * (1 + r₁) + (r₂ - r₁)) * assetCap < assetCap) + (n : ℕ) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₂ h₂).policyOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap := by + set Q₁ := P.withRate r₁ h₁ with hQ₁ + set Q₂ := P.withRate r₂ h₂ with hQ₂ + set v : (ℝ × Z) →ᵇ ℝ := (Q₁.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ) with hv + set κ : ℝ := Q₁.minMPC γ with hκ + have hR₁ : (0 : ℝ) < 1 + r₁ := h₁.1 + have hR₂ : (0 : ℝ) < 1 + r₂ := h₂.1 + have hslices : ConcaveSlices (0 : ℝ) assetCap v := Q₁.concaveSlices_iterate_zero n + have hκ1 : κ ≤ 1 := minMPC_le_one (P := Q₁) + -- economy 1's minimal-MPC bound at this iterate + have hmpc : κ * Q₁.resources (a, z) ≤ Q₁.consumptionFnOf v z a := + (Q₁.crra_minMPC_of_cap hγ0 (show Q₁.u = crraUtility γ from hu) rfl + hκ₁ hβ hpos₁ hthr₁ n).1 z a ha + -- the rescale, and monotonicity of consumption in assets + set t : ℝ := (1 + r₁) / (1 + r₂) with ht + have ht0 : 0 < t := div_pos hR₁ hR₂ + have ht1 : t ≤ 1 := by rw [ht, div_le_one hR₂]; linarith + have hta : t * a ∈ Icc (0 : ℝ) assetCap := + ⟨mul_nonneg ht0.le ha.1, le_trans (by nlinarith [ha.1, ha.2]) ha.2⟩ + have htale : t * a ≤ a := by nlinarith [ha.1] + have hrescale : Q₁.consumptionFnOf v z a = Q₂.consumptionFnOf v z (t * a) := + P.consumptionFnOf_withRate h₁ h₂ hr hslices z ha + have hmono : Q₂.consumptionFnOf v z (t * a) ≤ Q₂.consumptionFnOf v z a := + Q₂.consumptionFnOf_mono hslices hta ha htale + -- so economy 2 consumes at least `κ (y + R₁ a)` here + have hres₁ : Q₁.resources (a, z) = P.income z + (1 + r₁) * a := by + simp only [hQ₁, resources, withRate_income, withRate_interest, max_eq_right ha.1] + have hres₂ : Q₂.resources (a, z) = P.income z + (1 + r₂) * a := by + simp only [hQ₂, resources, withRate_income, withRate_interest, max_eq_right ha.1] + have hpol : Q₂.policyOf v (a, z) = Q₂.resources (a, z) - Q₂.consumptionFnOf v z a := by + simp only [consumptionFnOf, consumption]; ring + have hy : P.income z ≤ P.maxIncome := P.le_maxIncome z + have hcoef : (0 : ℝ) ≤ (1 - κ) * (1 + r₁) + (r₂ - r₁) := by nlinarith + rw [hpol, hres₂] + -- saving ≤ (1-κ) y + ((1-κ)(1+r₁) + (r₂-r₁)) a, and that is < cap at a = cap + have hbound : P.income z + (1 + r₂) * a - Q₂.consumptionFnOf v z a + ≤ (1 - κ) * P.income z + ((1 - κ) * (1 + r₁) + (r₂ - r₁)) * a := by + rw [hres₁] at hmpc + nlinarith [hmpc, hrescale, hmono] + have hmax : (1 - κ) * P.income z + ((1 - κ) * (1 + r₁) + (r₂ - r₁)) * a + ≤ (1 - κ) * P.maxIncome + ((1 - κ) * (1 + r₁) + (r₂ - r₁)) * assetCap := by + have h1 : (1 - κ) * P.income z ≤ (1 - κ) * P.maxIncome := + mul_le_mul_of_nonneg_left hy (by linarith) + have h2 : ((1 - κ) * (1 + r₁) + (r₂ - r₁)) * a + ≤ ((1 - κ) * (1 + r₁) + (r₂ - r₁)) * assetCap := + mul_le_mul_of_nonneg_left ha.2 hcoef + linarith + linarith [hbound, hmax, hgap] + +/-! ### Light's Theorem 1, locally -/ + +/-- **A household facing a slightly higher rate saves at least as much**, for CRRA with `γ ≤ 1`, +with every cap-slack hypothesis discharged from the minimal MPC. Positivity against arbitrary +concave continuations is the one hypothesis left, and it is free for CRRA (Inada). -/ +theorem crra_policy_mono_withRate_of_minMPC {γ : ℝ} (hγ0 : 0 < γ) (hγ1 : γ ≤ 1) + (hu : P.u = crraUtility γ) + {r₁ r₂ : ℝ} (h₁ : P.RateOK r₁) (h₂ : P.RateOK r₂) (hr : r₁ ≤ r₂) + (hκ₁ : 0 < (P.withRate r₁ h₁).minMPC γ) (hκ₂ : 0 < (P.withRate r₂ h₂).minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hpc₁ : (P.withRate r₁ h₁).PositiveConsumptionAll) + (hpc₂ : (P.withRate r₂ h₂).PositiveConsumptionAll) + (hthr₁ : (1 - (P.withRate r₁ h₁).minMPC γ) * (P.maxIncome + (1 + r₁) * assetCap) < assetCap) + (hthr₂ : (1 - (P.withRate r₂ h₂).minMPC γ) * (P.maxIncome + (1 + r₂) * assetCap) < assetCap) + (hgap : (1 - (P.withRate r₁ h₁).minMPC γ) * P.maxIncome + + ((1 - (P.withRate r₁ h₁).minMPC γ) * (1 + r₁) + (r₂ - r₁)) * assetCap < assetCap) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : + (P.withRate r₁ h₁).policy (a, z) ≤ (P.withRate r₂ h₂).policy (a, z) := by + -- positivity at the iterates, read in either economy + have hpos₁ : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < + (P.withRate r₁ h₁).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc₁ _ ((P.withRate r₁ h₁).concaveSlices_iterate_zero n) z a ha + have hpos₂ : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc₂ _ ((P.withRate r₂ h₂).concaveSlices_iterate_zero n) z a ha + have hposv : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < + (P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₁ h₁).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc₂ _ ((P.withRate r₁ h₁).concaveSlices_iterate_zero n) z a ha + -- cap slack along economy 2's own iteration, from its minimal MPC + have hslackw : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + (P.withRate r₂ h₂).policyOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap := + fun n a ha z => ((P.withRate r₂ h₂).crra_minMPC_of_cap hγ0 hu rfl + hκ₂ hβ hpos₂ hthr₂ n).2 a ha z + -- Carroll--Kimball along economy 2's iteration + have hcons : ∀ n : ℕ, ∀ z : Z, ConcaveOn ℝ (Icc (0 : ℝ) assetCap) + ((P.withRate r₂ h₂).consumptionFnOf + (((P.withRate r₂ h₂).toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z) := + (P.withRate r₂ h₂).concaveOn_consumptionFnOf_iterates_of_hara hγ0 le_rfl hβ + (by rw [haraUtility_zero_shift]; exact hu) hpos₂ + (fun n a ha z => (P.withRate r₂ h₂).policyOf_lt_maxSaving_of_pos (hpos₂ n z a ha) + (hslackw n a ha z)) + exact P.policy_mono_withRate_hara hγ0 hγ1 le_rfl (by rw [haraUtility_zero_shift]; exact hu) + h₁ h₂ hr (fun n a ha z => hposv n z a ha) (fun n a ha z => hpos₂ n z a ha) + (fun n a ha z => P.crra_policyOf_withRate_lt_cap hγ0 hu h₁ h₂ hr hκ₁ hβ hpos₁ hthr₁ + hgap n ha z) + hslackw hcons ha z + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/LogAiyagari.lean b/LeanPool/AiyagariEquilibrium/Models/LogAiyagari.lean new file mode 100644 index 000000000..c6e84a64d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/LogAiyagari.lean @@ -0,0 +1,217 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.LargeBetaLight +public import LeanPool.AiyagariEquilibrium.Models.EulerCorner +public import LeanPool.AiyagariEquilibrium.Distribution.Uniqueness +public import LeanPool.AiyagariEquilibrium.Models.MonotoneTransitions + +/-! +# The Doeblin chain at large `β` + +At each rate, the uniqueness of the stationary distribution and the convergence to it come from +the same three facts as always — a corner at the worst income state, decline above it, and the +worst state reachable from everywhere — but at Aiyagari's `β = 0.96` each has to come from a +route that does not carry `1/(1-β)` or `1/(1-βR)` in a constant. + +* The corner from the Euler inequality (`crra_policy_eq_zero_of_euler`), whose test is + `βR · minIncome^(-γ) < (income z₀ + R a₀)^(-γ)` — one inequality, no Lipschitz constant. +* Euler-based worst-state decline (`policy_lt_self_of_impatient_iid`), which holds at every + asset level above the constraint given `βR < 1` and iid income. +* Cap slack at the fixed point from the minimal MPC (`crra_policy_lt_cap_of_minMPC`). + +Nothing in this file mentions an oscillation bound. +-/ + +@[expose] public section + +open Set Filter Topology MeasureTheory BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **The exhaustion data at large `β`**, for CRRA, given that consumption is lowest at `z₀`. +The two ways of supplying that are iid income (`crra_exists_exhaust_of_euler_corner`) and +monotone transitions (`crra_exists_exhaust_of_euler_corner_monotone`). -/ +theorem crra_exists_exhaust_of_euler_corner_of_lowest {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} + (hlow : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, P.consumptionFn z₀ a ≤ P.consumptionFn z a) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + subst hfl + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + -- positivity, at the iterates and at the fixed point + have hposIt : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc _ (P.concaveSlices_iterate_zero n) z a ha + have hpos : ∀ z : Z, ∀ x ∈ Icc (0 : ℝ) assetCap, 0 < P.consumptionFn z x := + fun z x hx => hpc _ P.concaveSlices_valueFunction z x hx + -- cap slack at the fixed point, from the minimal MPC + have hslack : ∀ z : Z, ∀ x ∈ Icc (0 : ℝ) assetCap, P.policy (x, z) < P.maxSaving (x, z) := + fun z x hx => P.policyOf_lt_maxSaving_of_pos (hpos z x hx) + (P.crra_policy_lt_cap_of_minMPC hγ0 hu rfl hκ hβ hposIt hthr hx z) + -- the corner below `a₀`, from the Euler inequality + have hzero : ∀ a ∈ Icc (0 : ℝ) a₀, P.policy (a, z₀) = 0 := by + intro a ha + have hmem : a ∈ Icc (0 : ℝ) assetCap := ⟨ha.1, le_trans ha.2 hle⟩ + refine P.crra_policy_eq_zero_of_euler hγ0 hu rfl hβR hpos hslack hmem z₀ ?_ + have hres : P.resources (a, z₀) = P.income z₀ + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + have hres0 : 0 < P.income z₀ + (1 + P.interest) * a := by + have := lt_of_lt_of_le P.minIncome_pos (P.minIncome_le z₀) + nlinarith [ha.1] + have hle' : P.income z₀ + (1 + P.interest) * a ≤ P.income z₀ + (1 + P.interest) * a₀ := by + nlinarith [ha.2] + rw [hres] + exact lt_of_lt_of_le hcorner (rpow_neg_antitone hγ0 hres0 hle') + -- and decline above it, from impatience at the lowest-consumption state + refine P.exists_exhaust_of_decline ha₀ hle hzero fun a ha => ?_ + have hamem : a ∈ Icc (0 : ℝ) assetCap := ⟨le_trans ha₀.le ha.1, ha.2⟩ + exact P.policy_lt_self_of_impatient hβR (du := fun c => c ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_crraUtility γ hc) + (fun _ hx _ _ hxy => Real.rpow_le_rpow_of_nonpos hx hxy (by linarith)) + (fun c hc => Real.rpow_pos_of_pos hc _) hpos hslack hamem (lt_of_lt_of_le ha₀ ha.1) + (fun z' => hlow a hamem z') + +/-- **The exhaustion data with iid income.** -/ +theorem crra_exists_exhaust_of_euler_corner {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) + (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + subst hfl + exact P.crra_exists_exhaust_of_euler_corner_of_lowest hγ0 hu rfl hκ hβ hβR hpc hthr + (fun a ha z => P.consumptionFn_le_of_income_le hiid (hz₀ z) ha) ha₀ hle hcorner + +/-- **The exhaustion data with persistent income**: monotone transitions in place of iid, via +the income-monotonicity lemma (`consumptionFn_le_of_income_le_monotone`). The extra iterate-level +hypotheses that lemma needs are supplied by the minimal MPC. -/ +theorem crra_exists_exhaust_of_euler_corner_monotone {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hmono : P.MonotoneTransitions) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + subst hfl + have hposIt : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := + fun n z a ha => hpc _ (P.concaveSlices_iterate_zero n) z a ha + have hslackIt : ∀ n : ℕ, ∀ x ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (x, z) < assetCap := + fun n x hx z => (P.crra_minMPC_of_cap hγ0 hu rfl hκ hβ hposIt hthr n).2 x hx z + refine P.crra_exists_exhaust_of_euler_corner_of_lowest hγ0 hu rfl hκ hβ hβR hpc hthr + (fun a ha z => ?_) ha₀ hle hcorner + exact P.consumptionFn_le_of_income_le_monotone hmono (du := fun c => c ^ (-γ)) + (fun c hc => by rw [hu]; exact hasDerivAt_crraUtility γ hc) + (fun _ hx _ _ hxy => Real.rpow_le_rpow_of_nonpos hx hxy (by linarith)) + hpc hslackIt (hz₀ z) ha + +section Measure + +variable [MeasurableSpace Z] [BorelSpace Z] + +/-- **A unique stationary distribution at large `β`.** -/ +theorem crra_existsUnique_isStationary_of_euler_corner {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) : + ∃! μ : ProbabilityMeasure P.State, P.IsStationary μ := by + obtain ⟨N, hN⟩ := P.crra_exists_exhaust_of_euler_corner hγ0 hu hfl hκ hβ hβR hiid hpc hthr + hz₀ ha₀ hle hcorner + exact P.existsUnique_isStationary hreach hN + +/-- **Convergence to it from any start**, Doeblin with the atom at the constraint. -/ +theorem crra_tendsto_pushProb_of_euler_corner {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hiid : P.IidIncome) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) + (μ₀ : ProbabilityMeasure P.State) {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) : + Tendsto (fun m => P.pushProb^[m] μ₀) atTop (𝓝 μ) := by + classical + obtain ⟨N, hN⟩ := P.crra_exists_exhaust_of_euler_corner hγ0 hu hfl hκ hβ hβR hiid hpc hthr + hz₀ ha₀ hle hcorner + obtain ⟨z₁, -, hmin⟩ := Finset.exists_min_image Finset.univ + (fun z => P.transitionMatrix z z₀) ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + exact P.tendsto_pushProb_iterate (z₀ := z₀) (p₀ := P.transitionMatrix z₁ z₀) (N := N + 1) + (hreach z₁) (fun s => hmin _ (Finset.mem_univ _)) (P.badStep_iterate_eq hN) μ₀ hμ + +/-- **A unique stationary distribution at large `β`, with persistent income.** -/ +theorem crra_existsUnique_isStationary_of_euler_corner_monotone {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hmono : P.MonotoneTransitions) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) : + ∃! μ : ProbabilityMeasure P.State, P.IsStationary μ := by + obtain ⟨N, hN⟩ := P.crra_exists_exhaust_of_euler_corner_monotone hγ0 hu hfl hκ hβ hβR hmono + hpc hthr hz₀ ha₀ hle hcorner + exact P.existsUnique_isStationary hreach hN + +/-- **Convergence to it from any start, with persistent income.** -/ +theorem crra_tendsto_pushProb_of_euler_corner_monotone {γ : ℝ} (hγ0 : 0 < γ) + (hu : P.u = crraUtility γ) (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) + (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hmono : P.MonotoneTransitions) + (hpc : P.PositiveConsumptionAll) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) (hreach : ∀ z, 0 < P.transitionMatrix z z₀) + {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hcorner : (P.discount : ℝ) * (1 + P.interest) * P.minIncome ^ (-γ) + < (P.income z₀ + (1 + P.interest) * a₀) ^ (-γ)) + (μ₀ : ProbabilityMeasure P.State) {μ : ProbabilityMeasure P.State} (hμ : P.IsStationary μ) : + Tendsto (fun m => P.pushProb^[m] μ₀) atTop (𝓝 μ) := by + classical + obtain ⟨N, hN⟩ := P.crra_exists_exhaust_of_euler_corner_monotone hγ0 hu hfl hκ hβ hβR hmono + hpc hthr hz₀ ha₀ hle hcorner + obtain ⟨z₁, -, hmin⟩ := Finset.exists_min_image Finset.univ + (fun z => P.transitionMatrix z z₀) ⟨Classical.ofNonempty, Finset.mem_univ _⟩ + exact P.tendsto_pushProb_iterate (z₀ := z₀) (p₀ := P.transitionMatrix z₁ z₀) (N := N + 1) + (hreach z₁) (fun s => hmin _ (Finset.mem_univ _)) (P.badStep_iterate_eq hN) μ₀ hμ + +end Measure + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/MinimalMPC.lean b/LeanPool/AiyagariEquilibrium/Models/MinimalMPC.lean new file mode 100644 index 000000000..6f03bfec3 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/MinimalMPC.lean @@ -0,0 +1,585 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEuler +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationIterate +public import LeanPool.AiyagariEquilibrium.Analysis.HARA + +/-! +# The minimal marginal propensity to consume + +Carroll and Shanker's *Theoretical Foundations of Buffer Stock Saving*, draft dated +2025-12-05, Lemma 1 (Limiting MPCs), gives consumption-share bounds in their buffer-stock model: +https://econ-ark.github.io/BufferStockTheory/BufferStockTheory.pdf + +Here the minimal bound is proved for the finite capped-household model, under explicit +positivity and cap-slack hypotheses, + + `κ · m ≤ c(m)`, `κ = 1 - Þ/R`, `Þ = (βR)^(1/γ)`, + +for CRRA utility at a zero borrowing limit. `Þ` is Carroll's absolute patience factor, and `κ` +is the MPC of a perfect-foresight consumer with no human wealth. + +## Why this one and not the other + +The MAXIMAL MPC is `1 - ℘^(1/γ)Þ/R`, where `℘` is the probability of a zero-income event. This +model has `minIncome_pos`, so `℘ = 0`, the maximal MPC is `1`, and the bound it gives — +`c(m) ≤ m` — is the budget constraint and nothing more. That is not a defect of the estimate: a +liquidity-constrained household at the borrowing limit really does consume everything, and our +own corner condition says so. So only the minimal MPC has content here. + +## The proof is one Euler step, and the bound is self-reproducing + +Suppose the continuation's consumption function already satisfies `c ≥ κ m`. At an interior +choice `A > 0` the Euler inequality `u'(c) ≤ βR E[u'(c'(m'))]` applies, and `m' ≥ R A` because +income is positive, so + + `c^(-γ) ≤ βR (κ R A)^(-γ)`, hence `c ≥ (κR/Þ) A = (κR/Þ)(m - c)`, + +and `(κR/Þ)/(1 + κR/Þ) = κ` precisely because `1 - κ = Þ/R`. At the corner `A = 0` the household +consumes everything, so `c = m ≥ κ m` outright. The bound therefore survives the Bellman +operator, and holds at the zero continuation because the household with no future eats +everything. + +No limit of a sequence of MPCs is taken: `κ` is a fixed point of the recursion +`κ⁻¹ ↦ 1 + (Þ/R)κ⁻¹`, so the single constant reproduces itself. + +## The sign of the rate + +Everything below is stated with `0 < κ` as the hypothesis, not `0 ≤ r`. Return impatience +`βR < 1` at a non-negative rate gives `0 < κ` (`minMPC_pos`), but at `γ = 1` one has +`κ = 1 - β` (`minMPC_one`) at EVERY rate `r > -1`, so the log bounds — and with them the +capital ceiling — run into negative rates, where Aiyagari's `K(r)` has to be excluded too. + +## What it is for + +Two consequences the development already had homes for. `policy_lt_self_of_consumption_lower_bound` +turns it into the DECLINE condition, which was a raw assumption; and +`aggregateCapital_le_of_consumption_bound` turns it into a quantitative ceiling on capital +supply, in place of the asset cap. Both conditions ask for `(1-κ)R < 1`, and `(1-κ)R = Þ`, so +both are exactly `βR < 1`. +-/ + +@[expose] public section + +open Set Finset Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-- **The minimal marginal propensity to consume**, `κ = 1 - Þ/R` with `Þ = (βR)^(1/γ)`. -/ +noncomputable def minMPC (γ : ℝ) : ℝ := + 1 - ((P.discount : ℝ) * (1 + P.interest)) ^ (1 / γ) / (1 + P.interest) + +variable {P} + +/-- The absolute patience factor is positive when the household discounts at all. -/ +theorem patience_pos {γ : ℝ} (hβ : 0 < (P.discount : ℝ)) : + 0 < ((P.discount : ℝ) * (1 + P.interest)) ^ (1 / γ) := + Real.rpow_pos_of_pos (mul_pos hβ P.interest_gt_neg_one) _ + +/-- **Impatience is exactly `Þ < 1`.** -/ +theorem patience_lt_one {γ : ℝ} (hγ0 : 0 < γ) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) : + ((P.discount : ℝ) * (1 + P.interest)) ^ (1 / γ) < 1 := by + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + exact Real.rpow_lt_one (by positivity) hβR (by positivity) + +/-- `(1 - κ)R = Þ`: the factor by which assets grow under the bound is the patience factor. -/ +theorem one_sub_minMPC_mul {γ : ℝ} : + (1 - P.minMPC γ) * (1 + P.interest) + = ((P.discount : ℝ) * (1 + P.interest)) ^ (1 / γ) := by + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + simp only [minMPC] + field_simp + ring + +theorem minMPC_le_one {γ : ℝ} : P.minMPC γ ≤ 1 := by + unfold minMPC + exact sub_le_self _ (div_nonneg + (Real.rpow_nonneg (mul_nonneg P.discount.coe_nonneg P.interest_gt_neg_one.le) _) + P.interest_gt_neg_one.le) + +/-- **At `γ = 1` the patience factor is `βR` itself**, so `κ = 1 - β`: log and log Stone--Geary +need no root at all. -/ +theorem minMPC_one : P.minMPC 1 = 1 - (P.discount : ℝ) := by + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + simp only [minMPC, div_one, Real.rpow_one] + rw [mul_div_assoc, div_self hR.ne', mul_one] + +/-- **Sufficient conditions for a positive minimal MPC:** impatience and a nonnegative rate. +Here `βR < 1` and `R ≥ 1` imply the required return-impatience bound; these conditions are +not necessary, as `minMPC_one` also applies at negative admissible rates. -/ +theorem minMPC_pos {γ : ℝ} (hγ0 : 0 < γ) (hint : 0 ≤ P.interest) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) : 0 < P.minMPC γ := by + have hR : (1 : ℝ) ≤ 1 + P.interest := by linarith + have hT := patience_lt_one (P := P) hγ0 hβR + simp only [minMPC, sub_pos] + rw [div_lt_one (by linarith)] + linarith + +/-- Negative powers reverse the order: a smaller `x ^ (-γ)` means a larger `x`. -/ +theorem le_of_rpow_neg_le {x y γ : ℝ} (hx : 0 < x) (hy : 0 < y) (hγ : 0 < γ) + (h : x ^ (-γ) ≤ y ^ (-γ)) : y ≤ x := by + have hxp : (0 : ℝ) < x ^ γ := Real.rpow_pos_of_pos hx _ + have hyp : (0 : ℝ) < y ^ γ := Real.rpow_pos_of_pos hy _ + rw [Real.rpow_neg hx.le, Real.rpow_neg hy.le, inv_le_inv₀ hxp hyp] at h + by_contra hcon + exact absurd h (not_le.mpr (Real.rpow_lt_rpow hx.le (not_le.mp hcon) hγ)) + +/-! ### The bound along the iteration -/ + +/-- One shifted-CRRA Euler step propagates the minimal consumption bound. -/ +theorem hara_minMPC_step {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + {v : (ℝ × Z) →ᵇ ℝ} + (hposv : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < P.consumptionFnOf v z a) + (hcapv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, P.policyOf v (a, z) < assetCap) + (hposW : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (ih : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * (η + P.resources (a, z)) ≤ η + P.consumptionFnOf v z a) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPC γ * (η + P.resources (a, z)) ≤ η + P.consumptionFnOf (P.toExtended.bellman v) z a := by + subst hfl + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hβR0 : (0 : ℝ) < (P.discount : ℝ) * (1 + P.interest) := mul_pos hβ hR + set T : ℝ := ((P.discount : ℝ) * (1 + P.interest)) ^ (1 / γ) with hTdef + have hT0 : 0 < T := patience_pos (P := P) hβ + have hκ0 : 0 < P.minMPC γ := hκ + have hκ1 : P.minMPC γ ≤ 1 := minMPC_le_one (P := P) + have hkey : (1 - P.minMPC γ) * (1 + P.interest) = T := one_sub_minMPC_mul (P := P) + have hTneg : T ^ (-γ) = ((P.discount : ℝ) * (1 + P.interest))⁻¹ := by + rw [hTdef, ← Real.rpow_mul hβR0.le, + show (1 / γ) * (-γ) = -1 from by field_simp, Real.rpow_neg_one] + set W : (ℝ × Z) →ᵇ ℝ := P.toExtended.bellman v with hWdef + set A : ℝ := P.policyOf W (a, z) with hAdef + have hAmem : A ∈ Icc (0 : ℝ) assetCap := P.feasible_subset_region (P.policyOf_mem W (a, z)) + set c : ℝ := P.consumptionFnOf W z a with hcdef + have hcA : c = P.resources (a, z) - A := rfl + have hc0 : 0 < c := hposW z a ha + rcases eq_or_lt_of_le hAmem.1 with hA0 | hA0 + · nlinarith only [hκ1, hc0, hcA, hA0, hη] + · have hslack : ∀ z' : Z, P.policyOf v (A, z') < P.maxSaving (A, z') := fun z' => + P.policyOf_lt_maxSaving_of_pos (hposv z' A hAmem) (hcapv A hAmem z') + have hc' : ∀ z' : Z, 0 < P.consumptionFnOf v z' A := fun z' => hposv z' A hAmem + have hder : ∀ z' : Z, HasDerivAt P.u ((η + P.consumptionFnOf v z' A) ^ (-γ)) + (P.consumptionFnOf v z' A) := by + intro z'; rw [hu] + exact hasDerivAt_haraUtility γ (by linarith [hc' z']) + have heuler := P.euler_ge (v := v) (z := z) (a := a) (A := A) ha rfl hA0 hslack hc0 + (by rw [hu]; exact hasDerivAt_haraUtility γ (by linarith)) hc' hder + -- the continuation's shifted consumption is at least `κ (η + R A)` + set X : ℝ := P.minMPC γ * (η + (1 + P.interest) * A) with hXdef + have hX0 : 0 < X := by + have : (0 : ℝ) < (1 + P.interest) * A := by positivity + rw [hXdef]; nlinarith [hκ0, hη] + have hstep : ∀ z' : Z, X ≤ η + P.consumptionFnOf v z' A := by + intro z' + refine le_trans ?_ (ih z' A hAmem) + have hres : P.resources (A, z') = P.income z' + (1 + P.interest) * A := by + simp only [resources, max_eq_right hAmem.1] + have hinc : 0 < P.income z' := lt_of_lt_of_le P.minIncome_pos (P.minIncome_le z') + rw [hres, hXdef] + nlinarith only [hκ0, hinc] + have hsum : ∑ z' : Z, P.transitionMatrix z z' + * (η + P.consumptionFnOf v z' A) ^ (-γ) ≤ X ^ (-γ) := by + calc ∑ z' : Z, P.transitionMatrix z z' * (η + P.consumptionFnOf v z' A) ^ (-γ) + ≤ ∑ z' : Z, P.transitionMatrix z z' * X ^ (-γ) := + Finset.sum_le_sum fun z' _ => + mul_le_mul_of_nonneg_left (rpow_neg_antitone hγ0 hX0 (hstep z')) + (P.transitionMatrix_nonneg z z') + _ = X ^ (-γ) := by rw [← Finset.sum_mul, P.transitionMatrix_sum z, one_mul] + have hchain : (η + c) ^ (-γ) ≤ (P.discount : ℝ) * (1 + P.interest) * X ^ (-γ) := by + have := le_trans heuler + (mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left hsum hR.le) hβ.le) + linarith [this] + have hTc : (T * (η + c)) ^ (-γ) ≤ X ^ (-γ) := by + rw [Real.mul_rpow hT0.le (by linarith), hTneg, inv_mul_le_iff₀ hβR0] + linarith [hchain] + have hXTc : X ≤ T * (η + c) := + le_of_rpow_neg_le (by positivity) hX0 hγ0 hTc + rw [hXdef] at hXTc + -- `κR + Þ = R`, and the extra `η` term has the right sign because `κ ≥ 0` + refine le_of_mul_le_mul_right (a := 1 + P.interest) ?_ hR + linear_combination hXTc - (η + c) * hkey + - (P.minMPC γ * (1 + P.interest)) * hcA + mul_nonneg hκ0.le hη + +/-- **One Euler step of the minimal-MPC bound.** If a continuation `v` satisfies +`κ m ≤ c_v(m)` everywhere, with consumption positive and the cap slack against it, then so does +`bellman v`. Extracted from the induction so that it can also drive a SIMULTANEOUS induction on +cap slack (`crra_minMPC_of_cap`), which is what large `β` needs. -/ +theorem crra_minMPC_step {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + {v : (ℝ × Z) →ᵇ ℝ} + (hposv : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, 0 < P.consumptionFnOf v z a) + (hcapv : ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, P.policyOf v (a, z) < assetCap) + (hposW : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf (P.toExtended.bellman v) z a) + (ih : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) ≤ P.consumptionFnOf v z a) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPC γ * P.resources (a, z) ≤ P.consumptionFnOf (P.toExtended.bellman v) z a := by + simpa only [zero_add] using P.hara_minMPC_step hγ0 (le_refl (0 : ℝ)) + (hu.trans (haraUtility_zero_shift γ).symm) hfl hκ hβ hposv hcapv hposW + (fun z' a' ha' => by simpa only [zero_add] using ih z' a' ha') z ha + +/-! ### The shifted-CRRA version + +With `u'(c) = (η + c)^(-γ)` the same argument runs on `η + c` against `η + m`: + + `κ (η + m) ≤ η + c(m)`, + +with the SAME `κ`. The step produces `c ≥ κ m + η(κ - Þ)/R`, and what has to be checked is that +this is at least `κ m - η(1-κ)`, which holds because `κ ≥ 0`. So the subsistence level costs +nothing in the constant; it only shifts what the constant multiplies. + +Read as a bound on saving this is `policy ≤ (1-κ)(η + m)`, so the household behaves like a CRRA +household with income raised by `η` — which is what a subsistence level is. -/ + +/-- **The minimal-MPC bound for shifted CRRA, at every iterate.** -/ +theorem hara_minMPC_mul_le_consumptionFnOf {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) : + ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * (η + P.resources (a, z)) + ≤ η + P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := by + subst hfl + have hκ1 : P.minMPC γ ≤ 1 := minMPC_le_one (P := P) + intro n + induction n with + | zero => + intro z a ha + have hz : P.policyOf (0 : (ℝ × Z) →ᵇ ℝ) (a, z) = 0 := P.policyOf_zero (s := (a, z)) ha + have hres : (0 : ℝ) ≤ P.resources (a, z) := (P.assetFloor_lt_resources (a, z)).le + have heq : P.consumptionFnOf ((P.toExtended.bellman)^[0] (0 : (ℝ × Z) →ᵇ ℝ)) z a + = P.resources (a, z) := by + change P.consumption (a, z) (P.policyOf (0 : (ℝ × Z) →ᵇ ℝ) (a, z)) = P.resources (a, z) + rw [hz]; simp [consumption] + rw [heq] + nlinarith only [hres, hκ1, hη] + | succ k ih => + intro z a ha + rw [Function.iterate_succ_apply'] + exact P.hara_minMPC_step hγ0 hη hu rfl hκ hβ (hpos k) (hcap k) + (fun z' b hb => by + have h := hpos (k + 1) z' b hb + simpa only [Function.iterate_succ_apply'] using h) ih z ha + +/-- **The shifted-CRRA bound at the value function.** -/ +theorem hara_minMPC_mul_le_consumptionFn {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPC γ * (η + P.resources (a, z)) ≤ η + P.consumptionFn z a := by + have hiter := P.hara_minMPC_mul_le_consumptionFnOf hγ0 hη hu hfl hκ hβ hpos hcap + have hmem : ((a, z) : ℝ × Z).1 ∈ Icc assetFloor assetCap := by rw [hfl]; exact ha + have htend : Tendsto + (fun n => P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z)) atTop + (𝓝 (P.policy (a, z))) := + P.tendsto_policyOf (P.toExtended.tendsto_iterate_valueFunction 0) + P.concaveSlices_valueFunction hmem + have htendc : Tendsto + (fun n => η + P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) atTop + (𝓝 (η + P.consumptionFn z a)) := by + have hcf : P.consumptionFn z a = P.resources (a, z) - P.policy (a, z) := rfl + rw [hcf, ← add_sub_assoc] + simpa only [consumptionFnOf, consumption, ← add_sub_assoc] using + (tendsto_const_nhds (x := η + P.resources (a, z)) (f := atTop (α := ℕ))).sub htend + exact ge_of_tendsto htendc (Filter.Eventually.of_forall fun n => hiter n z a ha) + +/-- **The minimal-MPC bound, at every iterate.** Consumption is at least `κ` times cash on hand, +uniformly in the horizon — so it passes to the value function. + +The hypotheses beyond CRRA are the two the Euler inequality needs and nothing else: consumption +positive at each iterate, and the saving cap slack there. Both are fields (or immediate +consequences of fields) of `Calibrated`. -/ +theorem crra_minMPC_mul_le_consumptionFnOf {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) : + ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) + ≤ P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a := by + simpa only [zero_add] using P.hara_minMPC_mul_le_consumptionFnOf hγ0 (le_refl (0 : ℝ)) + (hu.trans (haraUtility_zero_shift γ).symm) hfl hκ hβ hpos hcap + +/-! ### At the fixed point, and what it buys + +The bound is uniform in the horizon, so it survives the limit: `tendsto_policyOf` says argmaxes +follow uniform limits when the limit has concave slices, which the value function does. -/ + +/-- **The minimal-MPC bound at the value function.** -/ +theorem crra_minMPC_mul_le_consumptionFn {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPC γ * P.resources (a, z) ≤ P.consumptionFn z a := by + simpa only [zero_add] using P.hara_minMPC_mul_le_consumptionFn hγ0 (le_refl (0 : ℝ)) + (hu.trans (haraUtility_zero_shift γ).symm) hfl hκ hβ hpos hcap z ha + +/-- **The decline condition, from primitives.** Above an explicit asset level the household runs +its assets down, because it consumes at least the share `κ` of cash on hand and +`(1-κ)R = Þ < 1`. This is the hypothesis `hdecl` of `exists_exhaust_of_decline`, which the +development has carried as an assumption. -/ +theorem crra_policy_lt_self_of_minMPC {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) + (hgt : (1 - P.minMPC γ) * P.income z + < (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a) : + P.policy (a, z) < a := by + subst hfl + refine P.policy_lt_self_of_consumption_lower_bound (ε := P.minMPC γ) ha ?_ (by linarith) + simpa using P.crra_minMPC_mul_le_consumptionFn hγ0 hu rfl hκ hβ hpos hcap z ha + +/-! ### Cap slack from the bound itself + +The induction above ASSUMES the cap is slack at every iterate. At small `β` that is supplied by +an oscillation bound; at Aiyagari's `β = 0.96` no such bound is available. But the minimal-MPC +bound itself implies cap slack — saving is at most `(1-κ) m`, and `(1-κ)(maxIncome + R·cap) +< cap` once the cap is large enough — so the two can be proved TOGETHER, one induction carrying +both. The cap is then bookkeeping again: any `cap > (1-κ)maxIncome / (1 - (1-κ)R)` will do, and +`1 - (1-κ)R = 1 - Þ > 0`. -/ + +/-- Saving under the minimal-MPC bound is at most `(1-κ) m`, hence below the cap once the cap +clears `(1-κ)(maxIncome + R·cap)`. -/ +theorem crra_policyOf_lt_cap_of_minMPC_bound {γ : ℝ} (hfl : assetFloor = 0) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {v : (ℝ × Z) →ᵇ ℝ} + (hv : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) ≤ P.consumptionFnOf v z a) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : P.policyOf v (a, z) < assetCap := by + subst hfl + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hκ1 : P.minMPC γ ≤ 1 := minMPC_le_one (P := P) + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right ha.1] + have hbd : P.income z + (1 + P.interest) * a ≤ P.maxIncome + (1 + P.interest) * assetCap := by + have := P.le_maxIncome z + nlinarith [ha.2, hR] + have hpol : P.policyOf v (a, z) = P.resources (a, z) - P.consumptionFnOf v z a := by + simp only [consumptionFnOf, consumption]; ring + have hmpc := hv z a ha + have hmono : (1 - P.minMPC γ) * P.resources (a, z) + ≤ (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) := by + rw [hres]; exact mul_le_mul_of_nonneg_left hbd (by linarith) + rw [hpol] + linarith + +/-- **The minimal-MPC bound and cap slack, by one induction.** Nothing is assumed about the cap +beyond one inequality among the primitives. -/ +theorem crra_minMPC_of_cap {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) : + ∀ n : ℕ, + (∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) + ≤ P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + ∧ (∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) := by + intro n + induction n with + | zero => + have hκ1 : P.minMPC γ ≤ 1 := minMPC_le_one (P := P) + have hm : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) + ≤ P.consumptionFnOf ((P.toExtended.bellman)^[0] (0 : (ℝ × Z) →ᵇ ℝ)) z a := by + intro z a ha + subst hfl + have hz : P.policyOf (0 : (ℝ × Z) →ᵇ ℝ) (a, z) = 0 := P.policyOf_zero (s := (a, z)) ha + have hres : (0 : ℝ) ≤ P.resources (a, z) := (P.assetFloor_lt_resources (a, z)).le + have heq : P.consumptionFnOf ((P.toExtended.bellman)^[0] (0 : (ℝ × Z) →ᵇ ℝ)) z a + = P.resources (a, z) := by + change P.consumption (a, z) (P.policyOf (0 : (ℝ × Z) →ᵇ ℝ) (a, z)) = P.resources (a, z) + rw [hz]; simp [consumption] + rw [heq] + nlinarith only [hres, hκ1] + exact ⟨hm, fun a ha z => P.crra_policyOf_lt_cap_of_minMPC_bound hfl hthr hm ha z⟩ + | succ k ih => + obtain ⟨ihm, ihc⟩ := ih + have hiter : (P.toExtended.bellman)^[k + 1] (0 : (ℝ × Z) →ᵇ ℝ) + = P.toExtended.bellman ((P.toExtended.bellman)^[k] (0 : (ℝ × Z) →ᵇ ℝ)) := + Function.iterate_succ_apply' _ _ _ + have hm : ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + P.minMPC γ * P.resources (a, z) + ≤ P.consumptionFnOf ((P.toExtended.bellman)^[k + 1] (0 : (ℝ × Z) →ᵇ ℝ)) z a := by + intro z a ha + rw [hiter] + exact P.crra_minMPC_step hγ0 hu hfl hκ hβ (hpos k) ihc + (fun z' b hb => by have := hpos (k + 1) z' b hb; rwa [hiter] at this) ihm z ha + exact ⟨hm, fun a ha z => P.crra_policyOf_lt_cap_of_minMPC_bound hfl hthr hm ha z⟩ + +/-- **The minimal-MPC bound at the value function, with the cap from primitives.** -/ +theorem crra_minMPC_mul_le_consumptionFn_of_cap {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPC γ * P.resources (a, z) ≤ P.consumptionFn z a := + P.crra_minMPC_mul_le_consumptionFn hγ0 hu hfl hκ hβ hpos + (fun n => (P.crra_minMPC_of_cap hγ0 hu hfl hκ hβ hpos hthr n).2) z ha + +/-- **The cap is slack at the fixed point**, from primitives. -/ +theorem crra_policy_lt_cap_of_minMPC {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hthr : (1 - P.minMPC γ) * (P.maxIncome + (1 + P.interest) * assetCap) < assetCap) + {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) (z : Z) : P.policy (a, z) < assetCap := + P.crra_policyOf_lt_cap_of_minMPC_bound hfl hthr + (fun z' _b hb => + P.crra_minMPC_mul_le_consumptionFn_of_cap hγ0 hu hfl hκ hβ hpos hthr z' hb) + ha z + +/-- **The share form.** `κ(η + m) ≤ η + c` is not a bound on the SHARE `c/m`, because of the +constant. Charging the constant against the smallest cash on hand there can be gives one: +`1 - (1-κ)(1 + η/minIncome)` is a share the household always consumes. It is what the capital +ceiling needs, and it degrades gracefully — at `η = 0` it is `κ` itself. -/ +noncomputable def minMPCShare (γ η : ℝ) : ℝ := + P.minMPC γ - (1 - P.minMPC γ) * η / P.minIncome + +theorem hara_minMPCShare_mul_le_consumptionFn {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) : + P.minMPCShare γ η * P.resources (a, z) ≤ P.consumptionFn z a := by + have hbound := P.hara_minMPC_mul_le_consumptionFn hγ0 hη hu hfl hκ hβ hpos hcap z ha + have hκ1 : P.minMPC γ ≤ 1 := minMPC_le_one (P := P) + have hy : 0 < P.minIncome := P.minIncome_pos + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right (hfl ▸ ha.1 : assetFloor ≤ a)] + have hmin : P.minIncome ≤ P.resources (a, z) := by + have h1 : P.minIncome ≤ P.income z := P.minIncome_le z + have h2 : (0 : ℝ) ≤ (1 + P.interest) * a := by + have : (0 : ℝ) ≤ a := hfl ▸ ha.1 + positivity + rw [hres]; linarith + set κ : ℝ := P.minMPC γ with hκdef + set m : ℝ := P.resources (a, z) with hmdef + set q : ℝ := (1 - κ) * η / P.minIncome with hqdef + have hnn : (0 : ℝ) ≤ (1 - κ) * η := mul_nonneg (by linarith) hη + have hgap : η - κ * η ≤ m * q := by + rw [hqdef, ← mul_div_assoc, le_div_iff₀ hy] + nlinarith [mul_le_mul_of_nonneg_left hmin hnn] + have hbound' : κ * η + κ * m ≤ η + P.consumptionFn z a := by nlinarith [hbound] + simp only [minMPCShare, ← hκdef, ← hqdef] + nlinarith [hbound', hgap] + +/-- **The decline condition for shifted CRRA.** Saving is at most `(1-κ)(η + m)`, so the +household behaves like a CRRA household whose income is raised by the subsistence level. -/ +theorem hara_policy_lt_self_of_minMPC {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + (z : Z) {a : ℝ} (ha : a ∈ Icc (0 : ℝ) assetCap) + (hgt : (1 - P.minMPC γ) * (η + P.income z) + < (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a) : + P.policy (a, z) < a := by + have hbound := P.hara_minMPC_mul_le_consumptionFn hγ0 hη hu hfl hκ hβ hpos hcap z ha + have hres : P.resources (a, z) = P.income z + (1 + P.interest) * a := by + simp only [resources, max_eq_right (hfl ▸ ha.1 : assetFloor ≤ a)] + have hpol : P.policy (a, z) = P.resources (a, z) - P.consumptionFn z a := by + simp only [consumptionFn, consumption]; ring + rw [hpol, hres] + rw [hres] at hbound + nlinarith [hbound, hgt] + +/-- **The Doeblin exhaustion data for shifted CRRA**, with no assumption on the income process. -/ +theorem hara_exists_exhaust_of_minMPC {γ η : ℝ} (hγ0 : 0 < γ) (hη : 0 ≤ η) + (hu : P.u = haraUtility γ η) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + {z₀ : Z} {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc (0 : ℝ) a₀, P.policy (a, z₀) = 0) + (hthr : (1 - P.minMPC γ) * (η + P.income z₀) + < (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a₀) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + subst hfl + have hslope : (0 : ℝ) < 1 - (1 - P.minMPC γ) * (1 + P.interest) := by + rw [one_sub_minMPC_mul (P := P)] + have := patience_lt_one (P := P) hγ0 hβR + linarith + refine P.exists_exhaust_of_decline ha₀ hle hzero fun a ha => ?_ + refine P.hara_policy_lt_self_of_minMPC hγ0 hη hu rfl hκ hβ hpos hcap z₀ + ⟨le_trans ha₀.le ha.1, ha.2⟩ ?_ + have hmono : (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a₀ + ≤ (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a := + mul_le_mul_of_nonneg_left ha.1 hslope.le + linarith + +/-! ### The exhaustion data, without `IidIncome` + +`exists_exhaust_of_impatient` (the capped worst-state decline argument) also supplies +the decline half from +impatience, but it gets there by comparing income states: consumption is lowest where income is +lowest, which needs `IidIncome` so that the continuation does not depend on today's state. The +minimal-MPC route compares nothing. It is a statement about the budget and the Euler inequality +at ONE state, so it holds whatever the income process does. + +What it costs instead is the two iterate-level hypotheses, which the Açıkgöz route asks for only +at the fixed point. -/ + +/-- **The Doeblin exhaustion data from the minimal MPC.** Only the corner condition and one +threshold inequality are left to the calibration — and no assumption is made about the income +process. -/ +theorem crra_exists_exhaust_of_minMPC {γ : ℝ} (hγ0 : 0 < γ) (hu : P.u = crraUtility γ) + (hfl : assetFloor = 0) (hκ : 0 < P.minMPC γ) (hβ : 0 < (P.discount : ℝ)) + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) + (hpos : ∀ n : ℕ, ∀ z : Z, ∀ a ∈ Icc (0 : ℝ) assetCap, + 0 < P.consumptionFnOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) z a) + (hcap : ∀ n : ℕ, ∀ a ∈ Icc (0 : ℝ) assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (a, z) < assetCap) + {z₀ : Z} {a₀ : ℝ} (ha₀ : 0 < a₀) (hle : a₀ ≤ assetCap) + (hzero : ∀ a ∈ Icc (0 : ℝ) a₀, P.policy (a, z₀) = 0) + (hthr : (1 - P.minMPC γ) * P.income z₀ + < (1 - (1 - P.minMPC γ) * (1 + P.interest)) * a₀) : + ∃ N : ℕ, (P.gBad z₀)^[N] P.topState = P.botState := by + exact P.hara_exists_exhaust_of_minMPC hγ0 (le_refl (0 : ℝ)) + (hu.trans (haraUtility_zero_shift γ).symm) hfl hκ hβ hβR hpos hcap ha₀ hle hzero + (by simpa only [zero_add] using hthr) + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/MonotoneTransitions.lean b/LeanPool/AiyagariEquilibrium/Models/MonotoneTransitions.lean new file mode 100644 index 000000000..cfca1acf4 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/MonotoneTransitions.lean @@ -0,0 +1,255 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationEuler +public import LeanPool.AiyagariEquilibrium.Models.ImpatientDecline +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationRate + +/-! +# Persistent income: the decline condition from monotone transitions + +Açıkgöz's decline argument (`policy_lt_self_of_impatient`) runs the Euler inequality at the state +where consumption is lowest, and needs to know which state that is. With iid income it is the +lowest-income state, because every state shares a continuation. With persistent income the +continuations differ, and the lowest-income state is still the lowest-consumption state only if +its continuation is the STEEPEST — a poor household today expects to be poor tomorrow, so wealth +is worth more to it at the margin. + +This file proves the property in secant form for a finite income process ordered by income, +using induction on Bellman iterates. Related literature: Huggett (1993), *The Risk-Free Rate +in Heterogeneous-Agent Incomplete-Insurance Economies*. + +## Monotone transitions + +The condition is stated by its use: a state with higher income has a transition row that makes +every income-antitone function of tomorrow's state smaller in expectation +(`MonotoneTransitions`). For two states it is exactly `π(h|h) ≥ π(h|l)` +(`monotoneTransitions_of_two_states`); for a Tauchen chain it is first-order stochastic +dominance of the rows, which the chain has by construction. + +## The induction + +`IncDiffAcrossStates v` says the increments of `v` in assets are antitone in income. It survives +the Bellman operator because the envelope derivative is `R · u'(c)` and consumption at a fixed +asset level is increasing in income (`consumptionFnOf_le_across_states`) — which in turn follows +from single crossing: a richer household with a flatter continuation saves less of its extra +resources than it receives. The base case is the zero continuation, and the property passes to +the value function as a pointwise limit. +-/ + +@[expose] public section + +open Set Filter Topology BoundedContinuousFunction + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} (P : IncomeFluctuation Z assetFloor assetCap) + +/-! ### The two properties -/ + +/-- **Monotone transitions.** A higher-income state's transition row makes every +income-antitone function of tomorrow's state smaller in expectation: first-order stochastic +dominance of the rows, in the income order. -/ +def MonotoneTransitions : Prop := + ∀ z₁ z₂ : Z, P.income z₁ ≤ P.income z₂ → ∀ f : Z → ℝ, + (∀ w₁ w₂ : Z, P.income w₁ ≤ P.income w₂ → f w₂ ≤ f w₁) → + ∑ z' : Z, P.transitionMatrix z₂ z' * f z' ≤ ∑ z' : Z, P.transitionMatrix z₁ z' * f z' + +/-- iid income has monotone transitions, trivially: the rows are all equal. -/ +theorem monotoneTransitions_of_iid (hiid : P.IidIncome) : P.MonotoneTransitions := by + intro z₁ z₂ _ f _ + exact le_of_eq (Finset.sum_congr rfl fun z' _ => by rw [hiid z₂ z₁ z']) + +/-- Changing the rate leaves the income process, hence monotonicity, untouched. -/ +theorem monotoneTransitions_withRate {assetCap : ℝ} {P : IncomeFluctuation Z 0 assetCap} + (hmono : P.MonotoneTransitions) {r : ℝ} (hrr : P.RateOK r) : + (P.withRate r hrr).MonotoneTransitions := hmono + +/-- **Increments of `v` in assets are antitone in income**: the secant form of Huggett's +`v'(a, e_h) ≤ v'(a, e_l)`. -/ +def IncDiffAcrossStates (v : (ℝ × Z) →ᵇ ℝ) : Prop := + ∀ z₁ z₂ : Z, P.income z₁ ≤ P.income z₂ → ∀ x y : ℝ, + x ∈ Icc assetFloor assetCap → y ∈ Icc assetFloor assetCap → x ≤ y → + v (y, z₂) - v (x, z₂) ≤ v (y, z₁) - v (x, z₁) + +theorem incDiffAcrossStates_zero : P.IncDiffAcrossStates 0 := by + intro _ _ _ _ _ _ _ _ + simp + +/-- The continuation inherits the property, by monotone transitions: the increment +`v (y, ·) - v (x, ·)` is income-antitone, and the poorer state's row weights it more. -/ +theorem contOf_sub_le_across_states (hmono : P.MonotoneTransitions) {v : (ℝ × Z) →ᵇ ℝ} + (hv : P.IncDiffAcrossStates v) {z₁ z₂ : Z} (hz : P.income z₁ ≤ P.income z₂) + {x y : ℝ} (hx : x ∈ Icc assetFloor assetCap) (hy : y ∈ Icc assetFloor assetCap) + (hxy : x ≤ y) : + P.contOf v z₂ y - P.contOf v z₂ x ≤ P.contOf v z₁ y - P.contOf v z₁ x := by + simp only [contOf, ← Finset.sum_sub_distrib, ← mul_sub] + exact hmono z₁ z₂ hz (fun z' => v (y, z') - v (x, z')) fun w₁ w₂ hw => hv w₁ w₂ hw x y hx hy hxy + +/-! ### Consumption rises with income at a fixed asset level + +The exchange argument of `consumptionFn_mono_of_cont_eq`, with the shared continuation replaced +by an ordered pair: the richer state has a flatter continuation as well as more resources, and +both push consumption up. -/ + +/-! ### The Bellman step, by the envelope -/ + +/-- **Income-antitone increments, one Bellman step.** If `v` has income-antitone increments, +then so does +`bellman v`: the derivative of `bellman v (·, z)` is `R · u'(c_v(·, z))`, and consumption at a +fixed asset level rises with income. -/ +theorem incDiffAcrossStates_bellman (hmono : P.MonotoneTransitions) + {v : (ℝ × Z) →ᵇ ℝ} {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) + (hv : ConcaveSlices assetFloor assetCap v) (hpc : P.PositiveConsumptionAll) + (hslack : ∀ x ∈ Icc assetFloor assetCap, ∀ z : Z, P.policyOf v (x, z) < assetCap) + (hid : P.IncDiffAcrossStates v) : + P.IncDiffAcrossStates (P.toExtended.bellman v) := by + intro z₁ z₂ hz x y hx hy hxy + have hR : (0 : ℝ) < 1 + P.interest := P.interest_gt_neg_one + -- the difference `bellman v (·, z₁) - bellman v (·, z₂)` is monotone in assets + have key : ∀ a ∈ Ioo assetFloor assetCap, ∃ D : ℝ, 0 ≤ D ∧ + HasDerivAt (fun t => (P.toExtended.bellman v) (t, z₁) - (P.toExtended.bellman v) (t, z₂)) + D a := by + intro a ha + have hamem : a ∈ Icc assetFloor assetCap := ⟨ha.1.le, ha.2.le⟩ + have hpos₁ : 0 < P.consumptionFnOf v z₁ a := hpc v hv z₁ a hamem + have hpos₂ : 0 < P.consumptionFnOf v z₂ a := hpc v hv z₂ a hamem + have hd₁ := P.hasDerivAt_bellman ha.1 ha.2 (P.concaveSlices_bellman hv z₁) + (P.policyOf_lt_maxSaving hpc hv hamem (hslack a hamem z₁)) hpos₁ (hderiv _ hpos₁) + have hd₂ := P.hasDerivAt_bellman ha.1 ha.2 (P.concaveSlices_bellman hv z₂) + (P.policyOf_lt_maxSaving hpc hv hamem (hslack a hamem z₂)) hpos₂ (hderiv _ hpos₂) + refine ⟨_, ?_, hd₁.sub hd₂⟩ + have hc : P.consumptionFnOf v z₁ a ≤ P.consumptionFnOf v z₂ a := + P.consumptionFnOf_le_across_states hv + (fun x y hx hy hxy => P.contOf_sub_le_across_states hmono hid hz hx hy hxy) hamem hamem + (by simp only [resources]; linarith) + have hdu := hanti (mem_Ioi.mpr hpos₁) (mem_Ioi.mpr hpos₂) hc + nlinarith [hR] + have hmonoOn : MonotoneOn + (fun t => (P.toExtended.bellman v) (t, z₁) - (P.toExtended.bellman v) (t, z₂)) + (Icc assetFloor assetCap) := by + refine monotoneOn_of_deriv_nonneg (convex_Icc _ _) ?_ ?_ ?_ + · exact (((P.toExtended.bellman v).continuous.comp + (continuous_id.prodMk continuous_const)).sub ((P.toExtended.bellman v).continuous.comp + (continuous_id.prodMk continuous_const))).continuousOn + · rw [interior_Icc] + intro t ht + obtain ⟨D, _, hD⟩ := key t ht + exact hD.differentiableAt.differentiableWithinAt + · rw [interior_Icc] + intro t ht + obtain ⟨D, hD0, hD⟩ := key t ht + rw [hD.deriv] + exact hD0 + have := hmonoOn hx hy hxy + simp only at this + linarith + +/-! ### Along the iteration, and at the fixed point -/ + +/-- **Income-antitone increments at the value function.** -/ +theorem incDiffAcrossStates_valueFunction (hmono : P.MonotoneTransitions) {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hpc : P.PositiveConsumptionAll) + (hslack : ∀ n : ℕ, ∀ x ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (x, z) < assetCap) : + P.IncDiffAcrossStates P.toExtended.valueFunction := by + have hslices : ∀ n : ℕ, + ConcaveSlices assetFloor assetCap ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact concaveSlices_zero + | succ k ih => + rw [Function.iterate_succ_apply'] + exact P.concaveSlices_bellman ih + have hstep : ∀ n : ℕ, P.IncDiffAcrossStates ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) := by + intro n + induction n with + | zero => exact P.incDiffAcrossStates_zero + | succ k ih => + rw [Function.iterate_succ_apply'] + exact P.incDiffAcrossStates_bellman hmono hderiv hanti (hslices k) hpc (hslack k) ih + have hpt : ∀ p : ℝ × Z, + Tendsto (fun n => ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) p) atTop + (𝓝 (P.toExtended.valueFunction p)) := fun p => + (BoundedContinuousFunction.tendsto_iff_tendstoUniformly.mp + (P.toExtended.tendsto_iterate_valueFunction 0)).tendsto_at p + intro z₁ z₂ hz x y hx hy hxy + exact le_of_tendsto_of_tendsto ((hpt (y, z₂)).sub (hpt (x, z₂))) ((hpt (y, z₁)).sub (hpt (x, z₁))) + (Eventually.of_forall fun n => hstep n z₁ z₂ hz x y hx hy hxy) + +/-- **Consumption is lowest where income is lowest**, for a persistent income process with +monotone transitions. The replacement for `consumptionFn_le_of_income_le`. -/ +theorem consumptionFn_le_of_income_le_monotone (hmono : P.MonotoneTransitions) {du : ℝ → ℝ} + (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hpc : P.PositiveConsumptionAll) + (hslack : ∀ n : ℕ, ∀ x ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (x, z) < assetCap) + {z₁ z₂ : Z} (hinc : P.income z₁ ≤ P.income z₂) {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) : + P.consumptionFn z₁ a ≤ P.consumptionFn z₂ a := + P.consumptionFnOf_le_across_states (v := P.toExtended.valueFunction) P.concaveSlices_valueFunction + (fun x y hx hy hxy => P.contOf_sub_le_across_states hmono + (P.incDiffAcrossStates_valueFunction hmono hderiv hanti hpc hslack) hinc hx hy hxy) + ha ha (by simp only [resources]; linarith) + +/-- **Worst-state decline with persistent income.** Assets strictly decline at the lowest +income state, at every asset level above the borrowing constraint, given `β(1+r) < 1` and +monotone transitions. -/ +theorem policy_lt_self_of_impatient_monotone + (hβR : (P.discount : ℝ) * (1 + P.interest) < 1) (hmono : P.MonotoneTransitions) + {du : ℝ → ℝ} (hderiv : ∀ c : ℝ, 0 < c → HasDerivAt P.u (du c) c) + (hanti : AntitoneOn du (Ioi (0 : ℝ))) (hdupos : ∀ c : ℝ, 0 < c → 0 < du c) + (hpc : P.PositiveConsumptionAll) + (hslackIt : ∀ n : ℕ, ∀ x ∈ Icc assetFloor assetCap, ∀ z : Z, + P.policyOf ((P.toExtended.bellman)^[n] (0 : (ℝ × Z) →ᵇ ℝ)) (x, z) < assetCap) + (hslack : ∀ (z : Z), ∀ x ∈ Icc assetFloor assetCap, P.policy (x, z) < P.maxSaving (x, z)) + {z₀ : Z} (hz₀ : ∀ z : Z, P.income z₀ ≤ P.income z) + {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (ha0 : assetFloor < a) : + P.policy (a, z₀) < a := + P.policy_lt_self_of_impatient hβR hderiv hanti hdupos + (fun z x hx => hpc _ P.concaveSlices_valueFunction z x hx) hslack ha ha0 + (fun z' => P.consumptionFn_le_of_income_le_monotone hmono hderiv hanti hpc hslackIt (hz₀ z') ha) + +/-! ### Two states -/ + +/-- **Huggett's condition is monotone transitions**, for two income states ordered by income: +`π(h | h) ≥ π(h | l)`. -/ +theorem monotoneTransitions_of_two_states {assetFloor assetCap : ℝ} + (P : IncomeFluctuation (Fin 2) assetFloor assetCap) + (hinc : P.income 0 ≤ P.income 1) + (hπ : P.transitionMatrix 0 1 ≤ P.transitionMatrix 1 1) : P.MonotoneTransitions := by + intro z₁ z₂ hz f hf + have hrow : ∀ z : Fin 2, P.transitionMatrix z 0 = 1 - P.transitionMatrix z 1 := by + intro z + have := P.transitionMatrix_sum z + simp only [Fin.sum_univ_two] at this + linarith + have hsum : ∀ z : Fin 2, ∑ z' : Fin 2, P.transitionMatrix z z' * f z' + = f 0 + P.transitionMatrix z 1 * (f 1 - f 0) := by + intro z + simp only [Fin.sum_univ_two, hrow z] + ring + rw [hsum, hsum] + have hf10 : f 1 ≤ f 0 := hf 0 1 hinc + have h₁ : z₁ = 0 ∨ z₁ = 1 := by fin_cases z₁ <;> simp + have h₂ : z₂ = 0 ∨ z₂ = 1 := by fin_cases z₂ <;> simp + rcases h₁ with rfl | rfl <;> rcases h₂ with rfl | rfl + · exact le_rfl + · nlinarith + · -- `income 1 ≤ income 0` forces the incomes equal, hence `f 0 = f 1` + have hf01 : f 0 ≤ f 1 := hf 1 0 hz + nlinarith + · exact le_rfl + +end IncomeFluctuation + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/NaturalAssetBound.lean b/LeanPool/AiyagariEquilibrium/Models/NaturalAssetBound.lean new file mode 100644 index 000000000..837779844 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/NaturalAssetBound.lean @@ -0,0 +1,354 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Models.ConsumptionFloor +public import LeanPool.AiyagariEquilibrium.Models.IncomeFluctuationLipschitz + +/-! +# A natural asset bound for log utility + +`exists_decline_of_consumption_lower_bound` turns a LINEAR lower bound on consumption into the +statement that assets above an explicit threshold decline — the natural asset bound, which is +what `assetCap` has been standing in for, and what +`not_concaveOn_consumptionFn_of_cap_binds` says has to hold before the Carroll–Kimball +hypothesis is even true. Its input, the linear bound, has been missing. This file supplies it +for log utility. + +## The argument is the crudest possible deviation + +Saving nothing is always feasible. Comparing the optimum with it, + + `u (resources) + β · cont 0 ≤ u (consumption) + β · cont (policy)`, + +so `u resources - u consumption ≤ β (cont (policy) - cont 0) ≤ 2 β ‖V‖`. For LOG utility that +reads `log (resources / consumption) ≤ 2 β ‖V‖`, which is a linear bound outright: + + `consumption ≥ exp (-2 β ‖V‖) · resources`. + +## Log and the other CRRA regimes + +For log utility, `u resources` grows without limit and the deviation gives the proportional +consumption bound above. In the unbounded-domain CRRA branch with `γ > 1`, utility is bounded +above, so this comparison gives a constant consumption floor rather than that linear bound. +The separate bounded-domain treatment also permits `0 < γ < 1`; `crra_pinch` does not exclude +that regime from the shared household structure. + +## What is honest about the constant + +`exp (-2 β ‖V‖)` is very lossy. The sharp constant is Ma and Toda's asymptotic MPC +`c_bar = 1 - (β R^(1-γ))^(1/γ)`, at which the decline condition becomes exactly `β R < 1` +(`one_sub_mpc_mul_of_asymptotic`). Getting it needs the Euler equation, hence the envelope +condition, hence the machinery that `IncomeFluctuationEnvelope` supplies only under side +conditions. So the bound proved here is qualitatively right and quantitatively weak: it +establishes a natural asset bound, but only for interest rates small relative to `exp (-2 β ‖V‖)`, +where the sharp argument would ask only for impatience. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +namespace IncomeFluctuation + +variable {Z : Type*} [Fintype Z] [Nonempty Z] [TopologicalSpace Z] [DiscreteTopology Z] +variable {assetFloor assetCap : ℝ} +variable (P : IncomeFluctuation Z assetFloor assetCap) + +/-- The slack in the "save nothing" comparison: twice the size of the value function, discounted. -/ +noncomputable def deviationGap : ℝ := 2 * P.discount * ‖P.toExtended.valueFunction‖ + +theorem deviationGap_nonneg : 0 ≤ P.deviationGap := by + have : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have := norm_nonneg P.toExtended.valueFunction + simp only [deviationGap] + positivity + +/-- **The optimum beats saving nothing, so utility cannot fall far short of the utility of +eating everything.** This holds for any utility function; it is the log case that turns it into +a linear bound. -/ +theorem utility_resources_sub_le {a : ℝ} (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + P.u (P.resources (a, z) - assetFloor) - P.u (P.consumptionFn z a) ≤ P.deviationGap := by + have hmem0 : assetFloor ∈ P.toExtended.feasible (a, z) := ⟨le_rfl, le_max_left _ _⟩ + have hc0 : 0 < P.consumption (a, z) assetFloor := by + have h1 := P.minConsumption_le_consumption_floor (a, z) + have h2 := P.minConsumption_pos + simp only [consumption]; linarith + have hopt := P.objR_le_of_mem ha hmem0 (P.mem_dom_of_pos hc0) + simp only [objR, consumption] at hopt + have hb1 := abs_le.mp (P.abs_cont_le z (P.policy (a, z))) + have hb2 := abs_le.mp (P.abs_cont_le z assetFloor) + have hβ : (0 : ℝ) ≤ P.discount := P.discount.coe_nonneg + have hgap : (P.discount : ℝ) * (P.cont z (P.policy (a, z)) - P.cont z assetFloor) + ≤ P.deviationGap := by + simp only [deviationGap] + nlinarith [hb1.2, hb2.1, hβ] + simp only [consumptionFn, consumption] + nlinarith [hopt, hgap] + +/-- **A linear lower bound on consumption, for log utility.** The one place the development gets +a bound that scales with resources rather than being a constant. -/ +theorem log_consumption_linear_lower_bound (hpc : P.PositiveConsumption) (hu : P.u = Real.log) + {a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + Real.exp (-P.deviationGap) * (P.resources (a, z) - assetFloor) ≤ P.consumptionFn z a := by + have hm : 0 < P.resources (a, z) - assetFloor := by + have h1 := P.minConsumption_le_consumption_floor (a, z) + have h2 := P.minConsumption_pos + linarith + have hc : 0 < P.consumptionFn z a := P.consumptionFn_pos hpc ha z + have hkey := P.utility_resources_sub_le ha z + rw [hu] at hkey + have hdiv : Real.log ((P.resources (a, z) - assetFloor) / P.consumptionFn z a) + ≤ P.deviationGap := by + rw [Real.log_div hm.ne' hc.ne'] + exact hkey + have hle : (P.resources (a, z) - assetFloor) / P.consumptionFn z a ≤ Real.exp P.deviationGap := + (Real.log_le_iff_le_exp (by positivity)).mp hdiv + rw [div_le_iff₀ hc] at hle + rw [Real.exp_neg] + rw [inv_mul_le_iff₀ (Real.exp_pos _)] + linarith + +/-- **The natural asset bound for log utility.** Assets above an explicit threshold decline, so +the household never accumulates without limit and the asset cap can be justified rather than +imposed. + +The hypothesis on the interest rate is the price of the crude constant: the sharp argument would +ask only for `β (1 + r) < 1`. -/ +theorem exists_natural_asset_bound_log (hpc : P.PositiveConsumption) (hu : P.u = Real.log) + (z : Z) + (hr : (1 - Real.exp (-P.deviationGap)) * (1 + P.interest) < 1) : + ∃ ā : ℝ, ∀ a ∈ Icc assetFloor assetCap, ā < a → P.policy (a, z) < a := + P.exists_decline_of_consumption_lower_bound hr + fun _ ha => P.log_consumption_linear_lower_bound hpc hu ha z + +/-! ### A better constant, from a smaller deviation + +`log_consumption_linear_lower_bound` compares the optimum with saving NOTHING, which is a huge +deviation and costs an exponential: `ε = exp (-2 β ‖V‖)`. Comparing instead with saving a +FRACTION `θ` of the optimum costs only a ratio, and letting `θ` approach one turns the bound +polynomial: + + `consumption ≥ resources / (1 + 2 · 2 β ‖V‖)`. + +No derivative is taken. The reason a near-zero deviation is affordable is that BOTH sides shrink +with `1 - θ`: the utility gain is `log (1 + (1-θ) b / c)`, and the continuation loss is bounded by +`2‖V‖ (1-θ) / θ` because concavity makes the slope of `cont` on `[θb, b]` no larger than its slope +on `[0, θb]`. The factor `(1-θ)` cancels, and what survives is the comparison of the two +constants. + +`θ = (G + 1/2) / (1 + G)` is chosen so that `θ (1 + G) - G = 1/2` exactly, which is what makes the +final inequality `b ≤ 2 G c`. + +The gain is small when `‖V‖` is small and large when it is not: at `G = 1.3` the two constants are +`0.27` and `0.28`, at `G = 5` they are `0.007` and `0.09`. Since `‖V‖` grows like `|log minIncome|`, +it is the dispersed calibrations — the ones the precautionary motive needs — where this matters. +-/ + +/-- `log (c + d) - log c ≥ d / (c + d)`, the elementary inequality behind the bound. -/ +theorem log_sub_log_ge {c d : ℝ} (hc : 0 < c) (hd : 0 ≤ d) : + d / (c + d) ≤ Real.log (c + d) - Real.log c := by + have hcd : 0 < c + d := by linarith + have h := Real.log_le_sub_one_of_pos (x := c / (c + d)) (by positivity) + rw [Real.log_div hc.ne' hcd.ne'] at h + have he : c / (c + d) - 1 = -(d / (c + d)) := by field_simp; ring + rw [he] at h + linarith + +/-- The arithmetic behind `b ≤ 2 G c`, isolated so the search stays small. -/ +private theorem two_gap_cancel {B G c θ : ℝ} (hθkey : θ * (1 + G) - G = 1 / 2) + (hcancel : θ * B ≤ G * (c + (1 - θ) * B)) : B ≤ 2 * G * c := by + have h : B * (θ * (1 + G) - G) = B * (1 / 2) := by rw [hθkey] + linarith [hcancel, h] + +/-- **A polynomial linear lower bound on consumption.** Sharper than +`log_consumption_linear_lower_bound` whenever the value function is large, and proved from a +deviation to `θ` times the optimal saving rather than to zero. -/ +theorem log_consumption_linear_lower_bound' (hpc : P.PositiveConsumption) (hu : P.u = Real.log) + {a : ℝ} + (ha : a ∈ Icc assetFloor assetCap) (z : Z) : + (P.resources (a, z) - assetFloor) / (1 + 2 * P.deviationGap) ≤ P.consumptionFn z a := by + have hres0 : P.resources (a, z) = P.consumptionFn z a + P.policy (a, z) := by + simp only [consumptionFn, consumption]; ring + have hc0 : 0 < P.consumptionFn z a := P.consumptionFn_pos hpc ha z + set G : ℝ := P.deviationGap with hGdef + have hG : 0 ≤ G := P.deviationGap_nonneg + set b : ℝ := P.policy (a, z) with hbdef + set c : ℝ := P.consumptionFn z a with hcdef + have hc : 0 < c := hc0 + have hres : P.resources (a, z) = c + b := hres0 + have hb2 : b - assetFloor ≤ 2 * G * c := by + rcases eq_or_lt_of_le (P.policy_mem_region (a, z)).1 with hzero | hbpos + · rw [← hbdef] at hzero + rw [← hzero, sub_self] + exact mul_nonneg (by linarith) hc.le + rw [← hbdef] at hbpos + have hbf : (0 : ℝ) < b - assetFloor := by linarith + set θ : ℝ := (G + 1 / 2) / (1 + G) with hθdef + have hden : (0 : ℝ) < 1 + G := by linarith + have hθ0 : 0 < θ := by rw [hθdef]; positivity + have hθ1 : θ < 1 := by rw [hθdef, div_lt_one hden]; linarith + have h1θ : (0 : ℝ) < 1 - θ := by linarith + have hθkey : θ * (1 + G) - G = 1 / 2 := by rw [hθdef]; field_simp; ring + have hbreg : b ∈ Icc assetFloor assetCap := P.policy_mem_region _ + have hmem0 : assetFloor ∈ Icc assetFloor assetCap := ⟨le_rfl, P.assetFloor_le_assetCap⟩ + -- deviate from `b` a fraction `1-θ` of the way back to the borrowing limit + set d : ℝ := assetFloor + θ * (b - assetFloor) with hddef + have hθb0 : assetFloor < d := by rw [hddef]; nlinarith + have hθbb : d < b := by rw [hddef]; nlinarith + have hfeas : d ∈ P.toExtended.feasible (a, z) := by + rw [P.feasible_eq] + exact ⟨by linarith, by linarith [(P.policy_mem (a, z)).2]⟩ + have hcons : P.consumption (a, z) d = c + (1 - θ) * (b - assetFloor) := by + simp only [consumption, hddef] + rw [hres] + ring + have hdpos : (0 : ℝ) ≤ (1 - θ) * (b - assetFloor) := mul_nonneg h1θ.le hbf.le + have hcpos : 0 < P.consumption (a, z) d := by rw [hcons]; linarith + have hopt := P.objR_le_of_mem ha hfeas (P.mem_dom_of_pos hcpos) + simp only [objR, hcons, hu] at hopt + rw [show P.consumption (a, z) (P.policy (a, z)) = c from rfl, + show P.policy (a, z) = b from rfl] at hopt + -- concavity bounds the continuation loss + have hslope := (P.concaveOn_cont z).slope_anti_adjacent hmem0 hbreg hθb0 hθbb + have hb1 := abs_le.mp (P.abs_cont_le z d) + have hb0 := abs_le.mp (P.abs_cont_le z assetFloor) + have hβ : (0 : ℝ) ≤ (P.discount : ℝ) := P.discount.coe_nonneg + have hd1 : d - assetFloor = θ * (b - assetFloor) := by rw [hddef]; ring + have hd2 : b - d = (1 - θ) * (b - assetFloor) := by rw [hddef]; ring + have hloss : P.cont z b - P.cont z d + ≤ 2 * ‖P.toExtended.valueFunction‖ * (1 - θ) / θ := by + rw [div_le_div_iff₀ (by linarith) (by linarith)] at hslope + rw [hd1, hd2] at hslope + rw [le_div_iff₀ hθ0] + have hbound : P.cont z d - P.cont z assetFloor ≤ + 2 * ‖P.toExtended.valueFunction‖ := by linarith only [hb1.2, hb0.1] + have hkey : (P.cont z b - P.cont z d) * θ * (b - assetFloor) + ≤ 2 * ‖P.toExtended.valueFunction‖ * (1 - θ) * (b - assetFloor) := by + calc + _ = (P.cont z b - P.cont z d) * (θ * (b - assetFloor)) := by ring + _ ≤ (P.cont z d - P.cont z assetFloor) * ((1 - θ) * (b - assetFloor)) := hslope + _ ≤ 2 * ‖P.toExtended.valueFunction‖ * ((1 - θ) * (b - assetFloor)) := + mul_le_mul_of_nonneg_right hbound (mul_nonneg h1θ.le hbf.le) + _ = _ := by ring + exact le_of_mul_le_mul_right hkey hbf + have heq : (P.discount : ℝ) * (2 * ‖P.toExtended.valueFunction‖ * (1 - θ) / θ) + = G * (1 - θ) / θ := by + rw [hGdef, IncomeFluctuation.deviationGap]; ring + have hβloss : (P.discount : ℝ) * (P.cont z b - P.cont z d) ≤ G * (1 - θ) / θ := by + have hmul := mul_le_mul_of_nonneg_left hloss hβ + linarith [hmul, heq.le, heq.ge] + -- the utility gain + have hgain := log_sub_log_ge hc hdpos + have hdist : (P.discount : ℝ) * (P.cont z b - P.cont z d) + = (P.discount : ℝ) * P.cont z b - (P.discount : ℝ) * P.cont z d := by ring + have hchain : (1 - θ) * (b - assetFloor) / (c + (1 - θ) * (b - assetFloor)) + ≤ G * (1 - θ) / θ := by + linarith [hopt, hβloss, hgain, hdist.le, hdist.ge] + rw [div_le_div_iff₀ (by linarith) hθ0] at hchain + have hcancel : θ * (b - assetFloor) ≤ G * (c + (1 - θ) * (b - assetFloor)) := by + refine le_of_mul_le_mul_left ?_ h1θ + convert hchain using 1 <;> ring + exact two_gap_cancel hθkey hcancel + rw [hres, div_le_iff₀ (by linarith)] + linarith [hb2] + +/-! ### The other half of exhaustion: the corner, for log + +`exists_decline_of_consumption_lower_bound` gives assets falling above a threshold. The ergodic +argument also needs the constraint to BIND below one — `policy = 0` on `[0, a₀]` at the bad income +state — and `policy_eq_zero_of_corner_at` supplies that from two explicit constants: a Lipschitz +bound `L` on the value function and a lower bound `m` on the secant slope of `u`, with `β L < m`. + +Both are computable for log. The secant slope of `log` on `(0, R]` is at least `1/R`, from +`log x ≤ x - 1`. The Lipschitz constant is the fixed point of the operator's own estimate, +`L = K (1+r) / (1 - β(1+r))` with `K = slopeBound log minIncome = 2 log 2 / minIncome`, so `β L < m` +reads + + `resources · β · 2 log 2 (1+r) / (minIncome (1 - β(1+r))) < 1`, + +a condition on the state's resources alone. Since resources at the bad state are +`minIncome + (1+r) a`, it cuts out an interval of low assets — exactly what the ergodic argument +wants, and exactly why the condition has to be state-dependent rather than global. -/ + +/-- The secant slope of `log` on `(0, R]` is at least `1 / R`. -/ +theorem log_marginal_bound {R : ℝ} (hR : 0 < R) {c d : ℝ} (hd : 0 < d) (hdc : d ≤ c) + (hcR : c ≤ R) : 1 / R * (c - d) ≤ Real.log c - Real.log d := by + have hc : 0 < c := lt_of_lt_of_le hd hdc + have hkey : Real.log (d / c) ≤ d / c - 1 := + Real.log_le_sub_one_of_pos (div_pos hd hc) + rw [Real.log_div hd.ne' hc.ne'] at hkey + have hstep : (c - d) / c ≤ Real.log c - Real.log d := by + rw [sub_div, div_self hc.ne'] + linarith + refine le_trans ?_ hstep + rw [div_mul_eq_mul_div, one_mul, div_le_div_iff₀ hR hc] + nlinarith [sub_nonneg.mpr hdc] + +/-- The slope bound of `log` at the income floor, in closed form. -/ +theorem log_slopeBoundU (hu : P.u = Real.log) : + P.slopeBoundU = 2 * Real.log 2 / P.minConsumption := by + have hm := P.minConsumption_pos + simp only [slopeBoundU, slopeBound, hu] + rw [← Real.log_div hm.ne' (by positivity), show P.minConsumption / (P.minConsumption / 2) = 2 by + field_simp] + field_simp + +/-- The Lipschitz constant the operator's own estimate is a fixed point of. -/ +noncomputable def logLipschitz : ℝ := + 2 * Real.log 2 / P.minConsumption * (1 + P.interest) / (1 - P.discount * (1 + P.interest)) + +theorem logLipschitz_nonneg (hβR : P.discount * (1 + P.interest) < 1) : + 0 ≤ P.logLipschitz := by + have hm := P.minConsumption_pos + have hr := P.interest_gt_neg_one + have hlog : (0 : ℝ) ≤ Real.log 2 := Real.log_nonneg (by norm_num) + refine div_nonneg (by positivity) (by linarith) + +/-- **The value function is Lipschitz with the explicit log constant.** -/ +theorem log_valueFunction_lipschitz (hu : P.u = Real.log) + (hβR : P.discount * (1 + P.interest) < 1) : + ∀ z : Z, ∀ x ∈ Icc assetFloor assetCap, ∀ y ∈ Icc assetFloor assetCap, + |P.toExtended.valueFunction (x, z) - P.toExtended.valueFunction (y, z)| + ≤ P.logLipschitz * |x - y| := by + refine P.valueFunction_lipschitz (P.logLipschitz_nonneg hβR) (le_of_eq ?_) + have hne : (1 : ℝ) - P.discount * (1 + P.interest) ≠ 0 := by linarith + simp only [logLipschitz, P.log_slopeBoundU hu] + field_simp + ring + +/-- **The borrowing constraint binds where resources are small**, with an explicit threshold in +the primitives. -/ +theorem log_policy_eq_zero_of_resources (hdom : P.Unbounded) (hu : P.u = Real.log) + (hβR : P.discount * (1 + P.interest) < 1) {s : ℝ × Z} (hs : s.1 ∈ Icc assetFloor assetCap) + (hlt : P.discount * P.logLipschitz < 1 / (P.resources s - assetFloor)) : + P.policy s = assetFloor := + P.policy_eq_zero_of_corner_at (P.log_valueFunction_lipschitz hu hβR) hs + (fun c d hd hdc hc => by + rw [hu] + refine log_marginal_bound ?_ (by rw [hdom] at hd; exact hd) hdc hc + have h1 := P.minConsumption_le_consumption_floor s + have h2 := P.minConsumption_pos + linarith) + hlt + +end IncomeFluctuation + +/-- **The bound is not vacuous.** At `r = 0` the interest-rate hypothesis holds automatically, +since `exp` is positive, so the log witness has a natural asset bound outright. -/ +theorem logImpatient_natural_asset_bound (z : Fin 2) : + ∃ ā : ℝ, ∀ a ∈ Icc (0 : ℝ) 1, ā < a → logImpatient.policy (a, z) < a := by + refine logImpatient.exists_natural_asset_bound_log + (logImpatient.positiveConsumption_of_unbounded rfl) logImpatient_u z ?_ + have hpos := Real.exp_pos (-logImpatient.deviationGap) + have hint : logImpatient.interest = 0 := rfl + rw [hint] + linarith + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Models/Tauchen.lean b/LeanPool/AiyagariEquilibrium/Models/Tauchen.lean new file mode 100644 index 000000000..a790ce3a5 --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Models/Tauchen.lean @@ -0,0 +1,199 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Probability.CDF +public import Mathlib.Probability.Distributions.Gaussian.Real +public import LeanPool.AiyagariEquilibrium.Models.MonotoneTransitions + +/-! +# Tauchen (1986) chains have monotone transitions + +Tauchen's discretisation of `z' = ρ z + σ ε`, `ε ~ N(0,1)`, on the grid `lo, lo + w, …, lo + n w` +puts on state `j` the normal mass of the interval of half-width `w/2` about `lo + j w`, with the +end states taking the tails. Written through the row's cumulative mass on states below `k`, + + `F_i(k) = Φ((lo + k w - w/2 - ρ(lo + i w)) / σ)` for `0 < k ≤ n`, `F_i(0) = 0`, `F_i(n+1) = 1`, + +the entry is `F_i(j+1) - F_i(j)`. Everything about the chain is then one fact about `Φ`: it is +monotone. Entries are non-negative and rows sum to one because `F_i` rises from `0` to `1` in +`k`; the rows are ordered by first-order stochastic dominance because `F_i(k)` FALLS in `i` when +`ρ ≥ 0`; and the lowest state is reachable from everywhere because `Φ > 0`. + +There is nothing special about seven states. Aiyagari (1994) used seven; the results here are for +`n + 1` states, any `n`. + +## First-order stochastic dominance on a finite chain + +`sum_mul_le_of_cum_le` is the finite FOSD lemma: if `q` puts no more mass than `p` on every +initial segment, then `q` gives the smaller expectation to every antitone `f`. Summation by parts +(`Finset.sum_range_by_parts`) does it in one line. +-/ + +@[expose] public section + +open Set Finset MeasureTheory ProbabilityTheory + +namespace LeanEconomics + +/-! ### The standard normal cdf -/ + +/-- The standard normal distribution function `Φ`. -/ +noncomputable def normalCdf : ℝ → ℝ := cdf (gaussianReal 0 1) + +theorem normalCdf_mono : Monotone normalCdf := monotone_cdf _ + +theorem normalCdf_nonneg (x : ℝ) : 0 ≤ normalCdf x := cdf_nonneg _ x + +theorem normalCdf_le_one (x : ℝ) : normalCdf x ≤ 1 := cdf_le_one _ x + +/-- `Φ > 0` everywhere: the normal density is positive and `Iic x` has positive Lebesgue +measure. -/ +theorem normalCdf_pos (x : ℝ) : 0 < normalCdf x := by + have h1 : (1 : NNReal) ≠ 0 := one_ne_zero + have hint : 0 < ∫ y in Iic x, gaussianPDFReal 0 1 y := by + rw [setIntegral_pos_iff_support_of_nonneg_ae + (Filter.Eventually.of_forall fun y => gaussianPDFReal_nonneg 0 1 y) + (integrable_gaussianPDFReal 0 1).integrableOn] + have hsupp : Function.support (gaussianPDFReal 0 1) = univ := by + ext y; simp [Function.mem_support, (gaussianPDFReal_pos 0 1 y h1).ne'] + rw [hsupp, univ_inter, Real.volume_Iic] + exact ENNReal.zero_lt_top + unfold normalCdf + rw [cdf_eq_real, Measure.real, gaussianReal_apply_eq_integral 0 h1, ENNReal.toReal_ofReal hint.le] + exact hint + +/-! ### The finite FOSD lemma -/ + +/-- **First-order stochastic dominance on `{0, …, n}`.** Two probability vectors given through +cumulative functions `F` and `G` (`p j = F (j+1) - F j`, `F 0 = 0`, `F (n+1) = 1`, likewise `G`), +with `G ≤ F` pointwise — `q` puts no more mass on any initial segment — give, for every antitone +`f`, the inequality `∑ q f ≤ ∑ p f`. -/ +theorem sum_mul_le_of_cum_le {n : ℕ} {p q : Fin (n + 1) → ℝ} {F G : ℕ → ℝ} + (hp : ∀ j : Fin (n + 1), p j = F (j.val + 1) - F j.val) + (hq : ∀ j : Fin (n + 1), q j = G (j.val + 1) - G j.val) + (hF0 : F 0 = 0) (hG0 : G 0 = 0) (hFn : F (n + 1) = 1) (hGn : G (n + 1) = 1) + (hle : ∀ k, G k ≤ F k) {f : Fin (n + 1) → ℝ} (hf : ∀ j j' : Fin (n + 1), j ≤ j' → f j' ≤ f j) : + ∑ j, q j * f j ≤ ∑ j, p j * f j := by + -- extend `f` to `ℕ` + set fe : ℕ → ℝ := fun k => if h : k < n + 1 then f ⟨k, h⟩ else 0 with hfe + have hfe_eq : ∀ j : Fin (n + 1), fe j.val = f j := by + intro j; simp only [hfe, j.isLt, ↓reduceDIte] + have hfe_anti : ∀ k, k < n → fe (k + 1) ≤ fe k := by + intro k hk + have h1 : k < n + 1 := by omega + have h2 : k + 1 < n + 1 := by omega + simp only [hfe, h1, h2, ↓reduceDIte] + exact hf ⟨k, h1⟩ ⟨k + 1, h2⟩ (Fin.mk_le_mk.mpr (Nat.le_succ k)) + -- the two sums as range sums, then summation by parts + have key : ∀ (H : ℕ → ℝ) (r : Fin (n + 1) → ℝ), + (∀ j : Fin (n + 1), r j = H (j.val + 1) - H j.val) → H 0 = 0 → H (n + 1) = 1 → + ∑ j, r j * f j = fe n - ∑ i ∈ range n, (fe (i + 1) - fe i) * H (i + 1) := by + intro H r hr hH0 hHn + have h1 : ∑ j, r j * f j = ∑ i ∈ range (n + 1), fe i • (H (i + 1) - H i) := by + rw [← Fin.sum_univ_eq_sum_range (fun i => fe i • (H (i + 1) - H i)) (n + 1)] + refine Finset.sum_congr rfl fun j _ => ?_ + rw [hr j, hfe_eq j, smul_eq_mul, mul_comm] + rw [h1, Finset.sum_range_by_parts fe (fun i => H (i + 1) - H i) (n + 1)] + simp only [Nat.add_sub_cancel, Finset.sum_range_sub, hH0, hHn, sub_zero, smul_eq_mul, mul_one] + rw [key F p hp hF0 hFn, key G q hq hG0 hGn] + have hterm : ∀ i ∈ range n, + (fe (i + 1) - fe i) * F (i + 1) ≤ (fe (i + 1) - fe i) * G (i + 1) := by + intro i hi + have hi' : i < n := Finset.mem_range.mp hi + exact mul_le_mul_of_nonpos_left (hle (i + 1)) (by linarith [hfe_anti i hi']) + linarith [Finset.sum_le_sum hterm] + +/-! ### The chain -/ + +/-- **Tauchen's cumulative row**: the mass row `i` puts on states below `k`. -/ +noncomputable def tauchenCum (ρ σ lo w : ℝ) (n : ℕ) (i : Fin (n + 1)) (k : ℕ) : ℝ := + if k = 0 then 0 else if n + 1 ≤ k then 1 + else normalCdf ((lo + k * w - w / 2 - ρ * (lo + i * w)) / σ) + +/-- **Tauchen's transition matrix** on `n + 1` states. -/ +noncomputable def tauchen (ρ σ lo w : ℝ) (n : ℕ) (i j : Fin (n + 1)) : ℝ := + tauchenCum ρ σ lo w n i (j.val + 1) - tauchenCum ρ σ lo w n i j.val + +variable {ρ σ lo w : ℝ} {n : ℕ} + +theorem tauchenCum_zero (i : Fin (n + 1)) : tauchenCum ρ σ lo w n i 0 = 0 := by + simp [tauchenCum] + +theorem tauchenCum_top (i : Fin (n + 1)) : tauchenCum ρ σ lo w n i (n + 1) = 1 := by + simp [tauchenCum] + +/-- The cumulative row rises in `k`. -/ +theorem tauchenCum_mono (hσ : 0 < σ) (hw : 0 < w) (i : Fin (n + 1)) : + Monotone (tauchenCum ρ σ lo w n i) := by + intro k k' hkk' + unfold tauchenCum + by_cases hk : k = 0 + · subst hk + simp only [ite_true] + split_ifs <;> first | exact le_rfl | exact zero_le_one | exact normalCdf_nonneg _ + · have hk' : k' ≠ 0 := by omega + simp only [hk, hk', ite_false] + by_cases htop : n + 1 ≤ k + · have htop' : n + 1 ≤ k' := le_trans htop hkk' + simp [htop, htop'] + · simp only [htop, ite_false] + split_ifs with htop' + · exact normalCdf_le_one _ + · apply normalCdf_mono + apply div_le_div_of_nonneg_right _ hσ.le + have : (k : ℝ) ≤ k' := by exact_mod_cast hkk' + nlinarith + +/-- The cumulative row falls in the state when `ρ ≥ 0`: first-order stochastic dominance. -/ +theorem tauchenCum_anti (hρ : 0 ≤ ρ) (hσ : 0 < σ) (hw : 0 < w) {i i' : Fin (n + 1)} (hii' : i ≤ i') + (k : ℕ) : tauchenCum ρ σ lo w n i' k ≤ tauchenCum ρ σ lo w n i k := by + unfold tauchenCum + split_ifs + · exact le_rfl + · exact le_rfl + · apply normalCdf_mono + apply div_le_div_of_nonneg_right _ hσ.le + have : (i : ℝ) ≤ i' := by exact_mod_cast hii' + nlinarith [mul_nonneg hρ hw.le] + +theorem tauchen_nonneg (hσ : 0 < σ) (hw : 0 < w) (i j : Fin (n + 1)) : + 0 ≤ tauchen ρ σ lo w n i j := + sub_nonneg.mpr (tauchenCum_mono hσ hw i (Nat.le_succ _)) + +theorem tauchen_sum (i : Fin (n + 1)) : ∑ j, tauchen ρ σ lo w n i j = 1 := by + unfold tauchen + rw [Fin.sum_univ_eq_sum_range + (fun k => tauchenCum ρ σ lo w n i (k + 1) - tauchenCum ρ σ lo w n i k) (n + 1), + Finset.sum_range_sub, tauchenCum_top, tauchenCum_zero, sub_zero] + +/-- The lowest state is reached from everywhere in one step. -/ +theorem tauchen_pos_zero (i : Fin (n + 1)) : 0 < tauchen ρ σ lo w n i 0 := by + unfold tauchen + simp only [Fin.val_zero, zero_add] + rw [tauchenCum_zero, sub_zero] + unfold tauchenCum + simp only [one_ne_zero, ite_false] + split_ifs + · exact one_pos + · exact normalCdf_pos _ + +/-- **A Tauchen chain with `ρ ≥ 0` has monotone transitions**, for any economy whose income is +strictly increasing in the state. -/ +theorem monotoneTransitions_of_tauchen {assetFloor assetCap : ℝ} + (P : IncomeFluctuation.{0} (Fin (n + 1)) assetFloor assetCap) + (hπ : P.transitionMatrix = tauchen ρ σ lo w n) (hinc : StrictMono P.income) + (hρ : 0 ≤ ρ) (hσ : 0 < σ) (hw : 0 < w) : P.MonotoneTransitions := by + intro z₁ z₂ hz f hf + rw [hπ] + have hz' : z₁ ≤ z₂ := hinc.le_iff_le.mp hz + refine sum_mul_le_of_cum_le (F := tauchenCum ρ σ lo w n z₁) (G := tauchenCum ρ σ lo w n z₂) + (fun j => rfl) (fun j => rfl) (tauchenCum_zero _) (tauchenCum_zero _) (tauchenCum_top _) + (tauchenCum_top _) (fun k => tauchenCum_anti hρ hσ hw hz' k) ?_ + intro j j' hjj' + exact hf j j' (hinc.monotone hjj') + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Topology/Berge.lean b/LeanPool/AiyagariEquilibrium/Topology/Berge.lean new file mode 100644 index 000000000..85c26ef6c --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Topology/Berge.lean @@ -0,0 +1,272 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Topology.Semicontinuity.Hemicontinuity +public import Mathlib.Topology.Instances.Real.Lemmas + +/-! +# Berge's maximum theorem + +Consider maximising a continuous objective `f x y` over a constraint set `Γ x` that moves +with the parameter `x`. Berge's maximum theorem says that if `Γ` is continuous -- upper and +lower hemicontinuous -- with nonempty compact values, then the value of the problem varies +continuously with `x`, and the set of maximisers varies upper hemicontinuously. + +The two halves need the two halves of the continuity of `Γ`, and it is worth keeping track +of which: upper hemicontinuity gives upper semicontinuity of the value, lower +hemicontinuity gives lower semicontinuity of it. Neither alone suffices. + +Mathlib supplies the hemicontinuity definitions (`UpperHemicontinuousAt`, +`LowerHemicontinuousAt`) but not this theorem. + +## Main definitions + +* `LeanEconomics.maxValue f Γ x` : the value `sSup (f x '' Γ x)` of the problem at `x`. +* `LeanEconomics.argmax f Γ x` : the set of maximisers at `x`. + +## Main results + +* `LeanEconomics.continuousAt_maxValue` : **the maximum theorem** -- the value function is + continuous. +* `LeanEconomics.upperHemicontinuousAt_argmax` : the maximiser correspondence is upper + hemicontinuous. +* `LeanEconomics.argmax_nonempty`, `LeanEconomics.isCompact_argmax` : it has nonempty + compact values. + +## A check on the hypotheses + +Berge's hypotheses are strong enough to be worth checking they can all hold at once: +`continuous_maxValue_const` instantiates them at a constraint set that does not move with +the parameter. Without such a witness the theorems above could be vacuous. + +## Implementation notes + +`eventually_forall_lt` is the engine. It says that a strict bound on `f` over `Γ x` +persists into a neighbourhood of `x`, and it is proved from the generalized tube lemma +together with upper hemicontinuity. Upper semicontinuity of the value follows at once, and +so does the hard half of upper hemicontinuity of `argmax`, applied there to the +correspondence `x ↦ Γ x ∩ Vᶜ`. Stating it without reference to a supremum is what lets it +serve the second purpose, where that correspondence may take empty values. + +## References + +* Berge, *Espaces topologiques: Fonctions multivoques*, 1959. +* Stokey, Lucas and Prescott, *Recursive Methods in Economic Dynamics*. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +variable {X Y : Type*} [TopologicalSpace X] [TopologicalSpace Y] +variable {f : X → Y → ℝ} {Γ : X → Set Y} {x : X} {c : ℝ} + +/-- The value of the problem `max f x y` subject to `y ∈ Γ x`. -/ +noncomputable def maxValue (f : X → Y → ℝ) (Γ : X → Set Y) (x : X) : ℝ := sSup (f x '' Γ x) + +/-- The set of maximisers of `f x` over `Γ x`. The codomain is any preorder, so that the +same definition serves the extended-real objectives of +`LeanEconomics.Topology.BergeEReal`. -/ +def argmax {Z : Type*} [Preorder Z] (f : X → Y → Z) (Γ : X → Set Y) (x : X) : Set Y := + {y ∈ Γ x | IsMaxOn (f x) (Γ x) y} + +/-- The objective as a function of the choice alone, with the parameter fixed. -/ +theorem continuous_fiber (hf : Continuous ↿f) (x : X) : Continuous (f x) := + hf.comp (Continuous.prodMk_right x) + +theorem bddAbove_image (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) : + BddAbove (f x '' Γ x) := + hΓc.bddAbove_image (continuous_fiber hf x).continuousOn + +theorem le_maxValue (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) {y : Y} (hy : y ∈ Γ x) : + f x y ≤ maxValue f Γ x := + le_csSup (bddAbove_image hf hΓc) ⟨y, hy, rfl⟩ + +omit [TopologicalSpace X] [TopologicalSpace Y] in +theorem maxValue_le (hΓne : (Γ x).Nonempty) (h : ∀ y ∈ Γ x, f x y ≤ c) : + maxValue f Γ x ≤ c := + csSup_le (hΓne.image _) (by rintro _ ⟨y, hy, rfl⟩; exact h y hy) + +/-- **The engine.** A strict upper bound on the objective over the constraint set survives +into a neighbourhood of the parameter. This is the generalized tube lemma combined with +upper hemicontinuity, and it makes no nonemptiness assumption, which matters because it is +applied below to a correspondence that may be empty-valued. -/ +theorem eventually_forall_lt (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) + (huhc : UpperHemicontinuousAt Γ x) (hc : ∀ y ∈ Γ x, f x y < c) : + ∀ᶠ x' in 𝓝 x, ∀ y ∈ Γ x', f x' y < c := by + have hW : IsOpen (↿f ⁻¹' Iio c) := isOpen_Iio.preimage hf + have hsub : ({x} : Set X) ×ˢ Γ x ⊆ ↿f ⁻¹' Iio c := by + rintro ⟨x', y⟩ ⟨hx', hy⟩ + rw [mem_singleton_iff] at hx' + subst hx' + exact hc y hy + obtain ⟨u, v, hu, hv, hxu, hΓv, huv⟩ := generalized_tube_lemma isCompact_singleton hΓc hW hsub + filter_upwards [huhc.forall_isOpen v hv hΓv, hu.mem_nhds (hxu rfl)] with x' hΓ' hx'u + intro y hy + have hmem : (x', y) ∈ u ×ˢ v := ⟨hx'u, hΓ' hy⟩ + exact huv hmem + +/-- Upper hemicontinuity of the constraint set gives upper semicontinuity of the value. -/ +theorem upperSemicontinuousAt_maxValue (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) : + UpperSemicontinuousAt (maxValue f Γ) x := by + intro c hc + obtain ⟨c', hc1, hc2⟩ := exists_between hc + have h : ∀ y ∈ Γ x, f x y < c' := fun y hy => lt_of_le_of_lt (le_maxValue hf (hΓc x) hy) hc1 + filter_upwards [eventually_forall_lt hf (hΓc x) huhc h] with x' hx' + exact lt_of_le_of_lt (maxValue_le (hΓne x') fun y hy => (hx' y hy).le) hc2 + +/-- Lower hemicontinuity of the constraint set gives lower semicontinuity of the value. -/ +theorem lowerSemicontinuousAt_maxValue (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (hlhc : LowerHemicontinuousAt Γ x) : + LowerSemicontinuousAt (maxValue f Γ) x := by + intro c hc + obtain ⟨_, ⟨y₀, hy₀, rfl⟩, hy₀c⟩ := exists_lt_of_lt_csSup ((hΓne x).image _) hc + have hW : IsOpen (↿f ⁻¹' Ioi c) := isOpen_Ioi.preimage hf + obtain ⟨u, v, hu, hv, hxu, hy₀v, huv⟩ := isOpen_prod_iff.mp hW x y₀ hy₀c + filter_upwards [(lowerHemicontinuousAt_iff.mp hlhc) v hv ⟨y₀, hy₀, hy₀v⟩, + hu.mem_nhds hxu] with x' hne hx'u + obtain ⟨y, hyΓ, hyv⟩ := hne + have hmem : (x', y) ∈ u ×ˢ v := ⟨hx'u, hyv⟩ + exact lt_of_lt_of_le (huv hmem) (le_maxValue hf (hΓc x') hyΓ) + +/-- **Berge's maximum theorem**, the value function half: if the constraint correspondence +is continuous with nonempty compact values and the objective is continuous, then the value +of the problem is continuous in the parameter. -/ +theorem continuousAt_maxValue (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) + (hlhc : LowerHemicontinuousAt Γ x) : ContinuousAt (maxValue f Γ) x := by + rw [ContinuousAt, tendsto_order] + exact ⟨lowerSemicontinuousAt_maxValue hf hΓne hΓc hlhc, + upperSemicontinuousAt_maxValue hf hΓne hΓc huhc⟩ + +/-- The value function is continuous, given continuity of the correspondence everywhere. -/ +theorem continuous_maxValue (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuous Γ) (hlhc : LowerHemicontinuous Γ) : + Continuous (maxValue f Γ) := + continuous_iff_continuousAt.mpr fun x => + continuousAt_maxValue hf hΓne hΓc (huhc x) (hlhc x) + +/-- A maximiser exists: the constraint set is compact and the objective continuous. -/ +theorem argmax_nonempty (hf : Continuous ↿f) (hΓne : (Γ x).Nonempty) (hΓc : IsCompact (Γ x)) : + (argmax f Γ x).Nonempty := by + obtain ⟨y, hy, hmax⟩ := hΓc.exists_isMaxOn hΓne (continuous_fiber hf x).continuousOn + exact ⟨y, hy, hmax⟩ + +theorem isCompact_argmax (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) : + IsCompact (argmax f Γ x) := by + have hcl : IsClosed {y | IsMaxOn (f x) (Γ x) y} := by + have heq : {y | IsMaxOn (f x) (Γ x) y} = ⋂ z ∈ Γ x, {y | f x z ≤ f x y} := by + ext y; simp [isMaxOn_iff] + rw [heq] + exact isClosed_biInter fun z _ => isClosed_le continuous_const (continuous_fiber hf x) + exact hΓc.inter_right hcl + +/-- At a maximiser the objective equals the value of the problem. -/ +theorem maxValue_eq (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) {y : Y} + (hy : y ∈ argmax f Γ x) : maxValue f Γ x = f x y := + le_antisymm (maxValue_le ⟨y, hy.1⟩ fun z hz => isMaxOn_iff.mp hy.2 z hz) + (le_maxValue hf hΓc hy.1) + +/-- **Berge's maximum theorem**, the maximiser half: the set of maximisers is upper +hemicontinuous. It need not be lower hemicontinuous -- a maximiser can appear abruptly when +a tie is reached -- which is why the conclusion is one-sided. -/ +theorem upperHemicontinuousAt_argmax (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) + (hlhc : LowerHemicontinuousAt Γ x) : UpperHemicontinuousAt (argmax f Γ) x := by + apply UpperHemicontinuousAt.of_forall_isOpen + intro V hV hsub + by_cases hsubV : Γ x ⊆ V + · -- Nothing to do: the whole constraint set is eventually inside `V`. + filter_upwards [huhc.forall_isOpen V hV hsubV] with x' h + exact fun y hy => h hy.1 + · -- The part of the constraint set outside `V` is compact, nonempty, and strictly + -- suboptimal; both facts persist into a neighbourhood of `x`. + obtain ⟨y', hy', hy'V⟩ := Set.not_subset.mp hsubV + have hKc : IsCompact (Γ x ∩ Vᶜ) := (hΓc x).inter_right hV.isClosed_compl + obtain ⟨y₁, hy₁, hmax₁⟩ := + hKc.exists_isMaxOn ⟨y', hy', hy'V⟩ (continuous_fiber hf x).continuousOn + have hlt : f x y₁ < maxValue f Γ x := by + rcases (le_maxValue hf (hΓc x) hy₁.1).lt_or_eq with h | h + · exact h + · refine absurd (hsub ⟨hy₁.1, isMaxOn_iff.mpr fun z hz => ?_⟩) hy₁.2 + rw [h] + exact le_maxValue hf (hΓc x) hz + obtain ⟨c, hc1, hc2⟩ := exists_between hlt + have hbound : ∀ y ∈ Γ x ∩ Vᶜ, f x y < c := fun y hy => + lt_of_le_of_lt (isMaxOn_iff.mp hmax₁ y hy) hc1 + filter_upwards [eventually_forall_lt hf hKc (huhc.inter hV.isClosed_compl) hbound, + lowerSemicontinuousAt_maxValue hf hΓne hΓc hlhc c hc2] with x' h1 h2 + intro y hy + by_contra hyV + have hlt' : f x' y < c := h1 y ⟨hy.1, hyV⟩ + rw [← maxValue_eq hf (hΓc x') hy] at hlt' + exact absurd h2 (not_lt.mpr hlt'.le) + +/-- **A single-valued upper hemicontinuous correspondence is a continuous function.** This +is how the second half of Berge becomes a continuity statement about the optimal policy, +once the maximiser is known to be unique. -/ +theorem continuous_of_upperHemicontinuous_singleton {Γ : X → Set Y} {g : X → Y} + (huhc : UpperHemicontinuous Γ) (hsingle : ∀ x, Γ x = {g x}) : Continuous g := by + rw [continuous_iff_continuousAt] + intro x + rw [ContinuousAt, tendsto_nhds] + intro V hV hgx + have hsub : Γ x ⊆ V := by + rw [hsingle x] + simpa using hgx + filter_upwards [huhc.forall_isOpen x V hV hsub] with x' h + refine h ?_ + rw [hsingle x'] + rfl + +/-- The same, on a set: single-valuedness is only needed where the conclusion is wanted. +This is the version the household models need, since the optimal action is unique on the +region where the value function is concave, not necessarily everywhere. -/ +theorem continuousOn_of_upperHemicontinuous_singleton {Γ : X → Set Y} {g : X → Y} {s : Set X} + (huhc : UpperHemicontinuous Γ) (hsingle : ∀ x ∈ s, Γ x = {g x}) : ContinuousOn g s := by + intro x hx + rw [ContinuousWithinAt, tendsto_nhds] + intro V hV hgx + have hsub : Γ x ⊆ V := by + rw [hsingle x hx] + simpa using hgx + filter_upwards [nhdsWithin_le_nhds (huhc.forall_isOpen x V hV hsub), self_mem_nhdsWithin] + with x' h hx' + refine h ?_ + rw [hsingle x' hx'] + rfl + +/-! ### A constraint set that does not move + +The degenerate case, recorded to show the hypotheses of the theorems above are +simultaneously satisfiable. -/ + +section Constant + +variable (K : Set Y) + +theorem upperHemicontinuous_const : UpperHemicontinuous (fun _ : X => K) := + UpperHemicontinuous.of_forall_isOpen fun _ _ _ h => Eventually.of_forall fun _ => h + +theorem lowerHemicontinuous_const : LowerHemicontinuous (fun _ : X => K) := + lowerHemicontinuous_iff.mpr fun _ => + lowerHemicontinuousAt_iff.mpr fun _ _ h => Eventually.of_forall fun _ => h + +/-- **Berge is not vacuous.** With a fixed compact nonempty constraint set every hypothesis +of the maximum theorem holds, and the value of the problem is continuous in the +parameter. -/ +theorem continuous_maxValue_const (hf : Continuous ↿f) (hKne : K.Nonempty) (hKc : IsCompact K) : + Continuous (maxValue f fun _ : X => K) := + continuous_maxValue hf (fun _ => hKne) (fun _ => hKc) + (upperHemicontinuous_const K) (lowerHemicontinuous_const K) + +end Constant + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Topology/BergeEReal.lean b/LeanPool/AiyagariEquilibrium/Topology/BergeEReal.lean new file mode 100644 index 000000000..8f37bd5ca --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Topology/BergeEReal.lean @@ -0,0 +1,178 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import LeanPool.AiyagariEquilibrium.Topology.Berge +public import Mathlib.Topology.Instances.EReal.Lemmas + +/-! +# Berge's maximum theorem for extended-real objectives + +`LeanEconomics.Topology.Berge` proves the maximum theorem for a real-valued objective. Here +the objective may take the value `-∞`, which is what a reward like `log c` does at zero +consumption. + +Allowing `-∞` is what lets a dynamic program be posed without a floor on the reward. Every +model in this library that uses a floor does so to keep the reward real-valued and bounded, +and then has to prove separately that the floor never binds. With an extended-real +objective the floor is unnecessary from the start. + +## What changes, and what does not + +Less than one might expect, and two of the changes make the proof *simpler*: `EReal` is a +complete lattice, so `le_sSup` and `sSup_le` carry no boundedness or nonemptiness side +conditions, unlike their conditionally-complete counterparts. + +Upper semicontinuity here goes through *attainment* rather than interpolation: the supremum +over a nonempty compact set is achieved, so a strict bound at every point of the set is a +strict bound on the supremum. (`DenselyOrdered EReal` does exist in Mathlib, as +`instDenselyOrderedEReal`, so the interpolating proof would work too; the argmax half below +uses it.) + +## Main results + +* `LeanEconomics.continuous_maxValueE` : the maximum theorem for `EReal`-valued objectives. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +variable {X Y : Type*} [TopologicalSpace X] [TopologicalSpace Y] +variable {f : X → Y → EReal} {Γ : X → Set Y} {x : X} {c : EReal} + +/-- The value of a maximisation problem whose objective may be `-∞`. No boundedness +hypothesis is needed: `EReal` is a complete lattice. -/ +noncomputable def maxValueE (f : X → Y → EReal) (Γ : X → Set Y) (x : X) : EReal := + sSup (f x '' Γ x) + +omit [TopologicalSpace X] [TopologicalSpace Y] in +theorem le_maxValueE {y : Y} (hy : y ∈ Γ x) : f x y ≤ maxValueE f Γ x := le_sSup ⟨y, hy, rfl⟩ + +omit [TopologicalSpace X] [TopologicalSpace Y] in +theorem maxValueE_le (h : ∀ y ∈ Γ x, f x y ≤ c) : maxValueE f Γ x ≤ c := + sSup_le (by rintro _ ⟨y, hy, rfl⟩; exact h y hy) + +theorem continuous_fiberE (hf : Continuous ↿f) (x : X) : Continuous (f x) := + hf.comp (Continuous.prodMk_right x) + +/-- The engine, exactly as in the real case: a strict upper bound over the constraint set +survives into a neighbourhood of the parameter. -/ +theorem eventually_forall_ltE (hf : Continuous ↿f) (hΓc : IsCompact (Γ x)) + (huhc : UpperHemicontinuousAt Γ x) (hc : ∀ y ∈ Γ x, f x y < c) : + ∀ᶠ x' in 𝓝 x, ∀ y ∈ Γ x', f x' y < c := by + have hW : IsOpen (↿f ⁻¹' Iio c) := isOpen_Iio.preimage hf + have hsub : ({x} : Set X) ×ˢ Γ x ⊆ ↿f ⁻¹' Iio c := by + rintro ⟨x', y⟩ ⟨hx', hy⟩ + rw [mem_singleton_iff] at hx' + subst hx' + exact hc y hy + obtain ⟨u, v, hu, hv, hxu, hΓv, huv⟩ := generalized_tube_lemma isCompact_singleton hΓc hW hsub + filter_upwards [huhc.forall_isOpen v hv hΓv, hu.mem_nhds (hxu rfl)] with x' hΓ' hx'u + intro y hy + have hmem : (x', y) ∈ u ×ˢ v := ⟨hx'u, hΓ' hy⟩ + exact huv hmem + +/-- Upper hemicontinuity of the constraint set gives upper semicontinuity of the value. +This goes through attainment of the supremum rather than interpolating a midpoint. -/ +theorem upperSemicontinuousAt_maxValueE (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) : + UpperSemicontinuousAt (maxValueE f Γ) x := by + intro c hc + have h : ∀ y ∈ Γ x, f x y < c := fun y hy => lt_of_le_of_lt (le_maxValueE hy) hc + filter_upwards [eventually_forall_ltE hf (hΓc x) huhc h] with x' hx' + obtain ⟨y₀, hy₀, heq⟩ := + (hΓc x').exists_sSup_image_eq (hΓne x') (continuous_fiberE hf x').continuousOn + rw [maxValueE, heq] + exact hx' y₀ hy₀ + +/-- Lower hemicontinuity of the constraint set gives lower semicontinuity of the value. -/ +theorem lowerSemicontinuousAt_maxValueE (hf : Continuous ↿f) + (hlhc : LowerHemicontinuousAt Γ x) : LowerSemicontinuousAt (maxValueE f Γ) x := by + intro c hc + obtain ⟨_, ⟨y₀, hy₀, rfl⟩, hy₀c⟩ := lt_sSup_iff.mp hc + have hW : IsOpen (↿f ⁻¹' Ioi c) := isOpen_Ioi.preimage hf + obtain ⟨u, v, hu, hv, hxu, hy₀v, huv⟩ := isOpen_prod_iff.mp hW x y₀ hy₀c + filter_upwards [(lowerHemicontinuousAt_iff.mp hlhc) v hv ⟨y₀, hy₀, hy₀v⟩, + hu.mem_nhds hxu] with x' hne hx'u + obtain ⟨y, hyΓ, hyv⟩ := hne + have hmem : (x', y) ∈ u ×ˢ v := ⟨hx'u, hyv⟩ + exact lt_of_lt_of_le (huv hmem) (le_maxValueE hyΓ) + +/-- **Berge's maximum theorem for an extended-real objective.** -/ +theorem continuousAt_maxValueE (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) + (hlhc : LowerHemicontinuousAt Γ x) : ContinuousAt (maxValueE f Γ) x := by + rw [ContinuousAt, tendsto_order] + exact ⟨lowerSemicontinuousAt_maxValueE hf hlhc, + upperSemicontinuousAt_maxValueE hf hΓne hΓc huhc⟩ + +theorem continuous_maxValueE (hf : Continuous ↿f) (hΓne : ∀ x, (Γ x).Nonempty) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuous Γ) (hlhc : LowerHemicontinuous Γ) : + Continuous (maxValueE f Γ) := + continuous_iff_continuousAt.mpr fun x => + continuousAt_maxValueE hf hΓne hΓc (huhc x) (hlhc x) + +/-- At a maximiser the objective equals the value, exactly as in the real case. -/ +theorem maxValueE_eq (hf : Continuous ↿f) (hΓne : (Γ x).Nonempty) (hΓc : IsCompact (Γ x)) : + ∃ y ∈ Γ x, maxValueE f Γ x = f x y := by + obtain ⟨y₀, hy₀, heq⟩ := hΓc.exists_sSup_image_eq hΓne (continuous_fiberE hf x).continuousOn + exact ⟨y₀, hy₀, heq⟩ + +omit [TopologicalSpace X] [TopologicalSpace Y] in +/-- At a maximiser the objective equals the value. In `EReal` this needs no side +conditions, the order being complete. -/ +theorem maxValueE_eq_of_mem_argmax {y : Y} (hy : y ∈ argmax f Γ x) : + maxValueE f Γ x = f x y := + le_antisymm (maxValueE_le fun z hz => isMaxOn_iff.mp hy.2 z hz) (le_maxValueE hy.1) + +/-- A maximiser exists. -/ +theorem argmaxE_nonempty (hf : Continuous ↿f) (hΓne : (Γ x).Nonempty) + (hΓc : IsCompact (Γ x)) : (argmax f Γ x).Nonempty := by + obtain ⟨y, hy, hmax⟩ := hΓc.exists_isMaxOn hΓne (continuous_fiberE hf x).continuousOn + exact ⟨y, hy, hmax⟩ + +/-- **Berge's maximum theorem, the maximiser half, for an extended-real objective.** The +proof is the real one unchanged: away from an open set containing the maximisers the +objective is strictly below the value, that gap survives into a neighbourhood by the +engine, and lower semicontinuity of the value keeps the value above the gap. -/ +theorem upperHemicontinuousAt_argmaxE (hf : Continuous ↿f) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuousAt Γ x) + (hlhc : LowerHemicontinuousAt Γ x) : UpperHemicontinuousAt (argmax f Γ) x := by + apply UpperHemicontinuousAt.of_forall_isOpen + intro V hV hsub + by_cases hsubV : Γ x ⊆ V + · filter_upwards [huhc.forall_isOpen V hV hsubV] with x' h + exact fun y hy => h hy.1 + · obtain ⟨y', hy', hy'V⟩ := Set.not_subset.mp hsubV + have hKc : IsCompact (Γ x ∩ Vᶜ) := (hΓc x).inter_right hV.isClosed_compl + obtain ⟨y₁, hy₁, hmax₁⟩ := + hKc.exists_isMaxOn ⟨y', hy', hy'V⟩ (continuous_fiberE hf x).continuousOn + have hlt : f x y₁ < maxValueE f Γ x := by + rcases (le_maxValueE hy₁.1).lt_or_eq with h | h + · exact h + · refine absurd (hsub ⟨hy₁.1, isMaxOn_iff.mpr fun z hz => ?_⟩) hy₁.2 + rw [h] + exact le_maxValueE hz + obtain ⟨c, hc1, hc2⟩ := exists_between hlt + have hbound : ∀ y ∈ Γ x ∩ Vᶜ, f x y < c := fun y hy => + lt_of_le_of_lt (isMaxOn_iff.mp hmax₁ y hy) hc1 + filter_upwards [eventually_forall_ltE hf hKc (huhc.inter hV.isClosed_compl) hbound, + lowerSemicontinuousAt_maxValueE hf hlhc c hc2] with x' h1 h2 + intro y hy + by_contra hyV + have hlt' : f x' y < c := h1 y ⟨hy.1, hyV⟩ + rw [← maxValueE_eq_of_mem_argmax hy] at hlt' + exact absurd h2 (not_lt.mpr hlt'.le) + +theorem upperHemicontinuous_argmaxE (hf : Continuous ↿f) + (hΓc : ∀ x, IsCompact (Γ x)) (huhc : UpperHemicontinuous Γ) (hlhc : LowerHemicontinuous Γ) : + UpperHemicontinuous (argmax f Γ) := fun x => + upperHemicontinuousAt_argmaxE hf hΓc (huhc x) (hlhc x) + +end LeanEconomics diff --git a/LeanPool/AiyagariEquilibrium/Topology/IccCorrespondence.lean b/LeanPool/AiyagariEquilibrium/Topology/IccCorrespondence.lean new file mode 100644 index 000000000..d0a10341d --- /dev/null +++ b/LeanPool/AiyagariEquilibrium/Topology/IccCorrespondence.lean @@ -0,0 +1,100 @@ +/- +Copyright (c) 2026 Robert Kirkby. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Robert Kirkby +-/ + +module +public import Mathlib.Topology.Semicontinuity.Hemicontinuity +public import Mathlib.Topology.MetricSpace.Thickening +public import Mathlib.Topology.Instances.Real.Lemmas + +/-! +# A closed interval with moving endpoints is a continuous correspondence + +`x ↦ Set.Icc (f x) (g x)` is upper and lower hemicontinuous when `f` and `g` are +continuous. This is the hypothesis Berge's maximum theorem needs, and a budget set +`{a' | 0 ≤ a' ≤ m a}` is exactly of this shape, so these two lemmas are what let a +constrained consumption problem be posed as a dynamic program. + +## Main results + +* `LeanEconomics.upperHemicontinuous_Icc` +* `LeanEconomics.lowerHemicontinuous_Icc` + +## The asymmetry in the hypotheses + +Upper hemicontinuity needs no order hypothesis: where `g x < f x` the interval is empty, +and the empty set is contained in everything. + +Lower hemicontinuity needs `f ≤ g` *everywhere*, not merely at the point in question, and +this is not a technicality. If `f x = g x` while `f x' > g x'` at nearby `x'`, then `Γ x` +is a single point and `Γ x'` is empty, so an open set meeting `Γ x` meets nothing nearby. +Lower hemicontinuity genuinely fails there. An interval that is allowed to collapse and +then vanish is not a continuous correspondence. +-/ + +@[expose] public section + +open Set Filter Topology + +namespace LeanEconomics + +variable {X : Type*} [TopologicalSpace X] {f g : X → ℝ} + +/-- An interval with continuous endpoints is upper hemicontinuous. -/ +theorem upperHemicontinuous_Icc (hf : Continuous f) (hg : Continuous g) : + UpperHemicontinuous fun x => Set.Icc (f x) (g x) := by + apply UpperHemicontinuous.of_forall_isOpen + intro x V hV hsub + rcases le_or_gt (f x) (g x) with hle | hlt + · -- The interval is nonempty: thicken it slightly and stay inside `V`. + obtain ⟨ε, hε, hthick⟩ := isCompact_Icc.exists_thickening_subset_open hV hsub + have h1 : ∀ᶠ x' in 𝓝 x, |f x' - f x| < ε / 2 := by + simpa [Real.dist_eq] using Metric.tendsto_nhds.mp (hf.tendsto x) (ε / 2) (by positivity) + have h2 : ∀ᶠ x' in 𝓝 x, |g x' - g x| < ε / 2 := by + simpa [Real.dist_eq] using Metric.tendsto_nhds.mp (hg.tendsto x) (ε / 2) (by positivity) + filter_upwards [h1, h2] with x' hx1 hx2 + intro z hz + obtain ⟨hf1, hf2⟩ := abs_lt.mp hx1 + obtain ⟨hg1, hg2⟩ := abs_lt.mp hx2 + obtain ⟨hzl, hzu⟩ := hz + apply hthick + rw [Metric.mem_thickening_iff] + refine ⟨max (f x) (min (g x) z), ⟨le_max_left _ _, max_le hle (min_le_left _ _)⟩, ?_⟩ + rw [Real.dist_eq] + rcases le_total z (f x) with h | h + · rw [min_eq_right (h.trans hle), max_eq_left h, abs_lt] + constructor <;> linarith + · rcases le_total z (g x) with h' | h' + · rw [min_eq_right h', max_eq_right h, sub_self, abs_zero] + exact hε + · rw [min_eq_left h', max_eq_right hle, abs_lt] + constructor <;> linarith + · -- The interval is empty, and stays empty nearby. + have hev : ∀ᶠ x' in 𝓝 x, g x' < f x' := + hg.continuousAt.eventually_lt hf.continuousAt hlt + filter_upwards [hev] with x' h + rw [Set.Icc_eq_empty_of_lt h] + exact Set.empty_subset V + +/-- An interval with continuous endpoints that never collapses past emptiness is lower +hemicontinuous. -/ +theorem lowerHemicontinuous_Icc (hf : Continuous f) (hg : Continuous g) + (hle : ∀ x, f x ≤ g x) : LowerHemicontinuous fun x => Set.Icc (f x) (g x) := by + refine lowerHemicontinuous_iff.mpr fun x => lowerHemicontinuousAt_iff.mpr ?_ + intro V hV hne + obtain ⟨z, hzI, hzV⟩ := hne + -- Clamp `z` into the interval at each nearby point; the clamp is continuous and fixes + -- `z` at `x`, so it stays inside `V` nearby and witnesses the intersection. + have hw : Continuous fun x' => max (f x') (min (g x') z) := + hf.max (hg.min continuous_const) + have hwx : max (f x) (min (g x) z) = z := by + rw [min_eq_right hzI.2, max_eq_right hzI.1] + have hev : ∀ᶠ x' in 𝓝 x, max (f x') (min (g x') z) ∈ V := by + have h := (hw.tendsto x) (hV.mem_nhds (by rw [hwx]; exact hzV)) + exact h + filter_upwards [hev] with x' hx' + exact ⟨_, ⟨le_max_left _ _, max_le (hle x') (min_le_left _ _)⟩, hx'⟩ + +end LeanEconomics diff --git a/LeanPool/projects/aiyagari-equilibrium.yaml b/LeanPool/projects/aiyagari-equilibrium.yaml new file mode 100644 index 000000000..115627a7c --- /dev/null +++ b/LeanPool/projects/aiyagari-equilibrium.yaml @@ -0,0 +1,55 @@ +slug: aiyagari-equilibrium +title: Normalized capped log-household clearing and dynamic programming +summary: 'Proves conditional uniqueness of a rate solving the unit-wage normalized clearing + equation: mean household assets equal α / ((1 - α) * (r + δ)), with α = 9/25 and δ = 2/25. + Households have capped log utility, discount 24/25, no borrowing, and monotone finite + earnings transitions with positive one-step access to a lowest-income state; an explicit + cap-slack inequality is required. Finite Tauchen chains instantiate the household model. + Bisection converges to a given solution of this normalized equation along a stationary-distribution + selection. Equivalence to full competitive market clearing for arbitrary earnings scales + is not established. Preserves Bellman contraction, Berge maximum theorems, stationary + Markov theory, Euler inequalities, consumption concavity and marginal-propensity bounds.' +branch: mathematical economics +entry_module: LeanPool.AiyagariEquilibrium +authors: +- Robert Kirkby +source: + url: https://github.com/LeanEconomics/LeanEconomics + github_repo: LeanEconomics/LeanEconomics + commit: 8295256bbd64ab488c8cb436d68e7064db560dfc + completion_url: https://github.com/LeanEconomics/LeanEconomics/commit/0cf50632dad6952f6feb45efec5a8fe2f3ff0876 + completed_at: '2026-09-18T07:21:51Z' +license: Apache-2.0 +status: verified +provenance: AI +main_declarations: +- LeanEconomics.aiyagariTauchen_equilibriumRate_unique_Ioo +main_results: +- declaration: LeanEconomics.aiyagariTauchen_equilibriumRate_unique_Ioo + informal: For capped log households with discount 24/25, no borrowing and a finite Tauchen + chain with persistence in [0,1), two rates in (-2/25, 1/24) whose stationary household + distributions have mean assets equal to 9 / (16 * (r + 2/25)) coincide, provided the + explicit asset-cap inequality holds at their larger rate. This is the prescribed unit-wage + normalized clearing equation; a bridge to full competitive equilibrium for arbitrary + earnings scales is not supplied. +- declaration: LeanEconomics.IncomeFluctuation.aiyagari1994_log_bisection + informal: Given a rate satisfying the same unit-wage normalized clearing equation in a + compact interval inside (-2/25, 1/24), a stationary-distribution selection and the stated + cap, monotonicity and reachability hypotheses, bisection on normalized excess supply + brackets that rate, bounds midpoint error by the initial width divided by 2^(n+1), and + has left endpoints converging to it. Existence of a solution to this equation is assumed. +- declaration: LeanEconomics.continuousAt_maxValue + informal: A continuous objective maximized over a nonempty compact-valued correspondence + that is upper and lower hemicontinuous has a continuous maximum value at the parameter. +- declaration: LeanEconomics.Blackwell.contractingWith + informal: A monotone operator on bounded continuous real functions that discounts constants + by a nonnegative factor below one is a contraction with that factor. +tags: +- mathematical-economics +- dynamic-programming +- stationary-equilibrium +- markov-processes +msc: +- 91B50 +- 90C39 +- 60J05