Basic theory of topological spaces. #
THIS FILE IS SYNCHRONIZED WITH MATHLIB4. Any changes to this file require a corresponding PR to mathlib4.
The main definition is the type class topological_space α
which endows a type α
with a topology.
Then set α
gets predicates is_open
, is_closed
and functions interior
, closure
and
frontier
. Each point x
of α
gets a neighborhood filter 𝓝 x
. A filter F
on α
has
x
as a cluster point if cluster_pt x F : 𝓝 x ⊓ F ≠ ⊥
. A map f : ι → α
clusters at x
along F : filter ι
if map_cluster_pt x F f : cluster_pt x (map f F)
. In particular
the notion of cluster point of a sequence u
is map_cluster_pt x at_top u
.
For topological spaces α
and β
, a function f : α → β
and a point a : α
,
continuous_at f a
means f
is continuous at a
, and global continuity is
continuous f
. There is also a version of continuity pcontinuous
for
partially defined functions.
Notation #
𝓝 x
: the filternhds x
of neighborhoods of a pointx
;𝓟 s
: the principal filter of a sets
;𝓝[s] x
: the filternhds_within x s
of neighborhoods of a pointx
within a sets
;𝓝[≤] x
: the filternhds_within x (set.Iic x)
of left-neighborhoods ofx
;𝓝[≥] x
: the filternhds_within x (set.Ici x)
of right-neighborhoods ofx
;𝓝[<] x
: the filternhds_within x (set.Iio x)
of punctured left-neighborhoods ofx
;𝓝[>] x
: the filternhds_within x (set.Ioi x)
of punctured right-neighborhoods ofx
;𝓝[≠] x
: the filternhds_within x {x}ᶜ
of punctured neighborhoods ofx
.
Implementation notes #
Topology in mathlib heavily uses filters (even more than in Bourbaki). See explanations in https://leanprover-community.github.io/theories/topology.html.
References #
Tags #
topological space, interior, closure, frontier, neighborhood, continuity, continuous function
Topological spaces #
- is_open : set α → Prop
- is_open_univ : topological_space.is_open set.univ
- is_open_inter : ∀ (s t : set α), topological_space.is_open s → topological_space.is_open t → topological_space.is_open (s ∩ t)
- is_open_sUnion : ∀ (s : set (set α)), (∀ (t : set α), t ∈ s → topological_space.is_open t) → topological_space.is_open (⋃₀ s)
A topology on α
.
Instances of this typeclass
- uniform_space.to_topological_space
- empty.topological_space
- pempty.topological_space
- punit.topological_space
- bool.topological_space
- nat.topological_space
- int.topological_space
- fin.topological_space
- sierpinski_space
- subtype.topological_space
- quot.topological_space
- quotient.topological_space
- prod.topological_space
- sum.topological_space
- sigma.topological_space
- Pi.topological_space
- ulift.topological_space
- additive.topological_space
- multiplicative.topological_space
- order_dual.topological_space
- cofinite_topology.topological_space
- connected_components.topological_space
- separation_quotient.topological_space
- mul_opposite.topological_space
- add_opposite.topological_space
- units.topological_space
- add_units.topological_space
- uniform_fun.topological_space
- uniform_on_fun.topological_space
- quotient_group.quotient.topological_space
- quotient_add_group.quotient.topological_space
- nnreal.topological_space
- ennreal.topological_space
- continuous_map.compact_open
- path.topological_space
- continuous_linear_map.topological_space
- topological_ring_quotient_topology
- matrix.topological_space
- add_circle.topological_space
- sign_type.topological_space
- weak_bilin.topological_space
- weak_dual.topological_space
- weak_space.topological_space
Instances of other typeclasses for topological_space
- subsingleton.unique_topological_space
- topological_space.has_sizeof_inst
- topological_space.partial_order
- topological_space.complete_lattice
- inhabited_topological_space
A constructor for topologies by specifying the closed sets, and showing that they satisfy the appropriate conditions.
Equations
- topological_space.of_closed T empty_mem sInter_mem union_mem = {is_open := λ (X : set α), Xᶜ ∈ T, is_open_univ := _, is_open_inter := _, is_open_sUnion := _}
is_open s
means that s
is open in the ambient topological space on α
Equations
Instances for is_open
A set is closed if its complement is open
Instances of this typeclass
Interior of a set #
Closure of a set #
Alias of the forward direction of closure_nonempty_iff
.
Alias of the reverse direction of closure_nonempty_iff
.
The closure of a set s
is dense if and only if s
is dense.
Alias of the forward direction of dense_closure
.
Alias of the reverse direction of dense_closure
.
Complement to a singleton is dense if and only if the singleton is not an open set.
Frontier of a set #
The complement of a set has the same frontier as the original set.
The frontier of a set is closed.
The frontier of a closed set has no interior point.
Neighborhoods #
A set is called a neighborhood of a
if it contains an open set around a
. The set of all
neighborhoods of a
forms a filter, the neighborhood filter at a
, is here defined as the
infimum over the principal filters of all open sets containing a
.
Instances for nhds
The "neighborhood within" filter. Elements of 𝓝[s] a
are sets containing the
intersection of s
and a neighborhood of a
.
Equations
- nhds_within a s = nhds a ⊓ filter.principal s
Instances for nhds_within
- topological_space.is_countably_generated_nhds_within
- nhds_within_Ici_self_ne_bot
- nhds_within_Iic_self_ne_bot
- tendsto_Ixx_nhds_within
- nhds_within_Ioi_self_ne_bot
- nhds_within_Iio_self_ne_bot
- nhds_within.filter.ne_bot
- ennreal.nhds_within_Ioi_coe_ne_bot
- ennreal.nhds_within_Ioi_zero_ne_bot
- normed_field.punctured_nhds_ne_bot
- normed_field.nhds_within_is_unit_ne_bot
- real.punctured_nhds_module_ne_bot
To show a filter is above the neighborhood filter at a
, it suffices to show that it is above
the principal filter of some open set s
containing a
.
If a predicate is true in a neighborhood of a
, then it is true for a
.
The open neighborhoods of a
are a basis for the neighborhood filter. See nhds_basis_opens
for a variant using open sets around a
instead.
If a predicate is true in a neighbourhood of a
, then for y
sufficiently close
to a
this predicate is true in a neighbourhood of y
.
If two functions are equal in a neighbourhood of a
, then for y
sufficiently close
to a
these functions are equal in a neighbourhood of y
.
If f x ≤ g x
in a neighbourhood of a
, then for y
sufficiently close to a
we have
f x ≤ g x
in a neighbourhood of y
.
Cluster points #
In this section we define cluster points (also known as limit points and accumulation points) of a filter and of a sequence.
A point x
is a cluster point of a filter F
if 𝓝 x ⊓ F ≠ ⊥
. Also known as
an accumulation point or a limit point, but beware that terminology varies. This
is not the same as asking 𝓝[≠] x ⊓ F ≠ ⊥
. See mem_closure_iff_cluster_pt
in particular.
Equations
- cluster_pt x F = (nhds x ⊓ F).ne_bot
x
is a cluster point of a set s
if every neighbourhood of x
meets s
on a nonempty
set. See also mem_closure_iff_cluster_pt
.
A point x
is a cluster point of a sequence u
along a filter F
if it is a cluster point
of map u F
.
Equations
- map_cluster_pt x F u = cluster_pt x (filter.map u F)
x
is an accumulation point of a set C
iff it is a cluster point of C ∖ {x}
.
x
is an accumulation point of a set C
iff
there are points near x
in C
and different from x
.
If x
is an accumulation point of F
and F ≤ G
, then
x
is an accumulation point of `D.
Interior, closure and frontier in terms of neighborhoods #
Alias of the reverse direction of mem_closure_iff_frequently
.
The set of cluster points of a filter is closed. In particular, the set of limit points of a sequence is closed.
If x
is not an isolated point of a topological space, then {x}ᶜ
is dense in the whole
space.
If x
is not an isolated point of a topological space, then the closure of {x}ᶜ
is the whole
space.
If x
is not an isolated point of a topological space, then the interior of {x}
is empty.
The intersection of an open dense set with a dense set is a dense set.
The intersection of a dense set with an open dense set is a dense set.
Suppose that f
sends the complement to s
to a single point a
, and l
is some filter.
Then f
tends to a
along l
restricted to s
if and only if it tends to a
along l
.
Limits of filters in topological spaces #
If F
is an ultrafilter, then filter.ultrafilter.Lim F
is a limit of the filter, if it exists.
Note that dot notation F.Lim
can be used for F : ultrafilter α
.
Equations
- ultrafilter.Lim = λ (F : ultrafilter α), Lim' ↑F
If a filter f
is majorated by some 𝓝 a
, then it is majorated by 𝓝 (Lim f)
. We formulate
this lemma with a [nonempty α]
argument of Lim
derived from h
to make it useful for types
without a [nonempty α]
instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance.
If g
tends to some 𝓝 a
along f
, then it tends to 𝓝 (lim f g)
. We formulate
this lemma with a [nonempty α]
argument of lim
derived from h
to make it useful for types
without a [nonempty α]
instance. Because of the built-in proof irrelevance, Lean will unify
this instance with any other instance.
Continuity #
A function between topological spaces is continuous if the preimage of every open set is open. Registered as a structure to make sure it is not unfolded by Lean.
A function between topological spaces is continuous at a point x₀
if f x
tends to f x₀
when x
tends to x₀
.
Equations
- continuous_at f x = filter.tendsto f (nhds x) (nhds (f x))
See also interior_preimage_subset_preimage_interior
.
A version of continuous.tendsto
that allows one to specify a simpler form of the limit.
E.g., one can write continuous_exp.tendsto' 0 1 exp_zero
.
If a continuous map f
maps s
to t
, then it maps closure s
to closure t
.
If a continuous map f
maps s
to a closed set t
, then it maps closure s
to t
.
Function with dense range #
f : ι → β
has dense range if its range (image) is a dense subset of β.
Equations
- dense_range f = dense (set.range f)
A surjective map has dense range.
The image of a dense set under a continuous map with dense range is a dense set.
If f
has dense range and s
is an open set in the codomain of f
, then the image of the
preimage of s
under f
is dense in s
.
If a continuous map with dense range maps a dense set to a subset of t
, then t
is a dense
set.
Composition of a continuous map with dense range and a function with dense range has dense range.
Given a function f : α → β
with dense range and b : β
, returns some a : α
.
Equations
- hf.some b = classical.choice _
The library contains many lemmas stating that functions/operations are continuous. There are many
ways to formulate the continuity of operations. Some are more convenient than others.
Note: for the most part this note also applies to other properties
(measurable
, differentiable
, continuous_on
, ...).
The traditional way #
As an example, let's look at addition (+) : M → M → M
. We can state that this is continuous
in different definitionally equal ways (omitting some typing information)
continuous (λ p, p.1 + p.2)
;continuous (function.uncurry (+))
;continuous ↿(+)
. (↿
is notation for recursively uncurrying a function)
However, lemmas with this conclusion are not nice to use in practice because
- They confuse the elaborator. The following two examples fail, because of limitations in the elaboration process.
variables {M : Type*} [has_add M] [topological_space M] [has_continuous_add M]
example : continuous (λ x : M, x + x) :=
continuous_add.comp _
example : continuous (λ x : M, x + x) :=
continuous_add.comp (continuous_id.prod_mk continuous_id)
The second is a valid proof, which is accepted if you write it as
continuous_add.comp (continuous_id.prod_mk continuous_id : _)
- If the operation has more than 2 arguments, they are impractical to use, because in your application the arguments in the domain might be in a different order or associated differently.
The convenient way #
A much more convenient way to write continuity lemmas is like continuous.add
:
continuous.add {f g : X → M} (hf : continuous f) (hg : continuous g) : continuous (λ x, f x + g x)
The conclusion can be continuous (f + g)
, which is definitionally equal.
This has the following advantages
- It supports projection notation, so is shorter to write.
continuous.add _ _
is recognized correctly by the elaborator and gives useful new goals.- It works generally, since the domain is a variable.
As an example for an unary operation, we have continuous.neg
.
continuous.neg {f : α → G} (hf : continuous f) : continuous (λ x, -f x)
For unary functions, the elaborator is not confused when applying the traditional lemma
(like continuous_neg
), but it's still convenient to have the short version available (compare
hf.neg.neg.neg
with continuous_neg.comp $ continuous_neg.comp $ continuous_neg.comp hf
).
As a harder example, consider an operation of the following type:
The precise definition is not important, only its type. The correct continuity principle for this operation is something like this:
{f : X → F} {γ γ' : ∀ x, path (f x) (f x)} {t₀ s : X → I}
(hγ : continuous ↿γ) (hγ' : continuous ↿γ')
(ht : continuous t₀) (hs : continuous s) :
continuous (λ x, strans (γ x) (γ' x) (t x) (s x))
Note that all arguments of strans
are indexed over X
, even the basepoint x
, and the last
argument s
that arises since path x x
has a coercion to I → F
. The paths γ
and γ'
(which
are unary functions from I
) become binary functions in the continuity lemma.
Summary #
- Make sure that your continuity lemmas are stated in the most general way, and in a convenient
form. That means that:
- The conclusion has a variable
X
as domain (not something likeY × Z
); - Wherever possible, all point arguments
c : Y
are replaced by functionsc : X → Y
; - All
n
-ary function arguments are replaced byn+1
-ary functions (f : Y → Z
becomesf : X → Y → Z
); - All (relevant) arguments have continuity assumptions, and perhaps there are additional assumptions needed to make the operation continuous;
- The function in the conclusion is fully applied.
- The conclusion has a variable
- These remarks are mostly about the format of the conclusion of a continuity lemma.
In assumptions it's fine to state that a function with more than 1 argument is continuous using
↿
orfunction.uncurry
.
Functions with discontinuities #
In some cases, you want to work with discontinuous functions, and in certain expressions they are
still continuous. For example, consider the fractional part of a number, fract : ℝ → ℝ
.
In this case, you want to add conditions to when a function involving fract
is continuous, so you
get something like this: (assumption hf
could be weakened, but the important thing is the shape
of the conclusion)
lemma continuous_on.comp_fract {X Y : Type*} [topological_space X] [topological_space Y]
{f : X → ℝ → Y} {g : X → ℝ} (hf : continuous ↿f) (hg : continuous g) (h : ∀ s, f s 0 = f s 1) :
continuous (λ x, f x (fract (g x)))
With continuous_at
you can be even more precise about what to prove in case of discontinuities,
see e.g. continuous_at.comp_div_cases
.
Lean's elaborator has trouble elaborating applications of lemmas that state that the composition of
two functions satisfy some property at a point, like continuous_at.comp
/ cont_diff_at.comp
and
cont_mdiff_within_at.comp
. The reason is that a lemma like this looks like
continuous_at g (f x) → continuous_at f x → continuous_at (g ∘ f) x
.
Since Lean's elaborator elaborates the arguments from left-to-right, when you write hg.comp hf
,
the elaborator will try to figure out both f
and g
from the type of hg
. It tries to figure
out f
just from the point where g
is continuous. For example, if hg : continuous_at g (a, x)
then the elaborator will assign f
to the function prod.mk a
, since in that case f x = (a, x)
.
This is undesirable in most cases where f
is not a variable. There are some ways to work around
this, for example by giving f
explicitly, or to force Lean to elaborate hf
before elaborating
hg
, but this is annoying.
Another better solution is to reformulate composition lemmas to have the following shape
continuous_at g y → continuous_at f x → f x = y → continuous_at (g ∘ f) x
.
This is even useful if the proof of f x = y
is rfl
.
The reason that this works better is because the type of hg
doesn't mention f
.
Only after elaborating the two continuous_at
arguments, Lean will try to unify f x
with y
,
which is often easy after having chosen the correct functions for f
and g
.
Here is an example that shows the difference:
example {x₀ : α} (f : α → α → β) (hf : continuous_at (function.uncurry f) (x₀, x₀)) :
continuous_at (λ x => f x x) x₀ :=
-- hf.comp x (continuous_at_id.prod continuous_at_id) -- type mismatch
-- hf.comp_of_eq (continuous_at_id.prod continuous_at_id) rfl -- works