10. Topology
Calculus is based on the concept of a function, which is used to model quantities that depend on one another. For example, it is common to study quantities that change over time. The notion of a limit is also fundamental. We may say that the limit of a function \(f(x)\) is a value \(b\) as \(x\) approaches a value \(a\), or that \(f(x)\) converges to \(b\) as \(x\) approaches \(a\). Equivalently, we may say that a \(f(x)\) approaches \(b\) as \(x\) approaches a value \(a\), or that it tends to \(b\) as \(x\) tends to \(a\). We have already begun to consider such notions in Section 3.6.
Topology is the abstract study of limits and continuity. Having covered the essentials of formalization in Chapters 2 to 6, in this chapter, we will explain how topological notions are formalized in Mathlib. Not only do topological abstractions apply in much greater generality, but that also, somewhat paradoxically, make it easier to reason about limits and continuity in concrete instances.
Topological notions build on quite a few layers of mathematical structure. The first layer is naive set theory, as described in Chapter 4. The next layer is the theory of filters, which we will describe in Section 10.1. On top of that, we layer the theories of topological spaces, metric spaces, and a slightly more exotic intermediate notion called a uniform space.
Whereas previous chapters relied on mathematical notions that were likely
familiar to you,
the notion of a filter less well known,
even to many working mathematicians.
The notion is essential, however, for formalizing mathematics effectively.
Let us explain why.
Let f : ℝ → ℝ
be any function. We can consider
the limit of f x
as x
approaches some value x₀
,
but we can also consider the limit of f x
as x
approaches infinity
or negative infinity.
We can moreover consider the limit of f x
as x
approaches x₀
from
the right, conventionally written x₀⁺
, or from the left,
written x₀⁻
. There are variations where x
approaches x₀
or x₀⁺
or x₀⁻
but
is not allowed to take on the value x₀
itself.
This results in at least eight ways that x
can approach something.
We can also restrict to rational values of x
or place other constraints on the domain, but let’s stick to those 8 cases.
We have a similar variety of options on the codomain:
we can specify that f x
approaches a value from the left or right,
or that it approaches positive or negative infinity, and so on.
For example, we may wish to say that f x
tends to +∞
when x
tends to x₀
from the right without
being equal to x₀
.
This results in 64 different kinds of limit statements,
and we haven’t even begun to deal with limits of sequences,
as we did in Section 3.6.
The problem is compounded even further when it comes to the supporting lemmas.
For instance, limits compose: if
f x
tends to y₀
when x
tends to x₀
and
g y
tends to z₀
when y
tends to y₀
then
g ∘ f x
tends to z₀
when x
tends to x₀
.
There are three notions of “tends to” at play here,
each of which can be instantiated in any of the eight ways described
in the previous paragraph.
This results in 512 lemmas, a lot to have to add to a library!
Informally, mathematicians generally prove two or three of these
and simply note that the rest can be proved “in the same way.”
Formalizing mathematics requires making the relevant notion of “sameness”
fully explicit, and that is exactly what Bourbaki’s theory of filters
manages to do.
10.1. Filters
A filter on a type X
is a collection of sets of X
that satisfies three
conditions that we will spell out below. The notion
supports two related ideas:
limits, including all the kinds of limits discussed above: finite and infinite limits of sequences, finite and infinite limits of functions at a point or at infinity, and so on.
things happening eventually, including things happening for large enough
n : ℕ
, or sufficiently near a pointx
, or for sufficiently close pairs of points, or almost everywhere in the sense of measure theory. Dually, filters can also express the idea of things happening often: for arbitrarily largen
, at a point in any neighborhood of a given point, etc.
The filters that correspond to these descriptions will be defined later in this section, but we can already name them:
(atTop : Filter ℕ)
, made of sets ofℕ
containing{n | n ≥ N}
for someN
𝓝 x
, made of neighborhoods ofx
in a topological space𝓤 X
, made of entourages of a uniform space (uniform spaces generalize metric spaces and topological groups)μ.ae
, made of sets whose complement has zero measure with respect to a measureμ
.
The general definition is as follows: a filter F : Filter X
is a
collection of sets F.sets : Set (Set X)
satisfying the following:
F.univ_sets : univ ∈ F.sets
F.sets_of_superset : ∀ {U V}, U ∈ F.sets → U ⊆ V → V ∈ F.sets
F.inter_sets : ∀ {U V}, U ∈ F.sets → V ∈ F.sets → U ∩ V ∈ F.sets
.
The first condition says that the set of all elements of X
belongs to F.sets
.
The second condition says that if U
belongs to F.sets
then anything
containing U
also belongs to F.sets
.
The third condition says that F.sets
is closed under finite intersections.
In Mathlib, a filter F
is defined to be a structure bundling F.sets
and its
three properties, but the properties carry no additional data,
and it is convenient to blur the distinction between F
and F.sets
. We
therefore define U ∈ F
to mean U ∈ F.sets
.
This explains why the word sets
appears in the names of some lemmas that
that mention U ∈ F
.
It may help to think of a filter as defining a notion of a “sufficiently large” set. The first
condition then says that univ
is sufficiently large, the second one says that a set containing a sufficiently
large set is sufficiently large and the third one says that the intersection of two sufficiently large sets
is sufficiently large.
It may be even more useful to think of a filter on a type X
as a generalized element of Set X
. For instance, atTop
is the
“set of very large numbers” and 𝓝 x₀
is the “set of points very close to x₀
.”
One manifestation of this view is that we can associate to any s : Set X
the so-called principal filter
consisting of all sets that contain s
.
This definition is already in Mathlib and has a notation 𝓟
(localized in the Filter
namespace).
For the purpose of demonstration, we ask you to take this opportunity to work out the definition here.
def principal {α : Type*} (s : Set α) : Filter α
where
sets := { t | s ⊆ t }
univ_sets := sorry
sets_of_superset := sorry
inter_sets := sorry
For our second example, we ask you to define the filter atTop : Filter ℕ
.
(We could use any type with a preorder instead of ℕ
.)
example : Filter ℕ :=
{ sets := { s | ∃ a, ∀ b, a ≤ b → b ∈ s }
univ_sets := sorry
sets_of_superset := sorry
inter_sets := sorry }
We can also directly define the filter 𝓝 x
of neighborhoods of any x : ℝ
.
In the real numbers, a neighborhood of x
is a set containing an open interval
\((x_0 - \varepsilon, x_0 + \varepsilon)\),
defined in Mathlib as Ioo (x₀ - ε) (x₀ + ε)
.
(This is notion of a neighborhood is only a special case of a more general construction in Mathlib.)
With these examples, we can already define what is means for a function f : X → Y
to converge to some G : Filter Y
along some F : Filter X
,
as follows:
def Tendsto₁ {X Y : Type*} (f : X → Y) (F : Filter X) (G : Filter Y) :=
∀ V ∈ G, f ⁻¹' V ∈ F
When X
is ℕ
and Y
is ℝ
, Tendsto₁ u atTop (𝓝 x)
is equivalent to saying that the sequence u : ℕ → ℝ
converges to the real number x
. When both X
and Y
are ℝ
, Tendsto f (𝓝 x₀) (𝓝 y₀)
is equivalent to the familiar notion \(\lim_{x \to x₀} f(x) = y₀\).
All of the other kinds of limits mentioned in the introduction are
also equivalent to instances of Tendsto₁
for suitable choices of filters on the source and target.
The notion Tendsto₁
above is definitionally equivalent to the notion Tendsto
that is defined in Mathlib,
but the latter is defined more abstractly.
The problem with the definition of Tendsto₁
is that it exposes a quantifier and elements of G
,
and it hides the intuition that we get by viewing filters as generalized sets. We can
hide the quantifier ∀ V
and make the intuition more salient by using more algebraic and set-theoretic machinery.
The first ingredient is the pushforward operation \(f_*\) associated to any map f : X → Y
,
denoted Filter.map f
in Mathlib. Given a filter F
on X
, Filter.map f F : Filter Y
is defined so that
V ∈ Filter.map f F ↔ f ⁻¹' V ∈ F
holds definitionally.
In this examples file we’ve opened the Filter
namespace so that
Filter.map
can be written as map
. This means that we can rewrite the definition of Tendsto
using
the order relation on Filter Y
, which is reversed inclusion of the set of members.
In other words, given G H : Filter Y
, we have G ≤ H ↔ ∀ V : Set Y, V ∈ H → V ∈ G
.
def Tendsto₂ {X Y : Type*} (f : X → Y) (F : Filter X) (G : Filter Y) :=
map f F ≤ G
example {X Y : Type*} (f : X → Y) (F : Filter X) (G : Filter Y) :
Tendsto₂ f F G ↔ Tendsto₁ f F G :=
Iff.rfl
It may seem that the order relation on filters is backward. But recall that we can view filters on X
as
generalized elements of Set X
, via the inclusion of 𝓟 : Set X → Filter X
which maps any set s
to the corresponding principal filter.
This inclusion is order preserving, so the order relation on Filter
can indeed be seen as the natural inclusion relation
between generalized sets. In this analogy, pushforward is analogous to the direct image.
And, indeed, map f (𝓟 s) = 𝓟 (f '' s)
.
We can now understand intuitively why a sequence u : ℕ → ℝ
converges to
a point x₀
if and only if we have map u atTop ≤ 𝓝 x₀
.
The inequality means the “direct image under u
” of
“the set of very big natural numbers” is “included” in “the set of points very close to x₀
.”
As promised, the definition of Tendsto₂
does not exhibit any quantifiers or sets.
It also leverages the algebraic properties of the pushforward operation.
First, each Filter.map f
is monotone. And, second, Filter.map
is compatible with
composition.
#check (@Filter.map_mono : ∀ {α β} {m : α → β}, Monotone (map m))
#check
(@Filter.map_map :
∀ {α β γ} {f : Filter α} {m : α → β} {m' : β → γ}, map m' (map m f) = map (m' ∘ m) f)
Together these two properties allow us to prove that limits compose, yielding in one shot all 256 variants
of the composition lemma described in the introduction, and lots more.
You can practice proving the following statement using either the definition
of Tendsto₁
in terms of the
universal quantifier or the algebraic definition,
together with the two lemmas above.
example {X Y Z : Type*} {F : Filter X} {G : Filter Y} {H : Filter Z} {f : X → Y} {g : Y → Z}
(hf : Tendsto₁ f F G) (hg : Tendsto₁ g G H) : Tendsto₁ (g ∘ f) F H :=
sorry
The pushforward construction uses a map to push filters from the map source to the map target.
There also a pullback operation, Filter.comap
, going in the other direction.
This generalizes the
preimage operation on sets. For any map f
,
Filter.map f
and Filter.comap f
form what is known as a Galois connection,
which is to say, they satisfy
Filter.map_le_iff_le_comap : Filter.map f F ≤ G ↔ F ≤ Filter.comap f G
for every F
and G
.
This operation could be used to provided another formulation of Tendsto
that would be provably
(but not definitionally) equivalent to the one in Mathlib.
The comap
operation can be used to restrict filters to a subtype. For instance, suppose we have f : ℝ → ℝ
,
x₀ : ℝ
and y₀ : ℝ
, and suppose we want to state that f x
approaches y₀
when x
approaches x₀
within the rational numbers.
We can pull the filter 𝓝 x₀
back to ℚ
using the coercion map
(↑) : ℚ → ℝ
and state Tendsto (f ∘ (↑) : ℚ → ℝ) (comap (↑) (𝓝 x₀)) (𝓝 y₀)
.
variable (f : ℝ → ℝ) (x₀ y₀ : ℝ)
#check comap ((↑) : ℚ → ℝ) (𝓝 x₀)
#check Tendsto (f ∘ (↑)) (comap ((↑) : ℚ → ℝ) (𝓝 x₀)) (𝓝 y₀)
The pullback operation is also compatible with composition, but it is contravariant, which is to say, it reverses the order of the arguments.
section
variable {α β γ : Type*} (F : Filter α) {m : γ → β} {n : β → α}
#check (comap_comap : comap m (comap n F) = comap (n ∘ m) F)
end
Let’s now shift attention to the plane ℝ × ℝ
and try to understand how the neighborhoods of a point
(x₀, y₀)
are related to 𝓝 x₀
and 𝓝 y₀
. There is a product operation
Filter.prod : Filter X → Filter Y → Filter (X × Y)
, denoted by ×ˢ
, which answers this question:
example : 𝓝 (x₀, y₀) = 𝓝 x₀ ×ˢ 𝓝 y₀ :=
nhds_prod_eq
The product operation is defined in terms of the pullback operation and the inf
operation:
F ×ˢ G = (comap Prod.fst F) ⊓ (comap Prod.snd G)
.
Here the inf
operation refers to the lattice structure on Filter X
for any type X
, whereby
F ⊓ G
is the greatest filter that is smaller than both F
and G
.
Thus the inf
operation generalizes the notion of the intersection of sets.
A lot of proofs in Mathlib use all of the aforementioned structure (map
, comap
, inf
, sup
, and prod
)
to give algebraic proofs about convergence without ever referring to members of filters.
You can practice doing this in a proof of the following lemma, unfolding the definition of Tendsto
and Filter.prod
if needed.
#check le_inf_iff
example (f : ℕ → ℝ × ℝ) (x₀ y₀ : ℝ) :
Tendsto f atTop (𝓝 (x₀, y₀)) ↔
Tendsto (Prod.fst ∘ f) atTop (𝓝 x₀) ∧ Tendsto (Prod.snd ∘ f) atTop (𝓝 y₀) :=
sorry
The ordered type Filter X
is actually a complete lattice,
which is to say, there is a bottom element, there is a top element, and
every set of filters on X
has an Inf
and a Sup
.
Note that given the second property in the definition of a filter
(if U
belongs to F
then anything larger than U
also belongs to F
),
the first property
(the set of all inhabitants of X
belongs to F
) is
equivalent to the property that F
is not the empty collection of sets.
This shouldn’t be confused with the more subtle question as to whether
the empty set is an element of F
. The
definition of a filter does not prohibit ∅ ∈ F
,
but if the empty set is in F
then
every set is in F
, which is to say, ∀ U : Set X, U ∈ F
.
In this case, F
is a rather trivial filter, which is precisely the
bottom element of the complete lattice Filter X
.
This contrasts with the definition of filters in
Bourbaki, which doesn’t allow filters containing the empty set.
Because we include the trivial filter in our definition, we sometimes need to explicitly assume
nontriviality in some lemmas.
In return, however, the theory has nicer global properties.
We have already seen that including the trivial filter gives us a
bottom element. It also allows us to define principal : Set X → Filter X
,
which maps ∅
to ⊥
, without adding a precondition to rule out the empty set.
And it allows us to define the pullback operation without a precondition as well.
Indeed, it can happen that comap f F = ⊥
although F ≠ ⊥
. For instance,
given x₀ : ℝ
and s : Set ℝ
, the pullback of 𝓝 x₀
under the coercion
from the subtype corresponding to s
is nontrivial if and only if x₀
belongs to the
closure of s
.
In order to manage lemmas that do need to assume some filter is nontrivial, Mathlib has
a type class Filter.NeBot
, and the library has lemmas that assume
(F : Filter X) [F.NeBot]
. The instance database knows, for example, that (atTop : Filter ℕ).NeBot
,
and it knows that pushing forward a nontrivial filter gives a nontrivial filter.
As a result, a lemma assuming [F.NeBot]
will automatically apply to map u atTop
for any sequence u
.
Our tour of the algebraic properties of filters and their relation to limits is essentially done,
but we have not yet justified our claim to have recaptured the usual limit notions.
Superficially, it may seem that Tendsto u atTop (𝓝 x₀)
is stronger than the notion of convergence defined in Section 3.6 because we ask that every neighborhood of x₀
has a preimage belonging to atTop
, whereas the usual definition only requires
this for the standard neighborhoods Ioo (x₀ - ε) (x₀ + ε)
.
The key is that, by definition, every neighborhood contains such a standard one.
This observation leads to the notion of a filter basis.
Given F : Filter X
,
a family of sets s : ι → Set X
is a basis for F
if for every set U
,
we have U ∈ F
if and only if it contains some s i
. In other words, formally speaking,
s
is a basis if it satisfies
∀ U : Set X, U ∈ F ↔ ∃ i, s i ⊆ U
. It is even more flexible to consider
a predicate on ι
that selects only some of the values i
in the indexing type.
In the case of 𝓝 x₀
, we want ι
to be ℝ
, we write ε
for i
, and the predicate should select the positive values of ε
.
So the fact that the sets Ioo (x₀ - ε) (x₀ + ε)
form a basis for the
neighborhood topology on ℝ
is stated as follows:
example (x₀ : ℝ) : HasBasis (𝓝 x₀) (fun ε : ℝ ↦ 0 < ε) fun ε ↦ Ioo (x₀ - ε) (x₀ + ε) :=
nhds_basis_Ioo_pos x₀
There is also a nice basis for the filter atTop
. The lemma
Filter.HasBasis.tendsto_iff
allows
us to reformulate a statement of the form Tendsto f F G
given bases for F
and G
.
Putting these pieces together gives us essentially the notion of convergence
that we used in Section 3.6.
example (u : ℕ → ℝ) (x₀ : ℝ) :
Tendsto u atTop (𝓝 x₀) ↔ ∀ ε > 0, ∃ N, ∀ n ≥ N, u n ∈ Ioo (x₀ - ε) (x₀ + ε) := by
have : atTop.HasBasis (fun _ : ℕ ↦ True) Ici := atTop_basis
rw [this.tendsto_iff (nhds_basis_Ioo_pos x₀)]
simp
We now show how filters facilitate working with properties that hold for sufficiently large numbers
or for points that are sufficiently close to a given point. In Section 3.6, we were often faced with the situation where
we knew that some property P n
holds for sufficiently large n
and that some
other property Q n
holds for sufficiently large n
.
Using cases
twice gave us N_P
and N_Q
satisfying
∀ n ≥ N_P, P n
and ∀ n ≥ N_Q, Q n
. Using set N := max N_P N_Q
, we could
eventually prove ∀ n ≥ N, P n ∧ Q n
.
Doing this repeatedly becomes tiresome.
We can do better by noting that the statement “P n
and Q n
hold for large enough n
” means
that we have {n | P n} ∈ atTop
and {n | Q n} ∈ atTop
.
The fact that atTop
is a filter implies that the intersection of two elements of atTop
is again in atTop
, so we have {n | P n ∧ Q n} ∈ atTop
.
Writing {n | P n} ∈ atTop
is unpleasant,
but we can use the more suggestive notation ∀ᶠ n in atTop, P n
.
Here the superscripted f
stands for “Filter.”
You can think of the notation as saying that for all n
in the “set of very large numbers,” P n
holds. The ∀ᶠ
notation stands for Filter.Eventually
, and the lemma Filter.Eventually.and
uses the intersection property of filters to do what we just described:
example (P Q : ℕ → Prop) (hP : ∀ᶠ n in atTop, P n) (hQ : ∀ᶠ n in atTop, Q n) :
∀ᶠ n in atTop, P n ∧ Q n :=
hP.and hQ
This notation is so convenient and intuitive that we also have specializations
when P
is an equality or inequality statement. For example, let u
and v
be
two sequences of real numbers, and let us show that if
u n
and v n
coincide for sufficiently large n
then
u
tends to x₀
if and only if v
tends to x₀
.
First we’ll use the generic Eventually
and then the one
specialized for the equality predicate, EventuallyEq
. The two statements are
definitionally equivalent so the same proof work in both cases.
example (u v : ℕ → ℝ) (h : ∀ᶠ n in atTop, u n = v n) (x₀ : ℝ) :
Tendsto u atTop (𝓝 x₀) ↔ Tendsto v atTop (𝓝 x₀) :=
tendsto_congr' h
example (u v : ℕ → ℝ) (h : u =ᶠ[atTop] v) (x₀ : ℝ) :
Tendsto u atTop (𝓝 x₀) ↔ Tendsto v atTop (𝓝 x₀) :=
tendsto_congr' h
It is instructive to review the definition of filters in terms of Eventually
.
Given F : Filter X
, for any predicates P
and Q
on X
,
the condition
univ ∈ F
ensures(∀ x, P x) → ∀ᶠ x in F, P x
,the condition
U ∈ F → U ⊆ V → V ∈ F
ensures(∀ᶠ x in F, P x) → (∀ x, P x → Q x) → ∀ᶠ x in F, Q x
, andthe condition
U ∈ F → V ∈ F → U ∩ V ∈ F
ensures(∀ᶠ x in F, P x) → (∀ᶠ x in F, Q x) → ∀ᶠ x in F, P x ∧ Q x
.
#check Eventually.of_forall
#check Eventually.mono
#check Eventually.and
The second item, corresponding to Eventually.mono
, supports nice ways
of using filters, especially when combined
with Eventually.and
. The filter_upwards
tactic allows us to combine them.
Compare:
example (P Q R : ℕ → Prop) (hP : ∀ᶠ n in atTop, P n) (hQ : ∀ᶠ n in atTop, Q n)
(hR : ∀ᶠ n in atTop, P n ∧ Q n → R n) : ∀ᶠ n in atTop, R n := by
apply (hP.and (hQ.and hR)).mono
rintro n ⟨h, h', h''⟩
exact h'' ⟨h, h'⟩
example (P Q R : ℕ → Prop) (hP : ∀ᶠ n in atTop, P n) (hQ : ∀ᶠ n in atTop, Q n)
(hR : ∀ᶠ n in atTop, P n ∧ Q n → R n) : ∀ᶠ n in atTop, R n := by
filter_upwards [hP, hQ, hR] with n h h' h''
exact h'' ⟨h, h'⟩
Readers who know about measure theory will note that the filter μ.ae
of sets whose complement has measure zero
(aka “the set consisting of almost every point”) is not very useful as the source or target of Tendsto
, but it can be conveniently
used with Eventually
to say that a property holds for almost every point.
There is a dual version of ∀ᶠ x in F, P x
, which is occasionally useful:
∃ᶠ x in F, P x
means
{x | ¬P x} ∉ F
. For example, ∃ᶠ n in atTop, P n
means there are arbitrarily large n
such that P n
holds.
The ∃ᶠ
notation stands for Filter.Frequently
.
For a more sophisticated example, consider the following statement about a sequence
u
, a set M
, and a value x
:
If
u
converges tox
andu n
belongs toM
for sufficiently largen
thenx
is in the closure ofM
.
This can be formalized as follows:
Tendsto u atTop (𝓝 x) → (∀ᶠ n in atTop, u n ∈ M) → x ∈ closure M
.
This is a special case of the theorem mem_closure_of_tendsto
from the
topology library.
See if you can prove it using the quoted lemmas,
using the fact that ClusterPt x F
means (𝓝 x ⊓ F).NeBot
and that,
by definition, the assumption ∀ᶠ n in atTop, u n ∈ M
means M ∈ map u atTop
.
#check mem_closure_iff_clusterPt
#check le_principal_iff
#check neBot_of_le
example (u : ℕ → ℝ) (M : Set ℝ) (x : ℝ) (hux : Tendsto u atTop (𝓝 x))
(huM : ∀ᶠ n in atTop, u n ∈ M) : x ∈ closure M :=
sorry
10.2. Metric spaces
Examples in the previous section focus on sequences of real numbers. In this section we will go up a bit in generality and focus on
metric spaces. A metric space is a type X
equipped with a distance function dist : X → X → ℝ
which is a generalization of
the function fun x y ↦ |x - y|
from the case where X = ℝ
.
Introducing such a space is easy and we will check all properties required from the distance function.
variable {X : Type*} [MetricSpace X] (a b c : X)
#check (dist a b : ℝ)
#check (dist_nonneg : 0 ≤ dist a b)
#check (dist_eq_zero : dist a b = 0 ↔ a = b)
#check (dist_comm a b : dist a b = dist b a)
#check (dist_triangle a b c : dist a c ≤ dist a b + dist b c)
Note we also have variants where the distance can be infinite or where dist a b
can be zero without having a = b
or both.
They are called EMetricSpace
, PseudoMetricSpace
and PseudoEMetricSpace
respectively (here “e” stands for “extended”).
Note that our journey from ℝ
to metric spaces jumped over the special case of normed spaces that also require linear algebra and
will be explained as part of the calculus chapter.
10.2.1. Convergence and continuity
Using distance functions, we can already define convergent sequences and continuous functions between metric spaces. They are actually defined in a more general setting covered in the next section, but we have lemmas recasting the definition in terms of distances.
example {u : ℕ → X} {a : X} :
Tendsto u atTop (𝓝 a) ↔ ∀ ε > 0, ∃ N, ∀ n ≥ N, dist (u n) a < ε :=
Metric.tendsto_atTop
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} :
Continuous f ↔
∀ x : X, ∀ ε > 0, ∃ δ > 0, ∀ x', dist x' x < δ → dist (f x') (f x) < ε :=
Metric.continuous_iff
A lot of lemmas have some continuity assumptions, so we end up proving a lot of continuity results and there
is a continuity
tactic devoted to this task. Let’s prove a continuity statement that will be needed
in an exercise below. Notice that Lean knows how to treat a product of two metric spaces as a metric space, so
it makes sense to consider continuous functions from X × X
to ℝ
.
In particular the (uncurried version of the) distance function is such a function.
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} (hf : Continuous f) :
Continuous fun p : X × X ↦ dist (f p.1) (f p.2) := by continuity
This tactic is a bit slow, so it is also useful to know
how to do it by hand. We first need to use that fun p : X × X ↦ f p.1
is continuous because it
is the composition of f
, which is continuous by assumption hf
, and the projection prod.fst
whose continuity
is the content of the lemma continuous_fst
. The composition property is Continuous.comp
which is
in the Continuous
namespace so we can use dot notation to compress
Continuous.comp hf continuous_fst
into hf.comp continuous_fst
which is actually more readable
since it really reads as composing our assumption and our lemma.
We can do the same for the second component to get continuity of fun p : X × X ↦ f p.2
. We then assemble
those two continuities using Continuous.prod_mk
to get
(hf.comp continuous_fst).prod_mk (hf.comp continuous_snd) : Continuous (fun p : X × X ↦ (f p.1, f p.2))
and compose once more to get our full proof.
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} (hf : Continuous f) :
Continuous fun p : X × X ↦ dist (f p.1) (f p.2) :=
continuous_dist.comp ((hf.comp continuous_fst).prod_mk (hf.comp continuous_snd))
The combination of Continuous.prod_mk
and continuous_dist
via Continuous.comp
feels clunky,
even when heavily using dot notation as above. A more serious issue is that this nice proof requires a lot of
planning. Lean accepts the above proof term because it is a full term proving a statement which is
definitionally equivalent to our goal, the crucial definition to unfold being that of a composition of functions.
Indeed our target function fun p : X × X ↦ dist (f p.1) (f p.2)
is not presented as a composition.
The proof term we provided proves continuity of dist ∘ (fun p : X × X ↦ (f p.1, f p.2))
which happens
to be definitionally equal to our target function. But if we try to build this proof gradually using
tactics starting with apply continuous_dist.comp
then Lean’s elaborator will fail to recognize a
composition and refuse to apply this lemma. It is especially bad at this when products of types are involved.
A better lemma to apply here is
Continuous.dist {f g : X → Y} : Continuous f → Continuous g → Continuous (fun x ↦ dist (f x) (g x))
which is nicer to Lean’s elaborator and also provides a shorter proof when directly providing a full
proof term, as can be seen from the following two new proofs of the above statement:
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} (hf : Continuous f) :
Continuous fun p : X × X ↦ dist (f p.1) (f p.2) := by
apply Continuous.dist
exact hf.comp continuous_fst
exact hf.comp continuous_snd
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} (hf : Continuous f) :
Continuous fun p : X × X ↦ dist (f p.1) (f p.2) :=
(hf.comp continuous_fst).dist (hf.comp continuous_snd)
Note that, without the elaboration issue coming from composition, another way to compress
our proof would be to use Continuous.prod_map
which is sometimes useful and gives
as an alternate proof term continuous_dist.comp (hf.prod_map hf)
which even shorter to type.
Since it is sad to decide between a version which is better for elaboration and a version which is shorter
to type, let us wrap this discussion with a last bit of compression offered
by Continuous.fst'
which allows to compress hf.comp continuous_fst
to hf.fst'
(and the same with snd
)
and get our final proof, now bordering obfuscation.
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] {f : X → Y} (hf : Continuous f) :
Continuous fun p : X × X ↦ dist (f p.1) (f p.2) :=
hf.fst'.dist hf.snd'
It’s your turn now to prove some continuity lemma. After trying the continuity tactic, you will need
Continuous.add
, continuous_pow
and continuous_id
to do it by hand.
example {f : ℝ → X} (hf : Continuous f) : Continuous fun x : ℝ ↦ f (x ^ 2 + x) :=
sorry
So far we saw continuity as a global notion, but one can also define continuity at a point.
example {X Y : Type*} [MetricSpace X] [MetricSpace Y] (f : X → Y) (a : X) :
ContinuousAt f a ↔ ∀ ε > 0, ∃ δ > 0, ∀ {x}, dist x a < δ → dist (f x) (f a) < ε :=
Metric.continuousAt_iff
10.2.2. Balls, open sets and closed sets
Once we have a distance function, the most important geometric definitions are (open) balls and closed balls.
variable (r : ℝ)
example : Metric.ball a r = { b | dist b a < r } :=
rfl
example : Metric.closedBall a r = { b | dist b a ≤ r } :=
rfl
Note that r is any real number here, there is no sign restriction. Of course some statements do require a radius condition.
example (hr : 0 < r) : a ∈ Metric.ball a r :=
Metric.mem_ball_self hr
example (hr : 0 ≤ r) : a ∈ Metric.closedBall a r :=
Metric.mem_closedBall_self hr
Once we have balls, we can define open sets. They are actually defined in a more general setting covered in the next section, but we have lemmas recasting the definition in terms of balls.
example (s : Set X) : IsOpen s ↔ ∀ x ∈ s, ∃ ε > 0, Metric.ball x ε ⊆ s :=
Metric.isOpen_iff
Then closed sets are sets whose complement is open. Their important property is they are closed under limits. The closure of a set is the smallest closed set containing it.
example {s : Set X} : IsClosed s ↔ IsOpen (sᶜ) :=
isOpen_compl_iff.symm
example {s : Set X} (hs : IsClosed s) {u : ℕ → X} (hu : Tendsto u atTop (𝓝 a))
(hus : ∀ n, u n ∈ s) : a ∈ s :=
hs.mem_of_tendsto hu (Eventually.of_forall hus)
example {s : Set X} : a ∈ closure s ↔ ∀ ε > 0, ∃ b ∈ s, a ∈ Metric.ball b ε :=
Metric.mem_closure_iff
Do the next exercise without using mem_closure_iff_seq_limit
example {u : ℕ → X} (hu : Tendsto u atTop (𝓝 a)) {s : Set X} (hs : ∀ n, u n ∈ s) :
a ∈ closure s :=
sorry
Remember from the filters sections that neighborhood filters play a big role in Mathlib.
In the metric space context, the crucial point is that balls provide bases for those filters.
The main lemmas here are Metric.nhds_basis_ball
and Metric.nhds_basis_closedBall
that claim this for open and closed balls with positive radius. The center point is an implicit
argument so we can invoke Filter.HasBasis.mem_iff
as in the following example.
example {x : X} {s : Set X} : s ∈ 𝓝 x ↔ ∃ ε > 0, Metric.ball x ε ⊆ s :=
Metric.nhds_basis_ball.mem_iff
example {x : X} {s : Set X} : s ∈ 𝓝 x ↔ ∃ ε > 0, Metric.closedBall x ε ⊆ s :=
Metric.nhds_basis_closedBall.mem_iff
10.2.3. Compactness
Compactness is an important topological notion. It distinguishes subsets of a metric space that enjoy the same kind of properties as segments in reals compared to other intervals:
Any sequence taking value in a compact set has a subsequence that converges in this set
Any continuous function on a nonempty compact set with values in real numbers is bounded and achieves its bounds somewhere (this is called the extreme values theorem).
Compact sets are closed sets.
Let us first check that the unit interval in reals is indeed a compact set, and then check the above
claims for compact sets in general metric spaces. In the second statement we only
need continuity on the given set so we will use ContinuousOn
instead of Continuous
, and
we will give separate statements for the minimum and the maximum. Of course all these results
are deduced from more general versions, some of which will be discussed in later sections.
example : IsCompact (Set.Icc 0 1 : Set ℝ) :=
isCompact_Icc
example {s : Set X} (hs : IsCompact s) {u : ℕ → X} (hu : ∀ n, u n ∈ s) :
∃ a ∈ s, ∃ φ : ℕ → ℕ, StrictMono φ ∧ Tendsto (u ∘ φ) atTop (𝓝 a) :=
hs.tendsto_subseq hu
example {s : Set X} (hs : IsCompact s) (hs' : s.Nonempty) {f : X → ℝ}
(hfs : ContinuousOn f s) :
∃ x ∈ s, ∀ y ∈ s, f x ≤ f y :=
hs.exists_isMinOn hs' hfs
example {s : Set X} (hs : IsCompact s) (hs' : s.Nonempty) {f : X → ℝ}
(hfs : ContinuousOn f s) :
∃ x ∈ s, ∀ y ∈ s, f y ≤ f x :=
hs.exists_isMaxOn hs' hfs
example {s : Set X} (hs : IsCompact s) : IsClosed s :=
hs.isClosed
We can also specify that a metric spaces is globally compact, using an extra Prop
-valued type class:
example {X : Type*} [MetricSpace X] [CompactSpace X] : IsCompact (univ : Set X) :=
isCompact_univ
In a compact metric space any closed set is compact, this is IsClosed.isCompact
.
10.2.4. Uniformly continuous functions
We now turn to uniformity notions on metric spaces : uniformly continuous functions, Cauchy sequences and completeness. Again those are defined in a more general context but we have lemmas in the metric name space to access their elementary definitions. We start with uniform continuity.
example {X : Type*} [MetricSpace X] {Y : Type*} [MetricSpace Y] {f : X → Y} :
UniformContinuous f ↔
∀ ε > 0, ∃ δ > 0, ∀ {a b : X}, dist a b < δ → dist (f a) (f b) < ε :=
Metric.uniformContinuous_iff
In order to practice manipulating all those definitions, we will prove that continuous functions from a compact metric space to a metric space are uniformly continuous (we will see a more general version in a later section).
We will first give an informal sketch. Let f : X → Y
be a continuous function from
a compact metric space to a metric space.
We fix ε > 0
and start looking for some δ
.
Let φ : X × X → ℝ := fun p ↦ dist (f p.1) (f p.2)
and let K := { p : X × X | ε ≤ φ p }
.
Observe φ
is continuous since f
and distance are continuous.
And K
is clearly closed (use isClosed_le
) hence compact since X
is compact.
Then we discuss two possibilities using eq_empty_or_nonempty
.
If K
is empty then we are clearly done (we can set δ = 1
for instance).
So let’s assume K
is not empty, and use the extreme value theorem to choose (x₀, x₁)
attaining the infimum
of the distance function on K
. We can then set δ = dist x₀ x₁
and check everything works.
example {X : Type*} [MetricSpace X] [CompactSpace X]
{Y : Type*} [MetricSpace Y] {f : X → Y}
(hf : Continuous f) : UniformContinuous f :=
sorry
10.2.5. Completeness
A Cauchy sequence in a metric space is a sequence whose terms get closer and closer to each other. There are a couple of equivalent ways to state that idea. In particular converging sequences are Cauchy. The converse is true only in so-called complete spaces.
example (u : ℕ → X) :
CauchySeq u ↔ ∀ ε > 0, ∃ N : ℕ, ∀ m ≥ N, ∀ n ≥ N, dist (u m) (u n) < ε :=
Metric.cauchySeq_iff
example (u : ℕ → X) :
CauchySeq u ↔ ∀ ε > 0, ∃ N : ℕ, ∀ n ≥ N, dist (u n) (u N) < ε :=
Metric.cauchySeq_iff'
example [CompleteSpace X] (u : ℕ → X) (hu : CauchySeq u) :
∃ x, Tendsto u atTop (𝓝 x) :=
cauchySeq_tendsto_of_complete hu
We’ll practice using this definition by proving a convenient criterion which is a special case of a
criterion appearing in Mathlib. This is also a good opportunity to practice using big sums in
a geometric context. In addition to the explanations from the filters section, you will probably need
tendsto_pow_atTop_nhds_zero_of_lt_one
, Tendsto.mul
and dist_le_range_sum_dist
.
theorem cauchySeq_of_le_geometric_two' {u : ℕ → X}
(hu : ∀ n : ℕ, dist (u n) (u (n + 1)) ≤ (1 / 2) ^ n) : CauchySeq u := by
rw [Metric.cauchySeq_iff']
intro ε ε_pos
obtain ⟨N, hN⟩ : ∃ N : ℕ, 1 / 2 ^ N * 2 < ε := by sorry
use N
intro n hn
obtain ⟨k, rfl : n = N + k⟩ := le_iff_exists_add.mp hn
calc
dist (u (N + k)) (u N) = dist (u (N + 0)) (u (N + k)) := sorry
_ ≤ ∑ i in range k, dist (u (N + i)) (u (N + (i + 1))) := sorry
_ ≤ ∑ i in range k, (1 / 2 : ℝ) ^ (N + i) := sorry
_ = 1 / 2 ^ N * ∑ i in range k, (1 / 2 : ℝ) ^ i := sorry
_ ≤ 1 / 2 ^ N * 2 := sorry
_ < ε := sorry
We are ready for the final boss of this section: Baire’s theorem for complete metric spaces!
The proof skeleton below shows interesting techniques. It uses the choose
tactic in its exclamation
mark variant (you should experiment with removing this exclamation mark) and it shows how to
define something inductively in the middle of a proof using Nat.rec_on
.
open Metric
example [CompleteSpace X] (f : ℕ → Set X) (ho : ∀ n, IsOpen (f n)) (hd : ∀ n, Dense (f n)) :
Dense (⋂ n, f n) := by
let B : ℕ → ℝ := fun n ↦ (1 / 2) ^ n
have Bpos : ∀ n, 0 < B n
sorry
/- Translate the density assumption into two functions `center` and `radius` associating
to any n, x, δ, δpos a center and a positive radius such that
`closedBall center radius` is included both in `f n` and in `closedBall x δ`.
We can also require `radius ≤ (1/2)^(n+1)`, to ensure we get a Cauchy sequence later. -/
have :
∀ (n : ℕ) (x : X),
∀ δ > 0, ∃ y : X, ∃ r > 0, r ≤ B (n + 1) ∧ closedBall y r ⊆ closedBall x δ ∩ f n :=
by sorry
choose! center radius Hpos HB Hball using this
intro x
rw [mem_closure_iff_nhds_basis nhds_basis_closedBall]
intro ε εpos
/- `ε` is positive. We have to find a point in the ball of radius `ε` around `x`
belonging to all `f n`. For this, we construct inductively a sequence
`F n = (c n, r n)` such that the closed ball `closedBall (c n) (r n)` is included
in the previous ball and in `f n`, and such that `r n` is small enough to ensure
that `c n` is a Cauchy sequence. Then `c n` converges to a limit which belongs
to all the `f n`. -/
let F : ℕ → X × ℝ := fun n ↦
Nat.recOn n (Prod.mk x (min ε (B 0)))
fun n p ↦ Prod.mk (center n p.1 p.2) (radius n p.1 p.2)
let c : ℕ → X := fun n ↦ (F n).1
let r : ℕ → ℝ := fun n ↦ (F n).2
have rpos : ∀ n, 0 < r n := by sorry
have rB : ∀ n, r n ≤ B n := by sorry
have incl : ∀ n, closedBall (c (n + 1)) (r (n + 1)) ⊆ closedBall (c n) (r n) ∩ f n := by
sorry
have cdist : ∀ n, dist (c n) (c (n + 1)) ≤ B n := by sorry
have : CauchySeq c := cauchySeq_of_le_geometric_two' cdist
-- as the sequence `c n` is Cauchy in a complete space, it converges to a limit `y`.
rcases cauchySeq_tendsto_of_complete this with ⟨y, ylim⟩
-- this point `y` will be the desired point. We will check that it belongs to all
-- `f n` and to `ball x ε`.
use y
have I : ∀ n, ∀ m ≥ n, closedBall (c m) (r m) ⊆ closedBall (c n) (r n) := by sorry
have yball : ∀ n, y ∈ closedBall (c n) (r n) := by sorry
sorry
10.3. Topological spaces
10.3.1. Fundamentals
We now go up in generality and introduce topological spaces. We will review the two main ways to define topological spaces and then explain how the category of topological spaces is much better behaved than the category of metric spaces. Note that we won’t be using Mathlib category theory here, only having a somewhat categorical point of view.
The first way to think about the transition from metric spaces to topological spaces is that we only remember the notion of open sets (or equivalently the notion of closed sets). From this point of view, a topological space is a type equipped with a collection of sets that are called open sets. This collection has to satisfy a number of axioms presented below (this collection is slightly redundant but we will ignore that).
section
variable {X : Type*} [TopologicalSpace X]
example : IsOpen (univ : Set X) :=
isOpen_univ
example : IsOpen (∅ : Set X) :=
isOpen_empty
example {ι : Type*} {s : ι → Set X} (hs : ∀ i, IsOpen (s i)) : IsOpen (⋃ i, s i) :=
isOpen_iUnion hs
example {ι : Type*} [Fintype ι] {s : ι → Set X} (hs : ∀ i, IsOpen (s i)) :
IsOpen (⋂ i, s i) :=
isOpen_iInter_of_finite hs
Closed sets are then defined as sets whose complement is open. A function between topological spaces is (globally) continuous if all preimages of open sets are open.
variable {Y : Type*} [TopologicalSpace Y]
example {f : X → Y} : Continuous f ↔ ∀ s, IsOpen s → IsOpen (f ⁻¹' s) :=
continuous_def
With this definition we already see that, compared to metric spaces, topological spaces only remember enough information to talk about continuous functions: two topological structures on a type are the same if and only if they have the same continuous functions (indeed the identity function will be continuous in both direction if and only if the two structures have the same open sets).
However as soon as we move on to continuity at a point we see the limitations of the approach based
on open sets. In Mathlib we frequently think of topological spaces as types equipped
with a neighborhood filter 𝓝 x
attached to each point x
(the corresponding function
X → Filter X
satisfies certain conditions explained further down). Remember from the filters section that
these gadgets play two related roles. First 𝓝 x
is seen as the generalized set of points of X
that are close to x
. And then it is seen as giving a way to say, for any predicate P : X → Prop
,
that this predicate holds for points that are close enough to x
. Let us state
that f : X → Y
is continuous at x
. The purely filtery way is to say that the direct image under
f
of the generalized set of points that are close to x
is contained in the generalized set of
points that are close to f x
. Recall this is spelled either map f (𝓝 x) ≤ 𝓝 (f x)
or Tendsto f (𝓝 x) (𝓝 (f x))
.
example {f : X → Y} {x : X} : ContinuousAt f x ↔ map f (𝓝 x) ≤ 𝓝 (f x) :=
Iff.rfl
One can also spell it using both neighborhoods seen as ordinary sets and a neighborhood filter
seen as a generalized set: “for any neighborhood U
of f x
, all points close to x
are sent to U
”. Note that the proof is again iff.rfl
, this point of view is definitionally
equivalent to the previous one.
example {f : X → Y} {x : X} : ContinuousAt f x ↔ ∀ U ∈ 𝓝 (f x), ∀ᶠ x in 𝓝 x, f x ∈ U :=
Iff.rfl
We now explain how to go from one point of view to the other. In terms of open sets, we can
simply define members of 𝓝 x
as sets that contain an open set containing x
.
example {x : X} {s : Set X} : s ∈ 𝓝 x ↔ ∃ t, t ⊆ s ∧ IsOpen t ∧ x ∈ t :=
mem_nhds_iff
To go in the other direction we need to discuss the condition that 𝓝 : X → Filter X
must satisfy
in order to be the neighborhood function of a topology.
The first constraint is that 𝓝 x
, seen as a generalized set, contains the set {x}
seen as the generalized set
pure x
(explaining this weird name would be too much of a digression, so we simply accept it for now).
Another way to say it is that if a predicate holds for points close to x
then it holds at x
.
example (x : X) : pure x ≤ 𝓝 x :=
pure_le_nhds x
example (x : X) (P : X → Prop) (h : ∀ᶠ y in 𝓝 x, P y) : P x :=
h.self_of_nhds
Then a more subtle requirement is that, for any predicate P : X → Prop
and any x
, if P y
holds for y
close
to x
then for y
close to x
and z
close to y
, P z
holds. More precisely we have:
example {P : X → Prop} {x : X} (h : ∀ᶠ y in 𝓝 x, P y) : ∀ᶠ y in 𝓝 x, ∀ᶠ z in 𝓝 y, P z :=
eventually_eventually_nhds.mpr h
Those two results characterize the functions X → Filter X
that are neighborhood functions for a topological space
structure on X
. There is a still a function TopologicalSpace.mkOfNhds : (X → Filter X) → TopologicalSpace X
but it will give back its input as a neighborhood function only if it satisfies the above two constraints.
More precisely we have a lemma TopologicalSpace.nhds_mkOfNhds
saying that in a different way and our
next exercise deduces this different way from how we stated it above.
example {α : Type*} (n : α → Filter α) (H₀ : ∀ a, pure a ≤ n a)
(H : ∀ a : α, ∀ p : α → Prop, (∀ᶠ x in n a, p x) → ∀ᶠ y in n a, ∀ᶠ x in n y, p x) :
∀ a, ∀ s ∈ n a, ∃ t ∈ n a, t ⊆ s ∧ ∀ a' ∈ t, s ∈ n a' :=
sorry
Note that TopologicalSpace.mkOfNhds
is not so frequently used, but it still good to know in what
precise sense the neighborhood filters is all there is in a topological space structure.
The next thing to know in order to efficiently use topological spaces in Mathlib is that we use a lot
of formal properties of TopologicalSpace : Type u → Type u
. From a purely mathematical point of view,
those formal properties are a very clean way to explain how topological spaces solve issues that metric spaces
have. From this point of view, the issues solved by topological spaces is that metric spaces enjoy very
little functoriality, and have very bad categorical properties in general. This comes on top of the fact
already discussed that metric spaces contain a lot of geometrical information that is not topologically relevant.
Let us focus on functoriality first. A metric space structure can be induced on a subset or, equivalently, it can be pulled back by an injective map. But that’s pretty much everything. They cannot be pulled back by general map or pushed forward, even by surjective maps.
In particular there is no sensible distance to put on a quotient of a metric space or on an uncountable
products of metric spaces. Consider for instance the type ℝ → ℝ
, seen as
a product of copies of ℝ
indexed by ℝ
. We would like to say that pointwise convergence of
sequences of functions is a respectable notion of convergence. But there is no distance on
ℝ → ℝ
that gives this notion of convergence. Relatedly, there is no distance ensuring that
a map f : X → (ℝ → ℝ)
is continuous if and only if fun x ↦ f x t
is continuous for every t : ℝ
.
We now review the data used to solve all those issues. First we can use any map f : X → Y
to
push or pull topologies from one side to the other. Those two operations form a Galois connection.
variable {X Y : Type*}
example (f : X → Y) : TopologicalSpace X → TopologicalSpace Y :=
TopologicalSpace.coinduced f
example (f : X → Y) : TopologicalSpace Y → TopologicalSpace X :=
TopologicalSpace.induced f
example (f : X → Y) (T_X : TopologicalSpace X) (T_Y : TopologicalSpace Y) :
TopologicalSpace.coinduced f T_X ≤ T_Y ↔ T_X ≤ TopologicalSpace.induced f T_Y :=
coinduced_le_iff_le_induced
Those operations are compactible with composition of functions.
As usual, pushing forward is covariant and pulling back is contravariant, see coinduced_compose
and induced_compose
.
On paper we will use notations \(f_*T\) for TopologicalSpace.coinduced f T
and
\(f^*T\) for TopologicalSpace.induced f T
.
Then the next big piece is a complete lattice structure on TopologicalSpace X
for any given structure. If you think of topologies as being primarily the data of open sets then you expect
the order relation on TopologicalSpace X
to come from Set (Set X)
, i.e. you expect t ≤ t'
if a set u
is open for t'
as soon as it is open for t
. However we already know that Mathlib focuses
on neighborhoods more than open sets so, for any x : X
we want the map from topological spaces to neighborhoods
fun T : TopologicalSpace X ↦ @nhds X T x
to be order preserving.
And we know the order relation on Filter X
is designed to ensure an order
preserving principal : Set X → Filter X
, allowing to see filters as generalized sets.
So the order relation we do use on TopologicalSpace X
is opposite to the one coming from Set (Set X)
.
example {T T' : TopologicalSpace X} : T ≤ T' ↔ ∀ s, T'.IsOpen s → T.IsOpen s :=
Iff.rfl
Now we can recover continuity by combining the push-forward (or pull-back) operation with the order relation.
example (T_X : TopologicalSpace X) (T_Y : TopologicalSpace Y) (f : X → Y) :
Continuous f ↔ TopologicalSpace.coinduced f T_X ≤ T_Y :=
continuous_iff_coinduced_le
With this definition and the compatibility of push-forward and composition, we get for free the universal property that, for any topological space \(Z\), a function \(g : Y → Z\) is continuous for the topology \(f_*T_X\) if and only if \(g ∘ f\) is continuous.
example {Z : Type*} (f : X → Y) (T_X : TopologicalSpace X) (T_Z : TopologicalSpace Z)
(g : Y → Z) :
@Continuous Y Z (TopologicalSpace.coinduced f T_X) T_Z g ↔
@Continuous X Z T_X T_Z (g ∘ f) := by
rw [continuous_iff_coinduced_le, coinduced_compose, continuous_iff_coinduced_le]
So we already get quotient topologies (using the projection map as f
). This wasn’t using that
TopologicalSpace X
is a complete lattice for all X
. Let’s now see how all this structure
proves the existence of the product topology by abstract non-sense.
We considered the case of ℝ → ℝ
above, but let’s now consider the general case of Π i, X i
for
some ι : Type*
and X : ι → Type*
. We want, for any topological space Z
and any function
f : Z → Π i, X i
, that f
is continuous if and only if (fun x ↦ x i) ∘ f
is continuous for all i
.
Let us explore that constraint “on paper” using notation \(p_i\) for the projection
(fun (x : Π i, X i) ↦ x i)
:
So we see that what is the topology we want on Π i, X i
:
example (ι : Type*) (X : ι → Type*) (T_X : ∀ i, TopologicalSpace (X i)) :
(Pi.topologicalSpace : TopologicalSpace (∀ i, X i)) =
⨅ i, TopologicalSpace.induced (fun x ↦ x i) (T_X i) :=
rfl
This ends our tour of how Mathlib thinks that topological spaces fix defects of the theory of metric spaces by being a more functorial theory and having a complete lattice structure for any fixed type.
10.3.2. Separation and countability
We saw that the category of topological spaces have very nice properties. The price to pay for
this is existence of rather pathological topological spaces.
There are a number of assumptions you can make on a topological space to ensure its behavior
is closer to what metric spaces do. The most important is T2Space
, also called “Hausdorff”,
that will ensure that limits are unique.
A stronger separation property is T3Space
that ensures in addition the RegularSpace property:
each point has a basis of closed neighborhoods.
example [TopologicalSpace X] [T2Space X] {u : ℕ → X} {a b : X} (ha : Tendsto u atTop (𝓝 a))
(hb : Tendsto u atTop (𝓝 b)) : a = b :=
tendsto_nhds_unique ha hb
example [TopologicalSpace X] [RegularSpace X] (a : X) :
(𝓝 a).HasBasis (fun s : Set X ↦ s ∈ 𝓝 a ∧ IsClosed s) id :=
closed_nhds_basis a
Note that, in every topological space, each point has a basis of open neighborhood, by definition.
example [TopologicalSpace X] {x : X} :
(𝓝 x).HasBasis (fun t : Set X ↦ t ∈ 𝓝 x ∧ IsOpen t) id :=
nhds_basis_opens' x
Our main goal is now to prove the basic theorem which allows extension by continuity. From Bourbaki’s general topology book, I.8.5, Theorem 1 (taking only the non-trivial implication):
Let \(X\) be a topological space, \(A\) a dense subset of \(X\), \(f : A → Y\) a continuous mapping of \(A\) into a \(T_3\) space \(Y\). If, for each \(x\) in \(X\), \(f(y)\) tends to a limit in \(Y\) when \(y\) tends to \(x\) while remaining in \(A\) then there exists a continuous extension \(φ\) of \(f\) to \(X\).
Actually Mathlib contains a more general version of the above lemma, DenseInducing.continuousAt_extend
,
but we’ll stick to Bourbaki’s version here.
Remember that, given A : Set X
, ↥A
is the subtype associated to A
, and Lean will automatically
insert that funny up arrow when needed. And the (inclusion) coercion map is (↑) : A → X
.
The assumption “tends to \(x\) while remaining in \(A\)” corresponds to the pull-back filter
comap (↑) (𝓝 x)
.
Let’s first prove an auxiliary lemma, extracted to simplify the context (in particular we don’t need Y to be a topological space here).
theorem aux {X Y A : Type*} [TopologicalSpace X] {c : A → X}
{f : A → Y} {x : X} {F : Filter Y}
(h : Tendsto f (comap c (𝓝 x)) F) {V' : Set Y} (V'_in : V' ∈ F) :
∃ V ∈ 𝓝 x, IsOpen V ∧ c ⁻¹' V ⊆ f ⁻¹' V' := by
sorry
Let’s now turn to the main proof of the extension by continuity theorem.
When Lean needs a topology on ↥A
it will automatically use the induced topology.
The only relevant lemma is
nhds_induced (↑) : ∀ a : ↥A, 𝓝 a = comap (↑) (𝓝 ↑a)
(this is actually a general lemma about induced topologies).
The proof outline is:
The main assumption and the axiom of choice give a function φ
such that
∀ x, Tendsto f (comap (↑) (𝓝 x)) (𝓝 (φ x))
(because Y
is Hausdorff, φ
is entirely determined, but we won’t need that until we try to
prove that φ
indeed extends f
).
Let’s first prove φ
is continuous. Fix any x : X
.
Since Y
is regular, it suffices to check that for every closed neighborhood
V'
of φ x
, φ ⁻¹' V' ∈ 𝓝 x
.
The limit assumption gives (through the auxiliary lemma above)
some V ∈ 𝓝 x
such IsOpen V ∧ (↑) ⁻¹' V ⊆ f ⁻¹' V'
.
Since V ∈ 𝓝 x
, it suffices to prove V ⊆ φ ⁻¹' V'
, i.e. ∀ y ∈ V, φ y ∈ V'
.
Let’s fix y
in V
. Because V
is open, it is a neighborhood of y
.
In particular (↑) ⁻¹' V ∈ comap (↑) (𝓝 y)
and a fortiori f ⁻¹' V' ∈ comap (↑) (𝓝 y)
.
In addition comap (↑) (𝓝 y) ≠ ⊥
because A
is dense.
Because we know Tendsto f (comap (↑) (𝓝 y)) (𝓝 (φ y))
this implies
φ y ∈ closure V'
and, since V'
is closed, we have proved φ y ∈ V'
.
It remains to prove that φ
extends f
. This is where the continuity of f
enters the
discussion, together with the fact that Y
is Hausdorff.
example [TopologicalSpace X] [TopologicalSpace Y] [T3Space Y] {A : Set X}
(hA : ∀ x, x ∈ closure A) {f : A → Y} (f_cont : Continuous f)
(hf : ∀ x : X, ∃ c : Y, Tendsto f (comap (↑) (𝓝 x)) (𝓝 c)) :
∃ φ : X → Y, Continuous φ ∧ ∀ a : A, φ a = f a := by
sorry
#check HasBasis.tendsto_right_iff
In addition to separation property, the main kind of assumption you can make on a topological space to bring it closer to metric spaces is countability assumption. The main one is first countability asking that every point has a countable neighborhood basis. In particular this ensures that closure of sets can be understood using sequences.
example [TopologicalSpace X] [FirstCountableTopology X]
{s : Set X} {a : X} :
a ∈ closure s ↔ ∃ u : ℕ → X, (∀ n, u n ∈ s) ∧ Tendsto u atTop (𝓝 a) :=
mem_closure_iff_seq_limit
10.3.3. Compactness
Let us now discuss how compactness is defined for topological spaces. As usual there are several ways to think about it and Mathlib goes for the filter version.
We first need to define cluster points of filters. Given a filter F
on a topological space X
,
a point x : X
is a cluster point of F
if F
, seen as a generalized set, has non-empty intersection
with the generalized set of points that are close to x
.
Then we can say that a set s
is compact if every nonempty generalized set F
contained in s
,
i.e. such that F ≤ 𝓟 s
, has a cluster point in s
.
variable [TopologicalSpace X]
example {F : Filter X} {x : X} : ClusterPt x F ↔ NeBot (𝓝 x ⊓ F) :=
Iff.rfl
example {s : Set X} :
IsCompact s ↔ ∀ (F : Filter X) [NeBot F], F ≤ 𝓟 s → ∃ a ∈ s, ClusterPt a F :=
Iff.rfl
For instance if F
is map u atTop
, the image under u : ℕ → X
of atTop
, the generalized set
of very large natural numbers, then the assumption F ≤ 𝓟 s
means that u n
belongs to s
for n
large enough. Saying that x
is a cluster point of map u atTop
says the image of very large numbers
intersects the set of points that are close to x
. In case 𝓝 x
has a countable basis, we can
interpret this as saying that u
has a subsequence converging to x
, and we get back what compactness
looks like in metric spaces.
example [FirstCountableTopology X] {s : Set X} {u : ℕ → X} (hs : IsCompact s)
(hu : ∀ n, u n ∈ s) : ∃ a ∈ s, ∃ φ : ℕ → ℕ, StrictMono φ ∧ Tendsto (u ∘ φ) atTop (𝓝 a) :=
hs.tendsto_subseq hu
Cluster points behave nicely with continuous functions.
variable [TopologicalSpace Y]
example {x : X} {F : Filter X} {G : Filter Y} (H : ClusterPt x F) {f : X → Y}
(hfx : ContinuousAt f x) (hf : Tendsto f F G) : ClusterPt (f x) G :=
ClusterPt.map H hfx hf
As an exercise, we will prove that the image of a compact set under a continuous map is
compact. In addition to what we saw already, you should use Filter.push_pull
and
NeBot.of_map
.
example [TopologicalSpace Y] {f : X → Y} (hf : Continuous f) {s : Set X} (hs : IsCompact s) :
IsCompact (f '' s) := by
intro F F_ne F_le
have map_eq : map f (𝓟 s ⊓ comap f F) = 𝓟 (f '' s) ⊓ F := by sorry
have Hne : (𝓟 s ⊓ comap f F).NeBot := by sorry
have Hle : 𝓟 s ⊓ comap f F ≤ 𝓟 s := inf_le_left
sorry
One can also express compactness in terms of open covers: s
is compact if every family of open sets that
cover s
has a finite covering sub-family.
example {ι : Type*} {s : Set X} (hs : IsCompact s) (U : ι → Set X) (hUo : ∀ i, IsOpen (U i))
(hsU : s ⊆ ⋃ i, U i) : ∃ t : Finset ι, s ⊆ ⋃ i ∈ t, U i :=
hs.elim_finite_subcover U hUo hsU