As an exercise, I wanted to try formalizing the topologist's sine curve (just the connectedness/path connectedness conditions, there is not currently a predicate for local connectedness in Mathlib) and I was surprised by how hard it was. In retrospect, I think the usual textbook proof leans heavily on the visual intuition and skips a lot of the boring details of moving between the image/preimage. Connectedness was easy, but path connectedness is what took some effort. Working with a Path constructed by choice and its input unitInterval being a subtype introduced some nitpicky inequalities that you don't think about in the informal version.
I realize it's a bit long, but I wondered if anyone had ideas on how to cut out some of the work I'm doing. In particular I wasn't sure if there was a better way of dealing with subtypes or if I could have been using non-metric parts of the Topology API to better effect.
To state the proof in words: I start by contradiction to get a path from the origin, find the supremum of the set of values for which the path is at the origin (zeroSup), then find a contradiction by showing that for any delta, there exists a value outside of the epsilon-ball at the supremum.
EDIT: One thing I considered is re-normalizing the path so that (path x).1 = x, but this seemed like it may be as much work as it saves.
importMathlib.Analysis.Normed.Order.LatticeimportMathlib.Data.Real.Pi.BoundsimportMathlib.Data.Real.StarOrderedimportMathlib.Geometry.Manifold.Instances.RealimportMathlib.Order.CompletePartialOrderimportMathlib.Tactic.Rify-- #22684/-- If a function is continuous on the union of two sets, it is continuous on both of them. -/theoremcontinuousOn_of_union{XY:Type*}[TopologicalSpaceX][TopologicalSpaceY]{st:SetX}{f:X→Y}(h:ContinuousOnf(s∪t)):ContinuousOnfs∧ContinuousOnft:=⟨funxhx↦continuousWithinAt_union.mp(hx(Set.mem_union_leftthx))|>.1,funxhx↦continuousWithinAt_union.mp(hx(Set.mem_union_rightshx))|>.2⟩noncomputableabbrevfn(x:ℝ):ℝ×ℝ:=⟨idx,Real.sinx⁻¹⟩abbrevfn_domain:=Set.Ioi(0:ℝ)defcurve:=fn''fn_domain∪{(0,0)}privatelemmasine_diverges(xy:ℝ)(hx:x>0)(hy:-1≤y∧y≤1):∃z:ℝ,(0<z∧z<x)∧Real.sinz⁻¹=y:=byobtain⟨n,hn⟩:=exists_nat_gtx⁻¹use(Real.arcsiny+(n*(2*Real.pi)))⁻¹constructor·have:=Real.monotone_arcsinhy.1simponly[Real.arcsin_neg,Real.arcsin_one]atthishave:1≤(n:ℝ):=byhave:0<n:=byrify;linarith[inv_pos_of_poshx]exactNat.one_le_cast.mprthisconstructor·applyinv_pos.mprhave:Real.pi<n*(2*Real.pi):=bynlinarith[Real.pi_gt_d2]linarith[Real.pi_gt_d2]·have:x⁻¹<Real.arcsiny+n*(2*Real.pi):=bynlinarith[Real.pi_gt_d2]exactinv_lt_of_inv_lt₀hxthis·simp[Real.sin_arcsin'hy]theoremconnected:IsConnectedcurve:=byhaverange:IsConnected(fn''fn_domain):=have:ContinuousOn(funx=>Real.sinx⁻¹)(Set.Ioi0):=byhaveh₁:ContinuousOn(funx=>Real.sinx⁻¹){0}ᶜ:=ContinuousOn.comp'Real.continuousOn_sincontinuousOn_inv₀(Set.mapsTo'.mprfun_x=>x)rw[Eq.symmSet.Iio_union_Ioi]ath₁exactcontinuousOn_of_unionh₁|>.2have:ContinuousOnfnfn_domain:=ContinuousOn.prodcontinuousOn_idthisIsConnected.imageisConnected_Ioifnthishave:curve⊆closure(fn''fn_domain):=byhave:0∈closure(fn''fn_domain):=byapplymem_closure_iff.mprintroshopenhmemobtain⟨ε,hε,hball⟩:=Metric.isOpen_iff.mphopen0hmemobtain⟨x,hx₁,hx₂⟩:=sine_divergesε0hε(bysimp)have:(x,0)∈s:=byhave:(x,0)∈Metric.ball(0:ℝ×ℝ)ε:=bysimponly[Prod.mk_zero_zero,Metric.mem_ball,dist_zero_right,Prod.norm_mk,Real.norm_eq_abs,norm_zero,abs_nonneg,sup_of_le_left,abs_lt]constructor<;>linarithexacthballthisuse⟨x,Real.sinx⁻¹⟩simp[hx₁,hx₂,this]applySet.union_subsetsubset_closuresimp[this]exactIsConnected.subset_closurerangeSet.subset_union_leftthistheoremnot_path_connected:¬IsPathConnectedcurve:=byby_contrahpathhave:JoinedIncurve0(1,Real.sin1):=byhaveh₁:0∈curve:=bysimp[curve]haveh₂:(1,Real.sin1)∈curve:=bysimp[curve]exactIsPathConnected.joinedInhpath0h₁(1,Real.sin1)h₂obtain⟨path,path_mem⟩:=thisletzero:Set(ℝ×ℝ):={0}×ˢSet.univletzero_preimage:=path⁻¹'zerohave:Fact((0:ℝ)≤1):=Fact.Manifold.instLeRealletzeroSup:=sSupzero_preimagehavezeroSup_mem:zeroSup∈zero_preimage:=byhaveh₁:IsClosedzero_preimage:=IsClosed.preimagepath.continuous(IsClosed.prodisClosed_singletonisClosed_univ)have:zero_preimage.Nonempty:=byuse0;simp[zero_preimage,zero]exactIsClosed.sSup_memthish₁havepath₁_zeroSup:(Prod.fst∘path)zeroSup=0:=byhave(x:unitInterval)(hx:x∈zero_preimage):(Prod.fst∘path)x=0:=bysimponly[Prod.mk_zero_zero,Set.singleton_prod,Set.image_univ,Set.mem_preimage,Set.mem_range,zero_preimage,zero]athxobtain⟨y,hy⟩:=hxsimp[←hy]exactthiszeroSupzeroSup_memhavepath_zeroSup:pathzeroSup=0:=byhave:=path_memzeroSupsimponly[id_eq,Prod.mk_zero_zero,Set.Icc_self,Set.union_singleton,Set.mem_insert_iff,Set.mem_image,curve,path₁_zeroSup]atthisrcasesthiswithh|⟨y,⟨hy₁,hy₂⟩⟩·exacth·exfalsohave:=(Prod.ext_iff.mphy₂).1have:(pathzeroSup).1≠0:=byrw[←this];exactNe.symm(ne_of_lthy₁)contradictionobtain⟨δ,hδ₁,hδ₂⟩:=Metric.continuous_iff.mppath.continuouszeroSup1Real.zero_lt_oneletzeroSup_plus_δ:unitInterval:=ifh:zeroSup+(δ/2)≤1thenhave:zeroSup+(δ/2)∈unitInterval:=bysimponly[Set.mem_Icc,h,and_true]have:(zeroSup:ℝ)≥0:=unitInterval.nonneg'linarith⟨zeroSup+(δ/2),this⟩else1havezeroSup_lt:zeroSup<zeroSup_plus_δ:=bydsimp[zeroSup_plus_δ]split·changezeroSup<zeroSup+δ/2linarith·have:zeroSup≠1:=byhave:1∉zero_preimage:=bysimp[zero_preimage,zero]exactne_of_mem_of_not_memzeroSup_memthisexactunitInterval.lt_one_iff_ne_one.mprthisletpt:=pathzeroSup_plus_δhavept₁_gt_zero:0<pt.1:=byhavepath_gt_zeroSup(x:unitInterval)(hx:x>zeroSup):0<(pathx).1:=byhave:∀p∈curve,p.1≥0:=bysimponly[id_eq,Prod.mk_zero_zero,Set.Icc_self,Set.union_singleton,Set.mem_insert_iff,Set.mem_image,Set.mem_Ioi,forall_eq_or_imp,Prod.fst_zero,le_refl,forall_exists_index,and_imp,forall_apply_eq_imp_iff₂,true_and,curve]introslinarithhaveh₁:(pathx).1≥0:=this(pathx)(path_memx)haveh₂:(pathx).1≠0:=byby_contrahhave:x∈zero_preimage:=bysimp[zero_preimage,zero,←h]have:¬(x>zeroSup):=not_lt_of_ge(CompleteLattice.le_sSupzero_preimagexthis)contradictionexactlt_of_le_of_neh₁(Ne.symmh₂)exactpath_gt_zeroSupzeroSup_plus_δzeroSup_ltobtain⟨x,hx₁,hx₂⟩:=sine_divergespt.11pt₁_gt_zero(bysimp)have:x∈(Prod.fst∘path)''(Set.IoozeroSupzeroSup_plus_δ):=byhaveh₁:Set.Ioo(0:ℝ)pt.1⊆(Prod.fst∘path)''(Set.IoozeroSupzeroSup_plus_δ):=byhavehp:(Prod.fst∘path)zeroSup_plus_δ=pt.1:=rflhave:ContinuousOn(Prod.fst∘path)(Set.IcczeroSupzeroSup_plus_δ):=Continuous.continuousOn(Continuous.compcontinuous_fstpath.continuous)have:=intermediate_value_Ioo(le_of_ltzeroSup_lt)thisrwa[path₁_zeroSup,hp]atthishaveh₂:x∈Set.Ioo0pt.1:=byrwa[Set.mem_Ioo]exacth₁h₂obtain⟨xinv,hxinv₁,hxinv₂⟩:=thishavedist_xinv:distxinvzeroSup<δ:=byhave:distzeroSup_plus_δzeroSup<δ:=bydsimp[zeroSup_plus_δ]split·changedist(zeroSup+δ/2)(zeroSup)<δsimp[abs_of_nonneg,le_of_lt,hδ₁]·simponly[dist,Set.Icc.coe_one,unitInterval.one_minus_nonnegzeroSup,abs_of_nonneg]linarithhave:distxinvzeroSup≤distzeroSup_plus_δzeroSup:=bysimp[dist,abs_of_nonneg,le_of_lt,Set.mem_Ioo.mphxinv₁,zeroSup_lt]linarithhavepath_xinv:pathxinv=⟨x,1⟩:=bysimponly[Prod.mk_zero_zero,Function.comp_apply]athxinv₂applyProd.exthxinv₂have:=path_memxinvsimponly[Prod.mk_zero_zero,Set.Icc_self,Set.union_singleton,Set.mem_insert_iff,Set.mem_image,curve]atthisrcasesthiswithh|⟨y,⟨hy₁,hy₂⟩⟩·exfalsohave:(pathxinv).1=0:=(Prod.ext_iff.mph).1linarith·have:=Prod.ext_iff.mphy₂simponly[id_eq,hxinv₂]atthissimp[←hx₂,←this.2,this.1]specializehδ₂xinvdist_xinvsimp[path_zeroSup,path_xinv]athδ₂
I thought about it, it's a well-known example, but given the amount of fiddly manipulation required I wasn't sure if it's a good candidate. If others think it'd make a good candidate I'd be happy to
In any case, here are some ideas for golfing/stylistic comments:
you can use dot notation to make your code more concise
For example, IsConnected.image isConnected_Ioi fn this can equivalently be written as isConnected_Ioi.image fn this: Lean knows that isConnected_Ioi is type IsConnected ...,
and deduces that you mean to apply the lemma IsConnected.image.
you can inline some very short haves: combined with the previous point,
could become isConnected_Ioi.image fn (continuousOn_id.prod this).
your proofs have a lot of have's: often, it makes sense to promote them to individual lemmas
(Mathlib code has a lot more lemmas than you'd see in a textbook or paper.)
Thanks for the feedback! Originally I thought, there's no way I can split this up more, the logic is too intertwined, but that was clearly wrong. I did manage to split it up more so the proof of non-path-connectedness is now actually fairly short, now the path-renormalization is the ugliest bit, but still, I feel like I've learned more about how to work with mathlib in all of it this. I'll post the current state in case anyone cares to offer more feedback.
importMathlib.Analysis.Normed.Order.LatticeimportMathlib.Data.Real.Pi.BoundsimportMathlib.Data.Real.StarOrderedimportMathlib.Geometry.Manifold.Instances.RealimportMathlib.Order.CompletePartialOrderimportMathlib.Tactic.Rify-- #22684/-- If a function is continuous on the union of two sets, it is continuous on both of them. -/theoremcontinuousOn_of_union{XY:Type*}[TopologicalSpaceX][TopologicalSpaceY]{st:SetX}{f:X→Y}(h:ContinuousOnf(s∪t)):ContinuousOnfs∧ContinuousOnft:=⟨funxhx↦continuousWithinAt_union.mp(hx(Set.mem_union_leftthx))|>.1,funxhx↦continuousWithinAt_union.mp(hx(Set.mem_union_rightshx))|>.2⟩noncomputableabbrevfn(x:ℝ):ℝ×ℝ:=⟨x,Real.sinx⁻¹⟩abbrevfn_domain:=Set.Ioi(0:ℝ)abbrevcurve:=fn''fn_domain∪{0}privatelemmasine_diverges(xy:ℝ)(hx:x>0)(hy:-1≤y∧y≤1):∃z:ℝ,(0<z∧z<x)∧Real.sinz⁻¹=y:=byobtain⟨n,hn⟩:=exists_nat_gtx⁻¹use(Real.arcsiny+(n*(2*Real.pi)))⁻¹constructor·have:=Real.monotone_arcsinhy.1simponly[Real.arcsin_neg,Real.arcsin_one]atthishave:1≤(n:ℝ):=byhave:0<n:=byrify;linarith[inv_pos_of_poshx]exactNat.one_le_cast.mprthisconstructor·applyinv_pos.mprnlinarith[Real.pi_gt_d2]·have:x⁻¹<Real.arcsiny+n*(2*Real.pi):=bynlinarith[Real.pi_gt_d2]exactinv_lt_of_inv_lt₀hxthis·simp[Real.sin_arcsin'hy]theoremconnected:IsConnectedcurve:=byhaverange:IsConnected(fn''fn_domain):=byapplyisConnected_Ioi.imagefn<|continuousOn_id.prod_applyContinuousOn.mono·exactReal.continuousOn_sin.comp'continuousOn_inv₀(Set.mapsTo'.mprfun_x↦x)·simphave:curve⊆closure(fn''fn_domain):=byrefineSet.union_subsetsubset_closure<|Set.singleton_subset_iff.mpr<|mem_closure_iff.mpr?_introshopenhmemobtain⟨ε,hε,hball⟩:=Metric.isOpen_iff.mphopen0hmemobtain⟨x,hx₁,hx₂⟩:=sine_divergesε0hε(bysimp)use⟨x,Real.sinx⁻¹⟩have:(x,0)∈s:=byapplyhballsimp[abs_lt,hx₁,show-ε<xbylinarith]simp[hx₂,hx₁,this]exactIsConnected.subset_closurerangeSet.subset_union_leftthislemmarenormalize_path2{X:Type*}[TopologicalSpaceX][T1SpaceX]{xy:X}{F:SetX}(joined:JoinedInFxy)(h:x≠y):∃p:Pathxy,(∀i,pi∈F)∧∀i>0,pi≠x:=byhaveI:Fact((0:ℝ)≤1):=Fact.Manifold.instLeReal-- TODOobtain⟨path,path_mem⟩:=joinedletpreimage:=path⁻¹'{x}letxSup:=sSuppreimagehavexSup_mem:xSup∈preimage:=byhave:preimage.Nonempty:=byuse0;simp[preimage]exactIsClosed.sSup_memthis<|IsClosed.preimagepath.continuousisClosed_singletonhavepath_xSup_eq:pathxSup=x:=bysimpaonly[Set.mem_preimage,Set.mem_singleton_iff,preimage]usingxSup_memletδ:ℝ:=1-xSuphavelt_δ:0<δ:=have:xSup<1:=byrefineunitInterval.lt_one_iff_ne_one.mpr?_by_contrahhsimp[Ne.symmh,hh]atpath_xSup_eqsub_pos.mprthishave(i:unitInterval):(xSup:ℝ)+(i:ℝ)*δ∈unitInterval:=byhave:δ*(i:ℝ)≤1-xSup:=mul_le_iff_le_one_rightlt_δ|>.mpr<|unitInterval.le_oneihave:0≤(xSup:ℝ):=unitInterval.nonneg'have:0≤(i:ℝ):=unitInterval.nonneg'constructor·positivity·linarithletnewPath:Pathxy:={toFun:=funi↦path⟨xSup+i*δ,thisi⟩continuous_toFun:=byrefinepath.continuous.comp'<|Continuous.subtype_mk?_thisexactcontinuous_const.add<|continuous_subtype_val.mulcontinuous_constsource':=bysimp[path_xSup_eq]target':=bysimp[δ]}usenewPathconstructor·simp[newPath,path_mem]·introihihave:(i:ℝ)*δ>0:=mul_poshilt_δhave:xSup+(i:ℝ)*δ>xSup:=bylinarithhaveh(y:unitInterval)(h:y>xSup):pathy≠x:=byby_contrahcontrahave:y∈preimage:=bysimp[preimage,hcontra]have:¬sSuppreimage<y:=bysimp[le_sSupthis]simp[xSup]athcontradictionexacth_thislemmapath_intermediate_value{xy:ℝ×ℝ}{i₁i₂:unitInterval}{pp₁p₂:ℝ}(path:Pathxy)(hi:i₁<i₂)(hp₁:(pathi₁).1=p₁)(hp₂:(pathi₂).1=p₂)(hp:p₁<p∧p<p₂):∃i,(i₁<i∧i<i₂)∧(pathi).1=p:=byhaveI:Fact((0:ℝ)≤1):=Fact.Manifold.instLeReal-- TODOhaveh₁:Set.Ioop₁p₂⊆(Prod.fst∘path)''(Set.Iooi₁i₂):=byhave:=intermediate_value_Ioo(le_of_lthi)<|continuous_fst.comppath.continuous|>.continuousOnsimpa[hp₁,hp₂]usingthishaveh₂:p∈Set.Ioop₁p₂:=byrwa[Set.mem_Ioo]exacth₁h₂lemmacurve_point_gt_zero{x:ℝ×ℝ}(h₁:x∈curve)(h₂:x.1>0):x=(x.1,Real.sin(x.1)⁻¹):=bysimponly[Prod.mk_zero_zero,Set.Icc_self,Set.union_singleton,Set.mem_insert_iff,Set.mem_image,curve]ath₁rcasesh₁withh|⟨y,⟨hy₁,hy₂⟩⟩·exfalsohave:x.1=0:=(Prod.ext_iff.mph).1linarith·have:=Prod.ext_iff.mphy₂simponly[id_eq]atthisapplyProd.ext<;>simp[←this.2,this.1]theoremnot_path_connected:¬IsPathConnectedcurve:=byhaveI:Fact((0:ℝ)≤1):=Fact.Manifold.instLeReal-- TODOby_contrahpconnhave:=hpconn.joinedIn0(bysimp)(1,Real.sin1)(bysimp)obtain⟨path,path_mem,hpath⟩:=renormalize_path2this(bysimp[Prod.ext_iff])obtain⟨δ,hδ₁,hδ₂⟩:=Metric.continuous_iff.mppath.continuous01Real.zero_lt_oneletδstep:unitInterval:=⟨δ/2⊓1,bysimp;linarith⟩haveδstep_gt:0<δstep:=bysimp[δstep,Subtype.mk_lt_mk.mpr,hδ₁]letpt:=pathδstephavept₁_gt_zero:0<pt.1:=havepath_gt(y:unitInterval)(hy:y>0):0<(pathy).1:=byhave:∀p∈curve,p=0∨0<p.1:=bysimp_allrcasesthis(pathy)(path_mem_)withh|h·exactFalse.elim(hpathyhyh)·exacthpath_gtδstepδstep_gtobtain⟨x,hx₁,hx₂⟩:=sine_divergespt.11pt₁_gt_zero(bysimp)obtain⟨xinv,hxinv₁,hxinv₂⟩:=path_intermediate_valuepathδstep_gt(bysimp)rflhx₁havedist_xinv:distxinv0<δ:=byhave:↑xinv<δ:=lt_trans(Set.mem_Ioo.mphxinv₁).2(bysimp[δstep,hδ₁])simp[dist,abs_of_nonneg,le_of_lt,Set.mem_Ioo.mphxinv₁,this]havepath_xinv:=curve_point_gt_zero(path_memxinv)(bysimp[hxinv₂,hx₁])rw[hxinv₂,hx₂]atpath_xinvspecializehδ₂xinvdist_xinvsimp[path_xinv,hxinv₂]athδ₂
In fact, the first result generalizes to any periodic function on ℝ (and can probably be generalized even further to other domains like ordered additive commutatve monoids where Tendsto (fun n : ℕ ↦ n • c) atTop atTop where c is the period of the function, and if you get this, then you can even multiplicativize the result).