Skip to content

Instantly share code, notes, and snippets.

@shingtaklam1324
Last active January 18, 2021 17:10
Show Gist options
  • Save shingtaklam1324/830bc70e0baa343cc68e05cd0a157507 to your computer and use it in GitHub Desktop.
Save shingtaklam1324/830bc70e0baa343cc68e05cd0a157507 to your computer and use it in GitHub Desktop.
import tactic
import data.real.basic
import topology.metric_space.basic
notation `|` x `|` := abs x
namespace epsilon_delta
/-!
## Epsilon-Delta - Motivation for Filters
To understand filters, and why we need them, first we must look at the ε-δ definition of the
limit, that is f(x) → L as x → p if
∀ ε > 0, ∃ δ > 0, ∀ x, 0 < |x - p| < δ → |f(x) - L| < ε
-/
def limit_of_function (f : ℝ → ℝ) (p L : ℝ) :=
∀ ε > 0, ∃ δ > 0, ∀ x, |x - p| < δ → |f x - L| < ε
/-!
On the other hand, we also have limits of sequences
-/
def limit_of_sequence (x : ℕ → ℝ) (c : ℝ) :=
∀ ε > 0, ∃ N, ∀ n ≥ N, |x n - c| < ε
/-!
and a variety of other limits too, such as one sided limits, limits to infinity, ...
With this, we end up with `k` different limits.
Doesn't seem too bad so far? But we can have different source/targets for ±∞, rationals, ...
Now we have `k * k` different definitions.
Worse still, limits compose. Now we're up to `k * k * k` different definitions.
Patrick Massot determined that `k = 13`, and defining all of those lemmas does not scale. Therefore
we introduce the idea of filters to tie everything together, and different types of limits can be
defined by defining new filters, and the composition lemmas will only need to be proven once.
-/
end epsilon_delta
open epsilon_delta
namespace filters_game
/-!
## Filters - Intuitively
This section isn't going to have much Lean code, rather it is an explanation for what Filters are
(in a simplified example). We're not even going to define a filter properly right now.
Let X be the set (Type) we're working in, this could be ℕ, ℝ, ℝ^2, ...
Then let ℱ be a collection of subsets of X satisfying a certain property. For now, let's just say
that the property is that the set is "big".
### Neighbourhood
For a point l in X (which we will take to be some metric space for now), we can say that a subset U
is "big" if and only if there exists δ > 0 such that N_δ(l) ⊆ U, that is, for all x ∈ U, d(x, l) < δ.
The neighbourhoods of l when δ gets smaller and smaller is similar to "zooming in" when we let
δ → 0 in the definition of the limit of a real function. In fact, for the real line, this is the
same definition. Intuitively, we use the neighbourhood filter to represent getting closer and closer
to a point.
### Principal Filter
Let s ⊆ X be a set, then we can define the principal filter on s, 𝓟(s), as all of the supersets of
X. For example, 𝓟(∅) includes all subsets of X, and 𝓟(univ) = {univ}.
### At Top
Next, we can look at X = ℕ, and we say a set U is big if it has some minimum value a, and for all
b ≥ a, b ∈ U. For example, {1,2,3,...} would be big, so would {795, 796, ...}, but {1,3,4,7, ...}
would not be.
With the ε-N definition of a limit of a sequence, we said that for some fixed N, all n ≥ N satisfies
some property. This is the same as the at top filter, since we just choose an element of the filter,
and then say that the property holds for all elements of the set.
Equally, we can also set X = ℝ, and then we can define the at top filter similarly. With this, we
can use the at top filter to represent the values in the filter getting bigger and bigger. That is,
x → ∞.
Equivalently, we also have the at bot filter, which we use to represent x → -∞.
## Filters - Formal Mathematical Definition
A filter ℱ on a set (Type) X is a collection of subsets of X such that
- X ∈ ℱ (the whole space/set/type is in the filter)
- If U ∈ ℱ and U ⊆ V, then V ∈ ℱ (anything bigger than something in the filter must also be in the
filter)
- If U, V ∈ ℱ, then U ∩ V ∈ ℱ (the intersection of two big sets is big)
## Filters - In Lean
-/
-- A filter F on some type X
structure filter (X : Type*) :=
-- Is a collection of subsets of X
(sets : set (set X))
-- such that X is in the sets
(univ_mem : set.univ ∈ sets)
-- anything bigger is also in the filter
(superset_mem : ∀ {U V}, U ∈ sets → U ⊆ V → V ∈ sets)
-- and the intersection of two things in the filter is also in the filter
(inter_mem : ∀ {U V}, U ∈ sets → V ∈ sets → U ∩ V ∈ sets)
variables {X : Type*}
-- We can say that a set s is in a filter F if s ∈ F.sets
instance : has_mem (set X) (filter X) := ⟨λ s F, s ∈ F.sets⟩
lemma mem_iff_mem_sets {s : set X} {F : filter X} : s ∈ F ↔ s ∈ F.sets := iff.rfl
lemma filter_eq : ∀{F G : filter X}, F.sets = G.sets → F = G
| ⟨a, _, _, _⟩ ⟨_, _, _, _⟩ rfl := rfl
@[ext]
protected lemma ext {F G : filter X} : (∀ s, s ∈ F ↔ s ∈ G) → F = G :=
begin
intro h,
apply filter_eq,
ext1 s,
exact h s,
end
/-
### Join
Given a F, a filter of filters on X, we can define a filter on X, G, by saying
s ∈ G if and only if there exists a set of filters T ∈ F such that ∀ t ∈ T, s ∈ T.
-/
def join (f : filter (filter X)) : filter X :=
{ sets := {s | {t : filter X | s ∈ t.sets} ∈ f.sets},
univ_mem := begin
simp only [set.mem_set_of_eq],
convert f.univ_mem,
ext1,
rw eq_true,
exact x.univ_mem,
end,
superset_mem := begin
intros s t hs hst,
apply f.superset_mem hs,
intros u hu,
apply u.superset_mem _ hst,
exact hu,
end,
inter_mem := begin
intros u h hu hv,
apply f.superset_mem (f.inter_mem hu hv),
rintros t ⟨h1, h2⟩,
exact t.inter_mem h1 h2,
end }
/-!
### Principal Filter
We can define the principal filter on a set, as described above.
-/
def principal (s : set X) : filter X :=
{ sets := {t | s ⊆ t},
univ_mem :=
begin
exact set.subset_univ _,
end,
superset_mem := begin
rintros U V (hU : s ⊆ U) (hUV),
exact set.subset.trans hU hUV,
end,
inter_mem := begin
rintros U V (hU : s ⊆ U) (hV : s ⊆ V),
apply set.subset_inter,
exact hU,
exact hV,
end }
notation `𝓟` := principal
lemma mem_principal (s t : set X) : t ∈ 𝓟 s ↔ s ⊆ t := iff.rfl
/-!
## Order Structure on Filters
Much as sets can be ordered, by defining A ≤ B ↔ A ⊆ B, we can also order filters. We can say that
a F ≤ G if G.sets ⊆ F.sets. Note that the order is reversed from the set inclusion order. This can be
thought of as being similar to the refinement of a partition of an interval (a, b) as used in the
definition of the Riemann Integral. That is, if F ≤ G then we can think of F as a refinement of G.
Similar to how ⊆ on sets is transitive, ≤ on filters is also transitive. This means that with the
refinement analogy, if F ≤ G ≤ H, then clearly F ≤ H as F is also a refinement of H.
First, we shall define the partial order on filters.
-/
instance : partial_order (filter X) :=
{ -- Let F, G be filters, we say F ≤ G if G.sets ⊆ F.sets, or equivalently s ∈ G ⇒ s ∈ F.
le := λ F G, ∀ ⦃s⦄, s ∈ G → s ∈ F,
le_refl := begin
intro F, exact set.subset.refl _,
end,
le_trans := begin
intros F G H h1 h2,
exact set.subset.trans h2 h1,
end,
le_antisymm := begin
intros F G h1 h2,
ext1,
split,
{ apply h2 },
{ apply h1 }
end }
lemma le_def {F G : filter X} : F ≤ G ↔ ∀ ⦃s⦄, s ∈ G → s ∈ F := iff.rfl
/-
Clearly if F is a refinement of G, and s is in G, then s must be in F.
-/
lemma mem_of_mem_of_le {s : set X} {F G : filter X} (h : F ≤ G) (hs : s ∈ G) : s ∈ F :=
begin
apply h,
exact hs,
end
-- This is a lemma which is helpful to prove a different lemma, but is not very useful in general.
lemma mp_sets {F : filter X} {s t : set X} (hs : s ∈ F.sets) (h : {x | x ∈ s → x ∈ t} ∈ F.sets) : t ∈ F.sets :=
F.superset_mem (F.inter_mem hs h)
begin
rintros x ⟨h₁, h₂⟩,
apply h₂,
exact h₁,
end
/-
### Infimum and Supremum
After defining the partial order structure, we now need to define the infimum and supremum of two
filters.
First, we will define the infimum. If F, G are filters, we define F ⊓ G to be the set of all s, such
that there exists t ∈ F and u ∈ G where t ∩ u ⊆ s. If we're using the analogy of refinements from
before, then F ⊓ G is the most refined filter which is a refinement of F and a refinement of G. That
is, F ⊓ G is the greatest common refinement of F and G.
This means that F ⊓ G ≤ F and F ⊓ G ≤ G. The final property we need is that if A ≤ B and A ≤ C, then
A ≤ B ⊓ C. That is, if A is a refinement of B, and a refinement of C, then A is a refinement of
B ⊓ C.
-/
instance : has_inf (filter X) := ⟨λ F G,
{ sets := {s | ∃ t ∈ F, ∃ u ∈ G, t ∩ u ⊆ s},
univ_mem := begin
use [set.univ, F.univ_mem, set.univ, G.univ_mem],
simp,
end,
superset_mem := begin
rintros U V ⟨t₁, ht₁, u₁, hu₁, h⟩ hUV,
use [t₁, ht₁, u₁, hu₁],
apply set.subset.trans h hUV,
end,
inter_mem := begin
rintros U V ⟨t₁, ht₁, u₁, hu₁, h₁⟩ ⟨t₂, ht₂, u₂, hu₂, h₂⟩,
use [t₁ ∩ t₂, F.inter_mem ht₁ ht₂, u₁ ∩ u₂, G.inter_mem hu₁ hu₂],
rw [←set.inter_assoc, set.inter_assoc _ t₂, set.inter_comm t₂, ←set.inter_assoc,
set.inter_assoc (t₁ ∩ u₁)],
apply set.inter_subset_inter,
exact h₁,
exact h₂,
end }⟩
/--
Next, we shall define the supremum of two filters. If F and G are filters, then F ⊔ G is the filter
that is the least refined such that F and G are both refinements of it. We define F ⊔ G = F ∩ G, and
we can check that the properties we expect hold, that is
F ≤ F ⊔ G
G ≤ F ⊔ G
and that if A ≤ C, B ≤ C then A ⊔ B ≤ C. This means that if A and B are both refinements of C, then
C must be a refinement of A ⊔ B, which comes from our requirement that it is the least refined.
-/
instance : has_sup (filter X) := ⟨λ F G,
{ sets := {s | s ∈ F ∧ s ∈ G},
univ_mem := begin
split,
exact F.univ_mem,
exact G.univ_mem,
end,
superset_mem := begin
intros U V hU hUV,
split,
{ apply F.superset_mem hU.1 hUV },
{ apply G.superset_mem hU.2 hUV }
end,
inter_mem := begin
intros U V hU hV,
split,
{ apply F.inter_mem hU.1 hV.1 },
{ apply G.inter_mem hU.2 hV.2 }
end }⟩
/-
With this, we can now define the two semilattice structures on filters on X, one for the sup and one
for the inf.
-/
instance : semilattice_sup (filter X) :=
{ le_sup_left := begin
intros a b s hs,
exact hs.1
end,
le_sup_right := begin
intros a b s hs,
exact hs.2
end,
sup_le := begin
intros a b c h1 h2 s hs,
split,
{ apply mem_of_mem_of_le h1 hs },
{ apply mem_of_mem_of_le h2 hs }
end,
..filter.has_sup,
..filter.partial_order }
instance : semilattice_inf (filter X) :=
{ inf_le_left := begin
intros a b s hs,
use [s, hs, set.univ, b.univ_mem],
simp,
end,
inf_le_right := begin
intros a b s hs,
use [set.univ, a.univ_mem, s, hs],
simp,
end,
le_inf := begin
rintros a b c h1 h2 s ⟨t, ht, u, hu, h⟩,
apply mp_sets (h2 hu),
apply mp_sets (h1 ht),
convert a.univ_mem, ext1,
dsimp,
rw eq_true,
intros hxt hxu,
apply h,
split,
{ exact hxt },
{ exact hxu }
end,
..filter.has_inf,
..filter.partial_order }
/-
### Top and Bottom Elements
With the lattice structure on filter X, there are top and bottom elements. That is, there is a filter
⊤ such that ∀ f, f ≤ ⊤. This means that all filters are refinements of ⊤. On the other hand, we
also have a filter ⊥, such that ∀ f, ⊥ ≤ f. This means that ⊥ is a refinement of all filters.
We can check that ⊤ = 𝓟(univ) and ⊥ = 𝓟(∅) satisfies those requirements. Once we have shown that,
we have proven that filters form a bounded lattice.
-/
instance : has_top (filter X) := ⟨principal set.univ⟩
/-
Clearly the only element in the ⊤ filter must be univ. Note that the ⊤ filter cannot be empty, as
we require that univ ∈ ⊤.
-/
lemma mem_top_iff {s : set X} : s ∈ (⊤ : filter X) ↔ s = set.univ :=
begin
exact set.univ_subset_iff,
end
instance : has_bot (filter X) := ⟨principal ∅⟩
/-
Similarly, we can show that if ⊥ is a refinement of all filters, then in fact all subsets of X must
be in ⊥.
-/
lemma mem_bot {s : set X} : s ∈ (⊥ : filter X) :=
begin
exact set.empty_subset s,
end
instance : bounded_lattice (filter X) :=
{ le_top := begin
intros a s hs,
rw mem_top_iff at hs,
rw hs,
exact a.univ_mem,
end,
bot_le := begin
intros a s hs,
exact mem_bot,
end,
..filter.semilattice_sup,
..filter.semilattice_inf,
..filter.has_top,
..filter.has_bot }
/-
### Sup and Inf
We can now define the supremum and infimum on a set of filters. That is, Sup(s) is the filter which
is least refined, such that all elements of s are a refinement of Sup(s). Similarly, Inf(s) is the
filter which is the greatest common refinement of the elements of s.
Let S be a set of filters. Then we can take the principal filter of S, and then join them. We can
show that the result of this operation is Sup(s).
We can define the Infimum of a set S to be the Supremum of the set of common refinements of the set
S.
-/
instance : has_Sup (filter X) := ⟨join ∘ principal⟩
protected lemma Sup_le {s : set (filter X)} {a : filter X} (h : ∀ (b : filter X), b ∈ s → b ≤ a) :
Sup s ≤ a :=
begin
intros t ht g hg,
exact h _ hg ht,
end
protected lemma le_Sup {s : set (filter X)} {a : filter X} (has : a ∈ s) :
a ≤ Sup s :=
begin
intros t ht,
apply ht,
exact has,
end
instance : has_Inf (filter X) := ⟨λ s, Sup {x | ∀ y ∈ s, x ≤ y}⟩
/-
Once we have defined Sup and Inf, we now have everything we need to show that filters form a
complete lattice.
-/
instance : complete_lattice (filter X) :=
{ le_Sup := begin
intros s a has,
apply filters_game.le_Sup has,
end,
Sup_le := begin
intros s a h,
apply filters_game.Sup_le h,
end,
Inf_le := begin
intros s a h,
apply filters_game.Sup_le,
intros b hb,
apply hb,
exact h,
end,
le_Inf := begin
intros s a h,
apply filters_game.le_Sup,
intros g hg,
apply h,
exact hg,
end,
..filter.bounded_lattice,
..filter.has_Sup,
..filter.has_Inf }
/-
### Basis
We can define a basis B on a filter F, which is a family of set X indexed by ι, s, with some
property p i which satisfies
t ∈ F if and only if there exists some i, which satifies p, and t includes s i
-/
protected structure filter.has_basis {ι : Type*} (l : filter X) (p : ι → Prop) (s : ι → set X) : Prop :=
(mem_iff' : ∀ (t : set X), t ∈ l ↔ ∃ i (hi : p i), s i ⊆ t)
lemma filter.has_basis.mem_iff {ι : Type*} {l : filter X} {p : ι → Prop} {s : ι → set X}
(hl : l.has_basis p s) (t : set X) : t ∈ l ↔ ∃ i (hi : p i), s i ⊆ t :=
hl.mem_iff' t
/-!
## Maps
As filters are just sets of sets, we can define a map function on filters. If F is a filter on X,
and m : X → Y, then we can define a new filter F.map f on Y.
(For anyone who knows what a monad/functor is, the `map` function means filters are functors, and
with the `join` operation, this makes them a monad).
Also note that I did not define the comap in this file. I didn't feel like developing the API for
comap as well was required in this file, as all proofs can be done without. However if you're
interested, feel free to take a look at mathlib and see how comap is defined, and also try and find
out where it can be used.
-/
def filter.map {Y : Type*} (m : X → Y) (F : filter X) : filter Y :=
{ -- set.preimage m is a function : set Y → set X, then we take the preimage of F.sets under that
-- function, which gives us set (set Y).
-- This is the sets of all S, such that m⁻¹(S) (the preimage of S) is in F.
sets := (set.preimage m) ⁻¹' F.sets,
univ_mem := begin
-- Note these rewrites aren't necessary, they are just here to illustrate the definitional equalities
rw set.mem_preimage,
rw set.preimage_univ,
exact F.univ_mem
end,
superset_mem := begin
intros s t hs hst,
rw [set.mem_preimage] at hs ⊢,
-- Note that if s ⊆ t, then we must have that m⁻¹(s) ⊆ m⁻¹(t)
apply F.superset_mem hs,
apply set.preimage_mono,
exact hst,
end,
inter_mem := begin
intros u v hu hv,
rw set.mem_preimage at hu hv ⊢,
-- Note that m⁻¹(u ∩ v) = m⁻¹(u) ∩ m⁻¹(v)
rw set.preimage_inter,
apply F.inter_mem,
{ exact hu },
{ exact hv }
end }
@[simp] lemma mem_map {Y : Type*} (m : X → Y) (F : filter X) (t : set Y) :
t ∈ F.map m ↔ {x | m x ∈ t} ∈ F := iff.rfl
lemma map_mono {Y : Type*} {F G : filter X} (h : F ≤ G) {f : X → Y} : F.map f ≤ G.map f :=
begin
intros s hs,
rw mem_map at hs,
apply mem_of_mem_of_le h hs,
end
lemma map_map {Y Z : Type*} (f : X → Y) (g : Y → Z) (F : filter X) :
(F.map f).map g = F.map (g ∘ f) :=
begin
ext1,
simp,
end
/-!
## Tendsto
Now that we defined the theory of filters, we can use these to generalise our idea of "limits" and
"tendsto". Recall that in the definition of a limit for a real function, we say that for all
intervals (L - ε, L + ε), there exists an interval (p - δ, p + δ) such that all points in
(p - δ, p + δ) is mapped to somewhere within (L - ε, L + ε). Equivalently, we can say that if S is
the preimage of (L - ε, L + ε) under f, then there exists an interval (p - δ, p + δ) ⊆ S.
We can recognise that (p - δ, p + δ) ⊆ S is precisely the requirement for S to be in the
neighbourhood filter around p.
Thus, if f : X → Y, F is a filter on X and G is a filter on Y, then we can say that f converges to
G along F if
∀ X ∈ G, f⁻¹(X) ∈ F
This is equivalent to saying F.map f ≤ G.
-/
def tendsto {α β : Type*} (f : α → β) (F : filter α) (G : filter β) := F.map f ≤ G
lemma tendsto_def {α β : Type*} (f : α → β) (F : filter α) (G : filter β) :
tendsto f F G ↔ F.map f ≤ G := iff.rfl
lemma tendsto_iff {α β : Type*} (f : α → β) (F : filter α) (G : filter β) :
tendsto f F G ↔ ∀ (X : set β), X ∈ G → f ⁻¹' X ∈ F := iff.rfl
/-!
## Preliminaries
It's nice that we have defined what it means for a filter to tend to another one, but without any
concrete examples of filters, it may not be obvious how filters work. But before then, we will need
some lemmas.
This section needs to be tidied up.
-/
lemma le_principal_iff {s : set X} {f : filter X} : f ≤ 𝓟 s ↔ s ∈ f :=
begin
change (∀ {t}, s ⊆ t → t ∈ f) ↔ _,
split,
{ intro h,
apply h,
exact set.subset.refl _ },
{ intros h t hst,
apply f.superset_mem h,
exact hst }
end
lemma principal_mono {s t : set X} : 𝓟 s ≤ 𝓟 t ↔ s ⊆ t :=
begin
rw [le_principal_iff, mem_principal],
end
lemma eq_Inf_of_mem_sets_iff_exists_mem {S : set (filter X)} {l : filter X}
(h : ∀ {s}, s ∈ l ↔ ∃ f ∈ S, s ∈ f) : l = Inf S :=
begin
apply le_antisymm,
{ apply le_Inf _,
intros b hb s hs,
rw h,
use [b, hb, hs] },
{ intros s hs,
rcases h.1 hs with ⟨f, hf, hs'⟩,
exact mem_of_mem_of_le (Inf_le hf) hs' }
end
lemma eq_infi_of_mem_sets_iff_exists_mem {ι : Type*} {f : ι → filter X} {l : filter X}
(h : ∀ {s}, s ∈ l ↔ ∃ i, s ∈ f i) :
l = infi f :=
eq_Inf_of_mem_sets_iff_exists_mem $ λ s, h.trans set.exists_range_iff.symm
lemma infi_sets_eq {ι : Type*} {f : ι → filter X} (h : directed (≥) f) [ne : nonempty ι] :
(⨅ i, f i).sets = ⋃ i, (f i).sets :=
let ⟨i⟩ := ne,
u : filter X :=
{ sets := ⋃ i, (f i).sets,
univ_mem := begin
rw [set.mem_Union],
use [i, (f i).univ_mem],
end,
superset_mem := begin
rintros U V ⟨S, ⟨j, rfl⟩, hj⟩ hUV,
rw set.mem_Union,
use j,
apply (f j).superset_mem _ hUV,
exact hj,
end,
inter_mem := begin
rintros U V ⟨S, ⟨j, rfl⟩, hj⟩ ⟨T, ⟨k, rfl⟩, hk⟩,
rcases h j k with ⟨l, hl⟩,
rw set.mem_Union,
use l,
apply (f l).inter_mem,
{ apply mem_of_mem_of_le hl.1,
exact hj },
{ apply mem_of_mem_of_le hl.2,
exact hk }
end } in
begin
change _ = u.sets,
apply congr_arg filter.sets,
symmetry,
apply eq_infi_of_mem_sets_iff_exists_mem,
intro s,
exact set.mem_Union,
end
lemma mem_infi {ι : Type*} {f : ι → filter X} (h : directed (≥) f) [nonempty ι] (s) :
s ∈ infi f ↔ ∃ i, s ∈ f i :=
by simp only [mem_iff_mem_sets, infi_sets_eq h, set.mem_Union]
lemma mem_binfi {ι : Type*} {f : ι → filter X} {s : set ι}
(h : directed_on (f ⁻¹'o (≥)) s) (ne : s.nonempty) {t : set X} :
t ∈ (⨅ i∈s, f i) ↔ ∃ i ∈ s, t ∈ f i :=
by haveI : nonempty {x // x ∈ s} := ne.to_subtype;
erw [infi_subtype', mem_infi h.directed_coe, subtype.exists]; refl
lemma has_basis_binfi_principal {ι : Type*} {s : ι → set X} {S : set ι} (h : directed_on (s ⁻¹'o (≥)) S)
(ne : S.nonempty) :
(⨅ i ∈ S, 𝓟 (s i)).has_basis (λ i, i ∈ S) s :=
⟨begin
refine λ t, (mem_binfi _ ne).trans $ by simp only [mem_principal],
rw [directed_on_iff_directed, ← directed_comp, (∘)] at h ⊢,
apply h.mono_comp _ _,
exact λ _ _, principal_mono.2
end⟩
lemma has_basis_infi_principal {ι : Type*} {s : ι → set X} (h : directed (≥) s) [nonempty ι] :
(⨅ i, 𝓟 (s i)).has_basis (λ _, true) s :=
⟨begin
refine λ t, (mem_infi (h.mono_comp _ _) t).trans $
by simp only [exists_prop, true_and, mem_principal],
exact λ _ _, principal_mono.2
end⟩
lemma mem_infi_sets {ι : Type*} {f : ι → filter X} (i : ι) : ∀{s}, s ∈ f i → s ∈ ⨅i, f i :=
show (⨅i, f i) ≤ f i, from infi_le _ _
/-!
## Neighbourhood Filter
One of the most basic examples of a filter is the neighbourhood filter. Let p ∈ X be a point, and let
S be any open subset of X such that x ∈ S. Then we can define the principal filter on S. Now if we
take the infimum over all S of the principle filters, we get the neighbourhood filter.
We denote the neighbourhood filter on a point p by 𝓝(p).
-/
def nhds {X : Type*} [topological_space X] (p : X) : filter X :=
(⨅ s ∈ {s : set X | p ∈ s ∧ is_open s}, 𝓟 s)
notation `𝓝` := nhds
/-
The open sets containing the point p form a basis for 𝓝 p.
-/
lemma nhds_basis_opens (a : X) [topological_space X] :
(𝓝 a).has_basis (λ s : set X, a ∈ s ∧ is_open s) (λ x, x) :=
begin
apply has_basis_binfi_principal,
{ rintros s ⟨hs₁, hs₂⟩ t ⟨ht₁, ht₂⟩,
use s ∩ t,
refine ⟨_, _, _⟩,
{ split,
{ exact ⟨hs₁, ht₁⟩ },
{ apply is_open_inter,
{ exact hs₂ },
{ exact ht₂ } } },
{ simp },
{ simp } },
{ use set.univ,
split,
{ exact set.mem_univ _ },
{ exact is_open_univ } }
end
/-
Within a topological space, a set s is in 𝓝 p if and only if there exists some open set t such that
t ⊆ s and p ∈ t.
-/
lemma mem_nhds_iff {p : X} [topological_space X] {s : set X} :
s ∈ 𝓝 p ↔ ∃ t ⊆ s, p ∈ t ∧ is_open t :=
begin
rw (nhds_basis_opens p).mem_iff,
simp only [exists_prop],
simp_rw [and_comm],
end
/-
Next, to show that this definition agrees with the intuitive understanding, let's show that in a
metric space X, the open ball around X with radius r is in the neighbourhood filter.
-/
lemma ball_mem_nhds {p : X} [metric_space X] {r : ℝ} (hr : 0 < r) : metric.ball p r ∈ 𝓝 p :=
begin
rw mem_nhds_iff,
use metric.ball p r,
refine ⟨_, _, _⟩,
{ exact rfl.subset },
{ exact metric.mem_ball_self hr },
{ exact metric.is_open_ball }
end
lemma mem_nhds_iff_exists_ball {p : X} [metric_space X] {s : set X} : s ∈ 𝓝 p ↔ ∃ (r > 0), metric.ball p r ⊆ s :=
begin
split,
{ intro h,
rw mem_nhds_iff at h,
rcases h with ⟨t, hst, ht₁, ht₂⟩,
rw metric.is_open_iff at ht₂,
rcases ht₂ p ht₁ with ⟨r, hr, hb⟩,
use [r, hr],
apply set.subset.trans hb hst },
{ rintro ⟨r, hr, hb⟩,
apply filter.superset_mem _ _ hb,
exact ball_mem_nhds hr }
end
lemma mem_of_mem_nhds {p : X} [topological_space X] {s : set X} (h : s ∈ 𝓝 p) : p ∈ s :=
begin
rw mem_nhds_iff at h,
rcases h with ⟨t, hts, hpt, ht⟩,
apply hts,
exact hpt,
end
/-!
### At Top Filter
Another filter that we will need is the at top filter, which is the filter of all subsets of the
form {x | a ≤ x}. This can be used to state the convergence of functions, as well as the convergence
of sequences.
-/
def at_top {X : Type*} [preorder X] : filter X := ⨅ a, principal (set.Ici a)
lemma mem_at_top [preorder X] (a : X) : {b | a ≤ b} ∈ (at_top : filter X) :=
begin
apply mem_infi_sets a,
exact set.subset.refl _,
end
lemma at_top_basis [nonempty X] [semilattice_sup X] :
(@at_top X _).has_basis (λ _, true) set.Ici :=
has_basis_infi_principal (directed_of_sup $ λ a b, set.Ici_subset_Ici.2)
lemma mem_at_top_sets [nonempty X] [semilattice_sup X] {s : set X} :
s ∈ (at_top : filter X) ↔ ∃a:X, ∀b≥a, b ∈ s :=
-- at_top_basis.mem_iff.trans $ exists_congr $ λ _, exists_const _
begin
rw at_top_basis.mem_iff,
{ apply exists_congr,
intro a,
simp only [ge_iff_le, exists_prop_of_true],
refl },
apply_instance
end
/-!
### At Bot Filter
Much like how at top represents the filter as x → ∞, we also have thet at bot filter for when
x → -∞.
-/
def at_bot {X : Type*} [preorder X] : filter X := ⨅ a, principal (set.Iic a)
lemma mem_at_bot [preorder X] (a : X) : {b | b ≤ a} ∈ (at_bot : filter X) :=
begin
apply mem_infi_sets a,
exact set.subset.refl _,
end
/-!
## Convergence
Let's start off with something simple. Let's prove that the constant function f(x) = c tendsto c as
x → p in some metric space X.
-/
example [metric_space X] (c p : X) : tendsto (λ x, c) (𝓝 p) (𝓝 c) :=
begin
intros s hs,
rw mem_map,
rw mem_nhds_iff,
use metric.ball p 1,
refine ⟨_, _, _⟩,
{ intros x hx,
simp only [set.mem_set_of_eq],
apply mem_of_mem_nhds,
exact hs },
{ apply metric.mem_ball_self,
exact zero_lt_one },
{ apply metric.is_open_ball }
end
/-
Now let's prove that the filter definition of convergence is the same as the epsilon-delta
version of convergence.
Note x ∈ metric.ball c δ is the same as dist(c, x) < δ
-/
lemma tendsto_nhds_nhds_iff [metric_space X] {Y : Type*} [metric_space Y] (c : X) (p : Y) (f : X → Y) :
tendsto f (𝓝 c) (𝓝 p) ↔ ∀ ε > 0, ∃ δ > 0, ∀ x ∈ metric.ball c δ, f x ∈ metric.ball p ε :=
begin
split,
{ intros h ε hε,
let B := metric.ball p ε,
have hB : B ∈ 𝓝 p,
{ apply ball_mem_nhds,
exact hε },
specialize h hB,
rw [mem_map, mem_nhds_iff_exists_ball] at h,
rcases h with ⟨δ, hδ, hδ'⟩,
use [δ, hδ],
intros x hx,
exact hδ' hx, },
{ intros h s hs,
rw [mem_nhds_iff_exists_ball] at hs,
rcases hs with ⟨ε, hε, hε'⟩,
rcases h ε hε with ⟨δ, hδ, hδ'⟩,
rw [mem_map, mem_nhds_iff_exists_ball],
use [δ, hδ],
intros x hx,
apply hε',
apply hδ',
exact hx }
end
/-
Now let's prove that the filter definition of f(x) = c as x → ∞ is the same as the one we commonly
use.
-/
lemma tendsto_at_top_nhds_iff [semilattice_sup X] [nonempty X] {Y : Type*} [metric_space Y]
(f : X → Y) (c : Y) : tendsto f at_top (𝓝 c) ↔ ∀ ε > 0, ∃ y, ∀ x ≥ y, f x ∈ metric.ball c ε :=
begin
split,
{ intros h ε hε,
let B := metric.ball c ε,
have hB : B ∈ 𝓝 c,
{ apply ball_mem_nhds,
exact hε },
specialize h hB,
rw [mem_map, mem_at_top_sets] at h,
rcases h with ⟨y, hy⟩,
use y,
intros x hx,
apply hy,
exact hx },
{ intros h s hs,
rw [mem_nhds_iff_exists_ball] at hs,
rcases hs with ⟨ε, hε, hε'⟩,
rw [mem_map, mem_at_top_sets],
cases h ε hε with y hy,
use y,
intros x hx,
apply hε',
apply hy,
exact hx }
end
/-
Finally, let's prove the convergence of a sequence.
-/
example [metric_space X] (x : ℕ → X) (c : X) :
tendsto x at_top (𝓝 c) ↔ ∀ ε > 0, ∃ N, ∀ n ≥ N, x n ∈ metric.ball c ε :=
begin
apply tendsto_at_top_nhds_iff,
end
/-
So the generality of the at_top filter means that the convergence of a function f(x) as x → ∞,
and the convergence of a sequence x₁, x₂, ... are in fact the same thing. Pretty cool?
-/
/-!
## Composition
So far, we've had to set up the different filters, so the merit of using filters versus epsilon-delta
limits isn't clear yet. However, once we want to compose limits, then filters make it a lot easier.
For example, if we want to show that if a sequence x_n → a, and f(x) → b as x → a, then f(x_n) → b.
-/
example (x : ℕ → ℝ) (a b : ℝ) (hx : limit_of_sequence x a) (f : ℝ → ℝ) (hf : limit_of_function f a b) :
limit_of_sequence (λ n, f (x n)) b :=
begin
intros ε hε,
rcases hf ε hε with ⟨δ, hδ, hδ'⟩,
cases hx δ hδ with N hN,
use N,
intros n hn,
apply hδ',
apply hN,
exact hn
end
/-!
Now for every type of limit we have, we will need to write a composition lemma. Instead, if we have
filters, we can write a more general `tendsto_comp`, and this single lemma can perform the task of
all of the composition lemmas for different types of limits.
-/
lemma tendsto_comp {α β γ : Type*} (F : filter α) (G : filter β) (H : filter γ) (f : α → β)
(g : β → γ) (hf : tendsto f F G) (hg : tendsto g G H) : tendsto (g ∘ f) F H :=
begin
rw tendsto_iff at *,
intros s hs,
rw set.preimage_comp,
apply hf,
apply hg,
exact hs,
end
/-!
Finally, as we have an order structure on filters, we can also use that to prove that limits compose.
With `calc` mode, this makes it easy to work out the composition of multiple tendstos/filters.
-/
example {α β γ : Type*} (F : filter α) (G : filter β) (H : filter γ) (f : α → β)
(g : β → γ) (hf : tendsto f F G) (hg : tendsto g G H) : tendsto (g ∘ f) F H :=
calc
-- Rewriting by definition
F.map (g ∘ f) = (F.map f).map g : by rw map_map
-- We need to show (F.map f).map g ≤ G.map g, and as `map` is monotone, suffices to show that
-- `F.map f ≤ G`, but that is precisely `tendsto f F G`
... ≤ G.map g : map_mono hf
-- Now we need to show that `G.map g ≤ H`, but that's just `tendsto g G H`.
... ≤ H : hg
end filters_game
/-
## The End
This is the end of this file, which should have been an introduction to filters, and some examples,
as well as showing the equivalence between filters and the epsilon-delta (and others) definitions of
limits that we are familiar with. With this, there isn't too much point in continuing on, as we will
simply be duplicating parts of mathlib.
To see filters further, feel free to check out the use of filters in mathlib (which in fact can be
used for more than just limits), and also the LFTCM2020 exercises, available at
https://github.com/leanprover-community/lftcm2020/blob/master/src/exercises_sources/friday/topology.lean
-/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment