Skip to content

Commit

Permalink
feat: port Topology.UrysohnsLemma (#3490)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas V. R <lvr@s-viva.xyz>
Co-authored-by: Jeremy Tan Jie Rui <reddeloostw@gmail.com>
  • Loading branch information
3 people committed Apr 18, 2023
1 parent 6c20894 commit b706a5f
Show file tree
Hide file tree
Showing 2 changed files with 319 additions and 0 deletions.
1 change: 1 addition & 0 deletions Mathlib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -1897,6 +1897,7 @@ import Mathlib.Topology.UniformSpace.UniformConvergence
import Mathlib.Topology.UniformSpace.UniformConvergenceTopology
import Mathlib.Topology.UniformSpace.UniformEmbedding
import Mathlib.Topology.UnitInterval
import Mathlib.Topology.UrysohnsLemma
import Mathlib.Util.AddRelatedDecl
import Mathlib.Util.AssertNoSorry
import Mathlib.Util.AtomM
Expand Down
318 changes: 318 additions & 0 deletions Mathlib/Topology/UrysohnsLemma.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
/-
Copyright (c) 2021 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
! This file was ported from Lean 3 source module topology.urysohns_lemma
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Analysis.NormedSpace.AddTorsor
import Mathlib.LinearAlgebra.AffineSpace.Ordered
import Mathlib.Topology.ContinuousFunction.Basic

/-!
# Urysohn's lemma
In this file we prove Urysohn's lemma `exists_continuous_zero_one_of_closed`: for any two disjoint
closed sets `s` and `t` in a normal topological space `X` there exists a continuous function
`f : X → ℝ` such that
* `f` equals zero on `s`;
* `f` equals one on `t`;
* `0 ≤ f x ≤ 1` for all `x`.
## Implementation notes
Most paper sources prove Urysohn's lemma using a family of open sets indexed by dyadic rational
numbers on `[0, 1]`. There are many technical difficulties with formalizing this proof (e.g., one
needs to formalize the "dyadic induction", then prove that the resulting family of open sets is
monotone). So, we formalize a slightly different proof.
Let `Urysohns.CU` be the type of pairs `(C, U)` of a closed set `C` and an open set `U` such that
`C ⊆ U`. Since `X` is a normal topological space, for each `c : CU X` there exists an open set `u`
such that `c.C ⊆ u ∧ closure u ⊆ c.U`. We define `c.left` and `c.right` to be `(c.C, u)` and
`(closure u, c.U)`, respectively. Then we define a family of functions
`Urysohns.CU.approx (c : Urysohns.CU X) (n : ℕ) : X → ℝ` by recursion on `n`:
* `c.approx 0` is the indicator of `c.Uᶜ`;
* `c.approx (n + 1) x = (c.left.approx n x + c.right.approx n x) / 2`.
For each `x` this is a monotone family of functions that are equal to zero on `c.C` and are equal to
one outside of `c.U`. We also have `c.approx n x ∈ [0, 1]` for all `c`, `n`, and `x`.
Let `Urysohns.CU.lim c` be the supremum (or equivalently, the limit) of `c.approx n`. Then
properties of `Urysohns.CU.approx` immediately imply that
* `c.lim x ∈ [0, 1]` for all `x`;
* `c.lim` equals zero on `c.C` and equals one outside of `c.U`;
* `c.lim x = (c.left.lim x + c.right.lim x) / 2`.
In order to prove that `c.lim` is continuous at `x`, we prove by induction on `n : ℕ` that for `y`
in a small neighborhood of `x` we have `|c.lim y - c.lim x| ≤ (3 / 4) ^ n`. Induction base follows
from `c.lim x ∈ [0, 1]`, `c.lim y ∈ [0, 1]`. For the induction step, consider two cases:
* `x ∈ c.left.U`; then for `y` in a small neighborhood of `x` we have `y ∈ c.left.U ⊆ c.right.C`
(hence `c.right.lim x = c.right.lim y = 0`) and `|c.left.lim y - c.left.lim x| ≤ (3 / 4) ^ n`.
Then
`|c.lim y - c.lim x| = |c.left.lim y - c.left.lim x| / 2 ≤ (3 / 4) ^ n / 2 < (3 / 4) ^ (n + 1)`.
* otherwise, `x ∉ c.left.right.C`; then for `y` in a small neighborhood of `x` we have
`y ∉ c.left.right.C ⊇ c.left.left.U` (hence `c.left.left.lim x = c.left.left.lim y = 1`),
`|c.left.right.lim y - c.left.right.lim x| ≤ (3 / 4) ^ n`, and
`|c.right.lim y - c.right.lim x| ≤ (3 / 4) ^ n`. Combining these inequalities, the triangle
inequality, and the recurrence formula for `c.lim`, we get
`|c.lim x - c.lim y| ≤ (3 / 4) ^ (n + 1)`.
The actual formalization uses `midpoint ℝ x y` instead of `(x + y) / 2` because we have more API
lemmas about `midpoint`.
## Tags
Urysohn's lemma, normal topological space
-/


variable {X : Type _} [TopologicalSpace X]

open Set Filter TopologicalSpace

open Topology Filter

namespace Urysohns

set_option linter.uppercaseLean3 false

/-- An auxiliary type for the proof of Urysohn's lemma: a pair of a closed set `C` and its
open neighborhood `U`. -/
structure CU (X : Type _) [TopologicalSpace X] where
protected (C U : Set X)
protected closed_C : IsClosed C
protected open_U : IsOpen U
protected Subset : C ⊆ U
#align urysohns.CU Urysohns.CU

instance : Inhabited (CU X) :=
⟨⟨∅, univ, isClosed_empty, isOpen_univ, empty_subset _⟩⟩

variable [NormalSpace X]

namespace CU

/-- Due to `normal_exists_closure_subset`, for each `c : CU X` there exists an open set `u`
such chat `c.C ⊆ u` and `closure u ⊆ c.U`. `c.left` is the pair `(c.C, u)`. -/
@[simps C]
def left (c : CU X) : CU X where
C := c.C
U := (normal_exists_closure_subset c.closed_C c.open_U c.Subset).choose
closed_C := c.closed_C
open_U := (normal_exists_closure_subset c.closed_C c.open_U c.Subset).choose_spec.1
Subset := (normal_exists_closure_subset c.closed_C c.open_U c.Subset).choose_spec.2.1
#align urysohns.CU.left Urysohns.CU.left

/-- Due to `normal_exists_closure_subset`, for each `c : CU X` there exists an open set `u`
such chat `c.C ⊆ u` and `closure u ⊆ c.U`. `c.right` is the pair `(closure u, c.U)`. -/
@[simps U]
def right (c : CU X) : CU X where
C := closure (normal_exists_closure_subset c.closed_C c.open_U c.Subset).choose
U := c.U
closed_C := isClosed_closure
open_U := c.open_U
Subset := (normal_exists_closure_subset c.closed_C c.open_U c.Subset).choose_spec.2.2
#align urysohns.CU.right Urysohns.CU.right

theorem left_U_subset_right_C (c : CU X) : c.left.U ⊆ c.right.C :=
subset_closure
#align urysohns.CU.left_U_subset_right_C Urysohns.CU.left_U_subset_right_C

theorem left_U_subset (c : CU X) : c.left.U ⊆ c.U :=
Subset.trans c.left_U_subset_right_C c.right.Subset
#align urysohns.CU.left_U_subset Urysohns.CU.left_U_subset

theorem subset_right_C (c : CU X) : c.C ⊆ c.right.C :=
Subset.trans c.left.Subset c.left_U_subset_right_C
#align urysohns.CU.subset_right_C Urysohns.CU.subset_right_C

/-- `n`-th approximation to a continuous function `f : X → ℝ` such that `f = 0` on `c.C` and `f = 1`
outside of `c.U`. -/
noncomputable def approx : ℕ → CU X → X → ℝ
| 0, c, x => indicator (c.Uᶜ) 1 x
| n + 1, c, x => midpoint ℝ (approx n c.left x) (approx n c.right x)
#align urysohns.CU.approx Urysohns.CU.approx

theorem approx_of_mem_C (c : CU X) (n : ℕ) {x : X} (hx : x ∈ c.C) : c.approx n x = 0 := by
induction' n with n ihn generalizing c
· exact indicator_of_not_mem (fun (hU : x ∈ c.Uᶜ) => hU <| c.Subset hx) _
· simp only [approx]
rw [ihn, ihn, midpoint_self]
exacts [c.subset_right_C hx, hx]
#align urysohns.CU.approx_of_mem_C Urysohns.CU.approx_of_mem_C

theorem approx_of_nmem_U (c : CU X) (n : ℕ) {x : X} (hx : x ∉ c.U) : c.approx n x = 1 := by
induction' n with n ihn generalizing c
· rw [← mem_compl_iff] at hx
exact indicator_of_mem hx _
· simp only [approx]
rw [ihn, ihn, midpoint_self]
exacts [hx, fun hU => hx <| c.left_U_subset hU]
#align urysohns.CU.approx_of_nmem_U Urysohns.CU.approx_of_nmem_U

theorem approx_nonneg (c : CU X) (n : ℕ) (x : X) : 0 ≤ c.approx n x := by
induction' n with n ihn generalizing c
· exact indicator_nonneg (fun _ _ => zero_le_one) _
· simp only [approx, midpoint_eq_smul_add, invOf_eq_inv]
refine' mul_nonneg (inv_nonneg.2 zero_le_two) (add_nonneg _ _) <;> apply ihn
#align urysohns.CU.approx_nonneg Urysohns.CU.approx_nonneg

theorem approx_le_one (c : CU X) (n : ℕ) (x : X) : c.approx n x ≤ 1 := by
induction' n with n ihn generalizing c
· exact indicator_apply_le' (fun _ => le_rfl) fun _ => zero_le_one
· simp only [approx, midpoint_eq_smul_add, invOf_eq_inv, smul_eq_mul, ← div_eq_inv_mul]
have := add_le_add (ihn (left c)) (ihn (right c))
norm_num at this
exact Iff.mpr (div_le_one zero_lt_two) this
#align urysohns.CU.approx_le_one Urysohns.CU.approx_le_one

theorem bddAbove_range_approx (c : CU X) (x : X) : BddAbove (range fun n => c.approx n x) :=
1, fun _ ⟨n, hn⟩ => hn ▸ c.approx_le_one n x⟩
#align urysohns.CU.bdd_above_range_approx Urysohns.CU.bddAbove_range_approx

theorem approx_le_approx_of_U_sub_C {c₁ c₂ : CU X} (h : c₁.U ⊆ c₂.C) (n₁ n₂ : ℕ) (x : X) :
c₂.approx n₂ x ≤ c₁.approx n₁ x := by
by_cases hx : x ∈ c₁.U
· calc
approx n₂ c₂ x = 0 := approx_of_mem_C _ _ (h hx)
_ ≤ approx n₁ c₁ x := approx_nonneg _ _ _
· calc
approx n₂ c₂ x ≤ 1 := approx_le_one _ _ _
_ = approx n₁ c₁ x := (approx_of_nmem_U _ _ hx).symm
#align urysohns.CU.approx_le_approx_of_U_sub_C Urysohns.CU.approx_le_approx_of_U_sub_C

theorem approx_mem_Icc_right_left (c : CU X) (n : ℕ) (x : X) :
c.approx n x ∈ Icc (c.right.approx n x) (c.left.approx n x) := by
induction' n with n ihn generalizing c
· exact ⟨le_rfl, indicator_le_indicator_of_subset (compl_subset_compl.2 c.left_U_subset)
(fun _ => zero_le_one) _⟩
· simp only [approx, mem_Icc]
refine' ⟨midpoint_le_midpoint _ (ihn _).1, midpoint_le_midpoint (ihn _).2 _⟩ <;>
apply approx_le_approx_of_U_sub_C
exacts [subset_closure, subset_closure]
#align urysohns.CU.approx_mem_Icc_right_left Urysohns.CU.approx_mem_Icc_right_left

theorem approx_le_succ (c : CU X) (n : ℕ) (x : X) : c.approx n x ≤ c.approx (n + 1) x := by
induction' n with n ihn generalizing c
· simp only [approx, right_U, right_le_midpoint]
exact (approx_mem_Icc_right_left c 0 x).2
· rw [approx, approx]
exact midpoint_le_midpoint (ihn _) (ihn _)
#align urysohns.CU.approx_le_succ Urysohns.CU.approx_le_succ

theorem approx_mono (c : CU X) (x : X) : Monotone fun n => c.approx n x :=
monotone_nat_of_le_succ fun n => c.approx_le_succ n x
#align urysohns.CU.approx_mono Urysohns.CU.approx_mono

/-- A continuous function `f : X → ℝ` such that
* `0 ≤ f x ≤ 1` for all `x`;
* `f` equals zero on `c.C` and equals one outside of `c.U`;
-/
protected noncomputable def lim (c : CU X) (x : X) : ℝ :=
⨆ n, c.approx n x
#align urysohns.CU.lim Urysohns.CU.lim

theorem tendsto_approx_atTop (c : CU X) (x : X) :
Tendsto (fun n => c.approx n x) atTop (𝓝 <| c.lim x) :=
tendsto_atTop_csupᵢ (c.approx_mono x) ⟨1, fun _ ⟨_, hn⟩ => hn ▸ c.approx_le_one _ _⟩
#align urysohns.CU.tendsto_approx_at_top Urysohns.CU.tendsto_approx_atTop

theorem lim_of_mem_C (c : CU X) (x : X) (h : x ∈ c.C) : c.lim x = 0 := by
simp only [CU.lim, approx_of_mem_C, h, csupᵢ_const]
#align urysohns.CU.lim_of_mem_C Urysohns.CU.lim_of_mem_C

theorem lim_of_nmem_U (c : CU X) (x : X) (h : x ∉ c.U) : c.lim x = 1 := by
simp only [CU.lim, approx_of_nmem_U c _ h, csupᵢ_const]
#align urysohns.CU.lim_of_nmem_U Urysohns.CU.lim_of_nmem_U

theorem lim_eq_midpoint (c : CU X) (x : X) :
c.lim x = midpoint ℝ (c.left.lim x) (c.right.lim x) := by
refine' tendsto_nhds_unique (c.tendsto_approx_atTop x) ((tendsto_add_atTop_iff_nat 1).1 _)
simp only [approx]
exact (c.left.tendsto_approx_atTop x).midpoint (c.right.tendsto_approx_atTop x)
#align urysohns.CU.lim_eq_midpoint Urysohns.CU.lim_eq_midpoint

theorem approx_le_lim (c : CU X) (x : X) (n : ℕ) : c.approx n x ≤ c.lim x :=
le_csupᵢ (c.bddAbove_range_approx x) _
#align urysohns.CU.approx_le_lim Urysohns.CU.approx_le_lim

theorem lim_nonneg (c : CU X) (x : X) : 0 ≤ c.lim x :=
(c.approx_nonneg 0 x).trans (c.approx_le_lim x 0)
#align urysohns.CU.lim_nonneg Urysohns.CU.lim_nonneg

theorem lim_le_one (c : CU X) (x : X) : c.lim x ≤ 1 :=
csupᵢ_le fun _ => c.approx_le_one _ _
#align urysohns.CU.lim_le_one Urysohns.CU.lim_le_one

theorem lim_mem_Icc (c : CU X) (x : X) : c.lim x ∈ Icc (0 : ℝ) 1 :=
⟨c.lim_nonneg x, c.lim_le_one x⟩
#align urysohns.CU.lim_mem_Icc Urysohns.CU.lim_mem_Icc

/-- Continuity of `Urysohns.CU.lim`. See module docstring for a sketch of the proofs. -/
theorem continuous_lim (c : CU X) : Continuous c.lim := by
obtain ⟨h0, h1234, h1⟩ : 0 < (2⁻¹ : ℝ) ∧ (2⁻¹ : ℝ) < 3 / 4 ∧ (3 / 4 : ℝ) < 1 := by norm_num
refine'
continuous_iff_continuousAt.2 fun x =>
(Metric.nhds_basis_closedBall_pow (h0.trans h1234) h1).tendsto_right_iff.2 fun n _ => _
simp only [Metric.mem_closedBall]
induction' n with n ihn generalizing c
· refine' eventually_of_forall fun y => _
rw [pow_zero]
exact Real.dist_le_of_mem_Icc_01 (c.lim_mem_Icc _) (c.lim_mem_Icc _)
· by_cases hxl : x ∈ c.left.U
· filter_upwards [IsOpen.mem_nhds c.left.open_U hxl, ihn c.left]with _ hyl hyd
rw [pow_succ, c.lim_eq_midpoint, c.lim_eq_midpoint,
c.right.lim_of_mem_C _ (c.left_U_subset_right_C hyl),
c.right.lim_of_mem_C _ (c.left_U_subset_right_C hxl)]
refine' (dist_midpoint_midpoint_le _ _ _ _).trans _
rw [dist_self, add_zero, div_eq_inv_mul]
exact mul_le_mul h1234.le hyd dist_nonneg (h0.trans h1234).le
· replace hxl : x ∈ c.left.right.Cᶜ
exact compl_subset_compl.2 c.left.right.Subset hxl
filter_upwards [IsOpen.mem_nhds (isOpen_compl_iff.2 c.left.right.closed_C) hxl,
ihn c.left.right, ihn c.right]with y hyl hydl hydr
replace hxl : x ∉ c.left.left.U
exact compl_subset_compl.2 c.left.left_U_subset_right_C hxl
replace hyl : y ∉ c.left.left.U
exact compl_subset_compl.2 c.left.left_U_subset_right_C hyl
simp only [pow_succ, c.lim_eq_midpoint, c.left.lim_eq_midpoint,
c.left.left.lim_of_nmem_U _ hxl, c.left.left.lim_of_nmem_U _ hyl]
refine' (dist_midpoint_midpoint_le _ _ _ _).trans _
refine' (div_le_div_of_le_of_nonneg (add_le_add_right (dist_midpoint_midpoint_le _ _ _ _) _)
zero_le_two).trans _
rw [dist_self, zero_add]
refine' (div_le_div_of_le_of_nonneg (add_le_add (div_le_div_of_le_of_nonneg hydl zero_le_two)
hydr) zero_le_two).trans_eq _
generalize (3 / 4 : ℝ) ^ n = r
field_simp [two_ne_zero' ℝ]
ring
#align urysohns.CU.continuous_lim Urysohns.CU.continuous_lim

end CU

end Urysohns

variable [NormalSpace X]

/-- Urysohn's lemma: if `s` and `t` are two disjoint closed sets in a normal topological space `X`,
then there exists a continuous function `f : X → ℝ` such that
* `f` equals zero on `s`;
* `f` equals one on `t`;
* `0 ≤ f x ≤ 1` for all `x`.
-/
theorem exists_continuous_zero_one_of_closed {s t : Set X} (hs : IsClosed s) (ht : IsClosed t)
(hd : Disjoint s t) : ∃ f : C(X, ℝ), EqOn f 0 s ∧ EqOn f 1 t ∧ ∀ x, f x ∈ Icc (0 : ℝ) 1 := by
-- The actual proof is in the code above. Here we just repack it into the expected format.
set c : Urysohns.CU X := ⟨s, tᶜ, hs, ht.isOpen_compl, disjoint_left.1 hd⟩
exact ⟨⟨c.lim, c.continuous_lim⟩, c.lim_of_mem_C, fun x hx => c.lim_of_nmem_U _ fun h => h hx,
c.lim_mem_Icc⟩
#align exists_continuous_zero_one_of_closed exists_continuous_zero_one_of_closed

0 comments on commit b706a5f

Please sign in to comment.