/-
Copyright © 2020 Nicolò Cavalleri. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Nicolò Cavalleri

! This file was ported from Lean 3 source module geometry.manifold.algebra.monoid
! leanprover-community/mathlib commit 3d7987cda72abc473c7cdbbb075170e9ac620042
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Geometry.Manifold.ContMdiffMap

/-!
# Smooth monoid
A smooth monoid is a monoid that is also a smooth manifold, in which multiplication is a smooth map
of the product manifold `G` × `G` into `G`.

In this file we define the basic structures to talk about smooth monoids: `has_smooth_mul` and its
additive counterpart `has_smooth_add`. These structures are general enough to also talk about smooth
semigroups.
-/

open Manifold

library_note "Design choices about smooth algebraic structures"/--
1. All smooth algebraic structures on `G` are `Prop`-valued classes that extend
`smooth_manifold_with_corners I G`. This way we save users from adding both
`[smooth_manifold_with_corners I G]` and `[has_smooth_mul I G]` to the assumptions. While many API
lemmas hold true without the `smooth_manifold_with_corners I G` assumption, we're not aware of a
mathematically interesting monoid on a topological manifold such that (a) the space is not a
`smooth_manifold_with_corners`; (b) the multiplication is smooth at `(a, b)` in the charts
`ext_chart_at I a`, `ext_chart_at I b`, `ext_chart_at I (a * b)`.

2. Because of `model_prod` we can't assume, e.g., that a `lie_group` is modelled on `𝓘(𝕜, E)`. So,
we formulate the definitions and lemmas for any model.

3. While smoothness of an operation implies its continuity, lemmas like
`has_continuous_mul_of_smooth` can't be instances becausen otherwise Lean would have to search for
`has_smooth_mul I G` with unknown `𝕜`, `E`, `H`, and `I : model_with_corners 𝕜 E H`. If users needs
`[has_continuous_mul G]` in a proof about a smooth monoid, then they need to either add
`[has_continuous_mul G]` as an assumption (worse) or use `haveI` in the proof (better). -/

-- See note [Design choices about smooth algebraic structures]
/-- Basic hypothesis to talk about a smooth (Lie) additive monoid or a smooth additive
semigroup. A smooth additive monoid over `α`, for example, is obtained by requiring both the
instances `add_monoid α` and `has_smooth_add α`. -/
class HasSmoothAdd {𝕜 : Type _} [NontriviallyNormedField 𝕜] {H : Type _} [TopologicalSpace H]
  {E : Type _} [NormedAddCommGroup E] [NormedSpace 𝕜 E] (I : ModelWithCorners 𝕜 E H) (G : Type _)
  [Add G] [TopologicalSpace G] [ChartedSpace H G] extends SmoothManifoldWithCorners I G : Prop where
  smooth_add : Smooth (I.Prod I) I fun p : G × G => p.1 + p.2
#align has_smooth_add HasSmoothAdd

-- See note [Design choices about smooth algebraic structures]
/-- Basic hypothesis to talk about a smooth (Lie) monoid or a smooth semigroup.
A smooth monoid over `G`, for example, is obtained by requiring both the instances `monoid G`
and `has_smooth_mul I G`. -/
@[to_additive]
class HasSmoothMul {𝕜 : Type _} [NontriviallyNormedField 𝕜] {H : Type _} [TopologicalSpace H]
  {E : Type _} [NormedAddCommGroup E] [NormedSpace 𝕜 E] (I : ModelWithCorners 𝕜 E H) (G : Type _)
  [Mul G] [TopologicalSpace G] [ChartedSpace H G] extends SmoothManifoldWithCorners I G : Prop where
  smooth_mul : Smooth (I.Prod I) I fun p : G × G => p.1 * p.2
#align has_smooth_mul HasSmoothMul
#align has_smooth_add HasSmoothAdd

section HasSmoothMul

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {H : Type _} [TopologicalSpace H] {E : Type _}
  [NormedAddCommGroup E] [NormedSpace 𝕜 E] {I : ModelWithCorners 𝕜 E H} {G : Type _} [Mul G]
  [TopologicalSpace G] [ChartedSpace H G] [HasSmoothMul I G] {E' : Type _} [NormedAddCommGroup E']
  [NormedSpace 𝕜 E'] {H' : Type _} [TopologicalSpace H'] {I' : ModelWithCorners 𝕜 E' H'}
  {M : Type _} [TopologicalSpace M] [ChartedSpace H' M]

section

variable (I)

@[to_additive]
theorem smooth_mul : Smooth (I.Prod I) I fun p : G × G => p.1 * p.2 :=
  HasSmoothMul.smooth_mul
#align smooth_mul smooth_mul
#align smooth_add smooth_add

/-- If the multiplication is smooth, then it is continuous. This is not an instance for technical
reasons, see note [Design choices about smooth algebraic structures]. -/
@[to_additive
      "If the addition is smooth, then it is continuous. This is not an instance for technical reasons,\nsee note [Design choices about smooth algebraic structures]."]
theorem continuousMul_of_smooth : ContinuousMul G :=
  ⟨(smooth_mul I).Continuous⟩
#align has_continuous_mul_of_smooth continuousMul_of_smooth
#align has_continuous_add_of_smooth has_continuous_add_of_smooth

end

section

variable {f g : M → G} {s : Set M} {x : M} {n : ℕ∞}

@[to_additive]
theorem ContMdiffWithinAt.mul (hf : ContMdiffWithinAt I' I n f s x)
    (hg : ContMdiffWithinAt I' I n g s x) : ContMdiffWithinAt I' I n (f * g) s x :=
  ((smooth_mul I).SmoothAt.of_le le_top).comp_contMdiffWithinAt x (hf.prod_mk hg)
#align cont_mdiff_within_at.mul ContMdiffWithinAt.mul
#align cont_mdiff_within_at.add ContMdiffWithinAt.add

@[to_additive]
theorem ContMdiffAt.mul (hf : ContMdiffAt I' I n f x) (hg : ContMdiffAt I' I n g x) :
    ContMdiffAt I' I n (f * g) x :=
  hf.mul hg
#align cont_mdiff_at.mul ContMdiffAt.mul
#align cont_mdiff_at.add ContMdiffAt.add

@[to_additive]
theorem ContMdiffOn.mul (hf : ContMdiffOn I' I n f s) (hg : ContMdiffOn I' I n g s) :
    ContMdiffOn I' I n (f * g) s := fun x hx => (hf x hx).mul (hg x hx)
#align cont_mdiff_on.mul ContMdiffOn.mul
#align cont_mdiff_on.add ContMdiffOn.add

@[to_additive]
theorem ContMdiff.mul (hf : ContMdiff I' I n f) (hg : ContMdiff I' I n g) :
    ContMdiff I' I n (f * g) := fun x => (hf x).mul (hg x)
#align cont_mdiff.mul ContMdiff.mul
#align cont_mdiff.add ContMdiff.add

@[to_additive]
theorem SmoothWithinAt.mul (hf : SmoothWithinAt I' I f s x) (hg : SmoothWithinAt I' I g s x) :
    SmoothWithinAt I' I (f * g) s x :=
  hf.mul hg
#align smooth_within_at.mul SmoothWithinAt.mul
#align smooth_within_at.add SmoothWithinAt.add

@[to_additive]
theorem SmoothAt.mul (hf : SmoothAt I' I f x) (hg : SmoothAt I' I g x) : SmoothAt I' I (f * g) x :=
  hf.mul hg
#align smooth_at.mul SmoothAt.mul
#align smooth_at.add SmoothAt.add

@[to_additive]
theorem SmoothOn.mul (hf : SmoothOn I' I f s) (hg : SmoothOn I' I g s) : SmoothOn I' I (f * g) s :=
  hf.mul hg
#align smooth_on.mul SmoothOn.mul
#align smooth_on.add SmoothOn.add

@[to_additive]
theorem Smooth.mul (hf : Smooth I' I f) (hg : Smooth I' I g) : Smooth I' I (f * g) :=
  hf.mul hg
#align smooth.mul Smooth.mul
#align smooth.add Smooth.add

@[to_additive]
theorem smooth_mul_left {a : G} : Smooth I I fun b : G => a * b :=
  smooth_const.mul smooth_id
#align smooth_mul_left smooth_mul_left
#align smooth_add_left smooth_add_left

@[to_additive]
theorem smooth_mul_right {a : G} : Smooth I I fun b : G => b * a :=
  smooth_id.mul smooth_const
#align smooth_mul_right smooth_mul_right
#align smooth_add_right smooth_add_right

end

variable (I) (g h : G)

/-- Left multiplication by `g`. It is meant to mimic the usual notation in Lie groups.
Lemmas involving `smooth_left_mul` with the notation `𝑳` usually use `L` instead of `𝑳` in the
names. -/
def smoothLeftMul : C^∞⟮I, G; I, G⟯ :=
  ⟨leftMul g, smooth_mul_left⟩
#align smooth_left_mul smoothLeftMul

/-- Right multiplication by `g`. It is meant to mimic the usual notation in Lie groups.
Lemmas involving `smooth_right_mul` with the notation `𝑹` usually use `R` instead of `𝑹` in the
names. -/
def smoothRightMul : C^∞⟮I, G; I, G⟯ :=
  ⟨rightMul g, smooth_mul_right⟩
#align smooth_right_mul smoothRightMul

-- mathport name: smooth_left_mul
-- Left multiplication. The abbreviation is `MIL`.
scoped[LieGroup] notation "𝑳" => smoothLeftMul

-- mathport name: smooth_right_mul
-- Right multiplication. The abbreviation is `MIR`.
scoped[LieGroup] notation "𝑹" => smoothRightMul

open LieGroup

@[simp]
theorem L_apply : (𝑳 I g) h = g * h :=
  rfl
#align L_apply L_apply

@[simp]
theorem R_apply : (𝑹 I g) h = h * g :=
  rfl
#align R_apply R_apply

@[simp]
theorem L_mul {G : Type _} [Semigroup G] [TopologicalSpace G] [ChartedSpace H G] [HasSmoothMul I G]
    (g h : G) : 𝑳 I (g * h) = (𝑳 I g).comp (𝑳 I h) :=
  by
  ext
  simp only [ContMdiffMap.comp_apply, L_apply, mul_assoc]
#align L_mul L_mul

@[simp]
theorem R_mul {G : Type _} [Semigroup G] [TopologicalSpace G] [ChartedSpace H G] [HasSmoothMul I G]
    (g h : G) : 𝑹 I (g * h) = (𝑹 I h).comp (𝑹 I g) :=
  by
  ext
  simp only [ContMdiffMap.comp_apply, R_apply, mul_assoc]
#align R_mul R_mul

section

variable {G' : Type _} [Monoid G'] [TopologicalSpace G'] [ChartedSpace H G'] [HasSmoothMul I G']
  (g' : G')

theorem smoothLeftMul_one : (𝑳 I g') 1 = g' :=
  mul_one g'
#align smooth_left_mul_one smoothLeftMul_one

theorem smoothRightMul_one : (𝑹 I g') 1 = g' :=
  one_mul g'
#align smooth_right_mul_one smoothRightMul_one

end

-- Instance of product
@[to_additive]
instance HasSmoothMul.prod {𝕜 : Type _} [NontriviallyNormedField 𝕜] {E : Type _}
    [NormedAddCommGroup E] [NormedSpace 𝕜 E] {H : Type _} [TopologicalSpace H]
    (I : ModelWithCorners 𝕜 E H) (G : Type _) [TopologicalSpace G] [ChartedSpace H G] [Mul G]
    [HasSmoothMul I G] {E' : Type _} [NormedAddCommGroup E'] [NormedSpace 𝕜 E'] {H' : Type _}
    [TopologicalSpace H'] (I' : ModelWithCorners 𝕜 E' H') (G' : Type _) [TopologicalSpace G']
    [ChartedSpace H' G'] [Mul G'] [HasSmoothMul I' G'] : HasSmoothMul (I.Prod I') (G × G') :=
  { SmoothManifoldWithCorners.prod G G' with
    smooth_mul :=
      ((smooth_fst.comp smooth_fst).Smooth.mul (smooth_fst.comp smooth_snd)).prod_mk
        ((smooth_snd.comp smooth_fst).Smooth.mul (smooth_snd.comp smooth_snd)) }
#align has_smooth_mul.prod HasSmoothMul.prod
#align has_smooth_add.sum HasSmoothAdd.sum

end HasSmoothMul

section Monoid

variable {𝕜 : Type _} [NontriviallyNormedField 𝕜] {H : Type _} [TopologicalSpace H] {E : Type _}
  [NormedAddCommGroup E] [NormedSpace 𝕜 E] {I : ModelWithCorners 𝕜 E H} {G : Type _} [Monoid G]
  [TopologicalSpace G] [ChartedSpace H G] [HasSmoothMul I G] {H' : Type _} [TopologicalSpace H']
  {E' : Type _} [NormedAddCommGroup E'] [NormedSpace 𝕜 E'] {I' : ModelWithCorners 𝕜 E' H'}
  {G' : Type _} [Monoid G'] [TopologicalSpace G'] [ChartedSpace H' G'] [HasSmoothMul I' G']

theorem smooth_pow : ∀ n : ℕ, Smooth I I fun a : G => a ^ n
  | 0 => by
    simp only [pow_zero]
    exact smooth_const
  | k + 1 => by simpa [pow_succ] using smooth_id.mul (smooth_pow _)
#align smooth_pow smooth_pow

/-- Morphism of additive smooth monoids. -/
structure SmoothAddMonoidMorphism (I : ModelWithCorners 𝕜 E H) (I' : ModelWithCorners 𝕜 E' H')
  (G : Type _) [TopologicalSpace G] [ChartedSpace H G] [AddMonoid G] [HasSmoothAdd I G]
  (G' : Type _) [TopologicalSpace G'] [ChartedSpace H' G'] [AddMonoid G']
  [HasSmoothAdd I' G'] extends G →+ G' where
  smooth_toFun : Smooth I I' to_fun
#align smooth_add_monoid_morphism SmoothAddMonoidMorphism

/-- Morphism of smooth monoids. -/
@[to_additive]
structure SmoothMonoidMorphism (I : ModelWithCorners 𝕜 E H) (I' : ModelWithCorners 𝕜 E' H')
  (G : Type _) [TopologicalSpace G] [ChartedSpace H G] [Monoid G] [HasSmoothMul I G] (G' : Type _)
  [TopologicalSpace G'] [ChartedSpace H' G'] [Monoid G'] [HasSmoothMul I' G'] extends G →* G' where
  smooth_toFun : Smooth I I' to_fun
#align smooth_monoid_morphism SmoothMonoidMorphism
#align smooth_add_monoid_morphism SmoothAddMonoidMorphism

@[to_additive]
instance : One (SmoothMonoidMorphism I I' G G') :=
  ⟨{  smooth_toFun := smooth_const
      toMonoidHom := 1 }⟩

@[to_additive]
instance : Inhabited (SmoothMonoidMorphism I I' G G') :=
  ⟨1⟩

@[to_additive]
instance : CoeFun (SmoothMonoidMorphism I I' G G') fun _ => G → G' :=
  ⟨fun a => a.toFun⟩

end Monoid

section CommMonoid

open BigOperators

variable {ι 𝕜 : Type _} [NontriviallyNormedField 𝕜] {H : Type _} [TopologicalSpace H] {E : Type _}
  [NormedAddCommGroup E] [NormedSpace 𝕜 E] {I : ModelWithCorners 𝕜 E H} {G : Type _} [CommMonoid G]
  [TopologicalSpace G] [ChartedSpace H G] [HasSmoothMul I G] {E' : Type _} [NormedAddCommGroup E']
  [NormedSpace 𝕜 E'] {H' : Type _} [TopologicalSpace H'] {I' : ModelWithCorners 𝕜 E' H'}
  {M : Type _} [TopologicalSpace M] [ChartedSpace H' M] {s : Set M} {x : M} {t : Finset ι}
  {f : ι → M → G} {n : ℕ∞} {p : ι → Prop}

@[to_additive]
theorem contMdiffWithinAt_finset_prod' (h : ∀ i ∈ t, ContMdiffWithinAt I' I n (f i) s x) :
    ContMdiffWithinAt I' I n (∏ i in t, f i) s x :=
  Finset.prod_induction f (fun f => ContMdiffWithinAt I' I n f s x) (fun f g hf hg => hf.mul hg)
    contMdiffWithinAt_const h
#align cont_mdiff_within_at_finset_prod' contMdiffWithinAt_finset_prod'
#align cont_mdiff_within_at_finset_sum' contMdiffWithinAt_finset_sum'

@[to_additive]
theorem contMdiffAt_finset_prod' (h : ∀ i ∈ t, ContMdiffAt I' I n (f i) x) :
    ContMdiffAt I' I n (∏ i in t, f i) x :=
  contMdiffWithinAt_finset_prod' h
#align cont_mdiff_at_finset_prod' contMdiffAt_finset_prod'
#align cont_mdiff_at_finset_sum' contMdiffAt_finset_sum'

@[to_additive]
theorem contMdiffOn_finset_prod' (h : ∀ i ∈ t, ContMdiffOn I' I n (f i) s) :
    ContMdiffOn I' I n (∏ i in t, f i) s := fun x hx =>
  contMdiffWithinAt_finset_prod' fun i hi => h i hi x hx
#align cont_mdiff_on_finset_prod' contMdiffOn_finset_prod'
#align cont_mdiff_on_finset_sum' contMdiffOn_finset_sum'

@[to_additive]
theorem contMdiff_finset_prod' (h : ∀ i ∈ t, ContMdiff I' I n (f i)) :
    ContMdiff I' I n (∏ i in t, f i) := fun x => contMdiffAt_finset_prod' fun i hi => h i hi x
#align cont_mdiff_finset_prod' contMdiff_finset_prod'
#align cont_mdiff_finset_sum' contMdiff_finset_sum'

@[to_additive]
theorem contMdiffWithinAt_finset_prod (h : ∀ i ∈ t, ContMdiffWithinAt I' I n (f i) s x) :
    ContMdiffWithinAt I' I n (fun x => ∏ i in t, f i x) s x :=
  by
  simp only [← Finset.prod_apply]
  exact contMdiffWithinAt_finset_prod' h
#align cont_mdiff_within_at_finset_prod contMdiffWithinAt_finset_prod
#align cont_mdiff_within_at_finset_sum contMdiffWithinAt_finset_sum

@[to_additive]
theorem contMdiffAt_finset_prod (h : ∀ i ∈ t, ContMdiffAt I' I n (f i) x) :
    ContMdiffAt I' I n (fun x => ∏ i in t, f i x) x :=
  contMdiffWithinAt_finset_prod h
#align cont_mdiff_at_finset_prod contMdiffAt_finset_prod
#align cont_mdiff_at_finset_sum contMdiffAt_finset_sum

@[to_additive]
theorem contMdiffOn_finset_prod (h : ∀ i ∈ t, ContMdiffOn I' I n (f i) s) :
    ContMdiffOn I' I n (fun x => ∏ i in t, f i x) s := fun x hx =>
  contMdiffWithinAt_finset_prod fun i hi => h i hi x hx
#align cont_mdiff_on_finset_prod contMdiffOn_finset_prod
#align cont_mdiff_on_finset_sum contMdiffOn_finset_sum

@[to_additive]
theorem contMdiff_finset_prod (h : ∀ i ∈ t, ContMdiff I' I n (f i)) :
    ContMdiff I' I n fun x => ∏ i in t, f i x := fun x =>
  contMdiffAt_finset_prod fun i hi => h i hi x
#align cont_mdiff_finset_prod contMdiff_finset_prod
#align cont_mdiff_finset_sum contMdiff_finset_sum

@[to_additive]
theorem smoothWithinAt_finset_prod' (h : ∀ i ∈ t, SmoothWithinAt I' I (f i) s x) :
    SmoothWithinAt I' I (∏ i in t, f i) s x :=
  contMdiffWithinAt_finset_prod' h
#align smooth_within_at_finset_prod' smoothWithinAt_finset_prod'
#align smooth_within_at_finset_sum' smoothWithinAt_finset_sum'

@[to_additive]
theorem smoothAt_finset_prod' (h : ∀ i ∈ t, SmoothAt I' I (f i) x) :
    SmoothAt I' I (∏ i in t, f i) x :=
  contMdiffAt_finset_prod' h
#align smooth_at_finset_prod' smoothAt_finset_prod'
#align smooth_at_finset_sum' smoothAt_finset_sum'

@[to_additive]
theorem smoothOn_finset_prod' (h : ∀ i ∈ t, SmoothOn I' I (f i) s) :
    SmoothOn I' I (∏ i in t, f i) s :=
  contMdiffOn_finset_prod' h
#align smooth_on_finset_prod' smoothOn_finset_prod'
#align smooth_on_finset_sum' smoothOn_finset_sum'

@[to_additive]
theorem smooth_finset_prod' (h : ∀ i ∈ t, Smooth I' I (f i)) : Smooth I' I (∏ i in t, f i) :=
  contMdiff_finset_prod' h
#align smooth_finset_prod' smooth_finset_prod'
#align smooth_finset_sum' smooth_finset_sum'

@[to_additive]
theorem smoothWithinAt_finset_prod (h : ∀ i ∈ t, SmoothWithinAt I' I (f i) s x) :
    SmoothWithinAt I' I (fun x => ∏ i in t, f i x) s x :=
  contMdiffWithinAt_finset_prod h
#align smooth_within_at_finset_prod smoothWithinAt_finset_prod
#align smooth_within_at_finset_sum smoothWithinAt_finset_sum

@[to_additive]
theorem smoothAt_finset_prod (h : ∀ i ∈ t, SmoothAt I' I (f i) x) :
    SmoothAt I' I (fun x => ∏ i in t, f i x) x :=
  contMdiffAt_finset_prod h
#align smooth_at_finset_prod smoothAt_finset_prod
#align smooth_at_finset_sum smoothAt_finset_sum

@[to_additive]
theorem smoothOn_finset_prod (h : ∀ i ∈ t, SmoothOn I' I (f i) s) :
    SmoothOn I' I (fun x => ∏ i in t, f i x) s :=
  contMdiffOn_finset_prod h
#align smooth_on_finset_prod smoothOn_finset_prod
#align smooth_on_finset_sum smoothOn_finset_sum

@[to_additive]
theorem smooth_finset_prod (h : ∀ i ∈ t, Smooth I' I (f i)) :
    Smooth I' I fun x => ∏ i in t, f i x :=
  contMdiff_finset_prod h
#align smooth_finset_prod smooth_finset_prod
#align smooth_finset_sum smooth_finset_sum

open Function Filter

@[to_additive]
theorem contMdiff_finprod (h : ∀ i, ContMdiff I' I n (f i))
    (hfin : LocallyFinite fun i => mulSupport (f i)) : ContMdiff I' I n fun x => ∏ᶠ i, f i x :=
  by
  intro x
  rcases finprod_eventually_eq_prod hfin x with ⟨s, hs⟩
  exact (contMdiff_finset_prod (fun i hi => h i) x).congr_of_eventuallyEq hs
#align cont_mdiff_finprod contMdiff_finprod
#align cont_mdiff_finsum contMdiff_finsum

@[to_additive]
theorem contMdiff_finprod_cond (hc : ∀ i, p i → ContMdiff I' I n (f i))
    (hf : LocallyFinite fun i => mulSupport (f i)) :
    ContMdiff I' I n fun x => ∏ᶠ (i) (hi : p i), f i x :=
  by
  simp only [← finprod_subtype_eq_finprod_cond]
  exact contMdiff_finprod (fun i => hc i i.2) (hf.comp_injective Subtype.coe_injective)
#align cont_mdiff_finprod_cond contMdiff_finprod_cond
#align cont_mdiff_finsum_cond contMdiff_finsum_cond

@[to_additive]
theorem smooth_finprod (h : ∀ i, Smooth I' I (f i))
    (hfin : LocallyFinite fun i => mulSupport (f i)) : Smooth I' I fun x => ∏ᶠ i, f i x :=
  contMdiff_finprod h hfin
#align smooth_finprod smooth_finprod
#align smooth_finsum smooth_finsum

@[to_additive]
theorem smooth_finprod_cond (hc : ∀ i, p i → Smooth I' I (f i))
    (hf : LocallyFinite fun i => mulSupport (f i)) :
    Smooth I' I fun x => ∏ᶠ (i) (hi : p i), f i x :=
  contMdiff_finprod_cond hc hf
#align smooth_finprod_cond smooth_finprod_cond
#align smooth_finsum_cond smooth_finsum_cond

end CommMonoid


/-
There are two key things we do with proofs.

(1) We build them
(2) We use them

In logic, the basic rules for building,
or constructing, proofs go by the name
of introduction rules. The basic rules
for using proofs are called elimination
rules.

Each *form* of proposition has its own
introduction and elimination rules. For
example, to build a proof of P ∧ Q, one
must use the introduction rule for and,
while using a proof of P ∧ Q relies on
the two elimination rules, which serve
to extract from a proof of P ∧ Q a proof
of P or a proof of Q.

In this class, we assume (P Q : Prop),
(T : Type), (x y : T) then summarize,
review,  and practice using the rules
to build and use proofs of these forms:

- ∀ (p : P), Q  [Q usually a predicate]
    - introduction: making arbitrary assumption
    - elimination: take the predicate for any value of P you want
- x = y ⇒ eq.refl _ _
    - introduction: eq.refl _ _
    - elimination:
- P → Q
    - introduction:
- P ∧ Q
    - introduction: and.intro
    - elimination: and.elim_left and and.elim_right
- ∃ (x : P), Q  [Q usually a predicate]

A key to understanding how to build
proofs is that it's a top-down and
recursive process. For example, to
build a proof of ∀ (p : P), Q, we
use the introduction rule for ∀. It
tells us to assume that p is some
arbitrary but specific value of type
P, and then to prove Q for that p.
To do this, we recursively apply
our approach to building proofs to
build a proof of Q, but now within
a context in which we can use the
assumption that p is some specific
value of type P. We now repeat the
same process: (1) ask what form is
Q; (2) choose an introduction rule
for that form of proposition. Often
we need smaller proofs to apply the
introduction rules, and for this, we
use proofs we have already built or
assumed, using elimination rules as
necessary.
-/

def no_largest_nat := ¬∃ (n : nat), ∀ (k : nat), n > k

def append {α : Type} : (list α) → α → (list α)
| list.nil a := list.cons a list.nil
| (list.cons b t) a := list.cons b (append t a)

def list_reverse {α : Type} : (list α) → (list α)
| list.nil := list.nil
| (list.cons a t) := append (list_reverse t) a

/-
Forall introduction

Assume that you have an arbitrary but specific value of the quantified type,
(p : P), then prove Q for p.

Example: prove "all balls are green"

Step 0: formalize the proposition

∀ (b : Ball), Green b

Step 1: We start by assuming
that b0 is an arbitrary but specific ball,
and now all that remains to be proved is
Green b.

Formally, we just need to prove Green b0

Example: prove ∀ (n : ℕ), n = n.

In english: prove that, for all natural numbers n, n = n.

Proof.
Assume an arbitrary but specific natural number n. What remains to be shown is that n=n.
By the reflexive property of equality, any natural number equals itself, so n = n.
Therefore, for all natural numbers n, n = n.
-/

theorem pf : ∀ (n : nat), n = n := eq.refl

#check ∀ (k : ℕ), k = k

example : ∀ (n : ℕ), nat.pred (nat.succ n) = n :=
begin
    assume n0,
    apply eq.refl
end

/- ∀ elimination
Given a proof that every ball is green, we can conclude that any ball of your choice is green

If every ball is green, then we can prove that b0 is green

Proof:

Assume that every ball is green.
Assume b0 is a ball.
By our assumption that every ball is green, b0 is green.
-/

example : ((∀ (n : nat), n = n) → (2 = 2)) := (λ (pf), pf _)

inductive Ball : Type
| mk : Ball

inductive Green : Ball → Prop

def to_prove := (∀ (b : Ball), Green b) → (∀ (b0 : Ball), Green b0)

example : to_prove := λ h, λ b0, h b0
example : to_prove := id
example : to_prove :=
begin
    unfold to_prove,
    assume a,
    assume b0,
    apply a b0
end


/-
Copyright (c) 2021 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov

! This file was ported from Lean 3 source module topology.urysohns_bounded
! leanprover-community/mathlib commit af683b11d1bd89d1e85a03bf1eb5519379aabdc9
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.UrysohnsLemma
import Mathbin.Topology.ContinuousFunction.Bounded

/-!
# Urysohn's lemma for bounded continuous functions

In this file we reformulate Urysohn's lemma `exists_continuous_zero_one_of_closed` in terms of
bounded continuous functions `X →ᵇ ℝ`. These lemmas live in a separate file because
`topology.continuous_function.bounded` imports too many other files.

## Tags

Urysohn's lemma, normal topological space
-/

open BoundedContinuousFunction

open Set Function

/-- Urysohns lemma: if `s` and `t` are two disjoint closed sets in a normal topological space `X`,
then there exists a continuous function `f : X → ℝ` such that

* `f` equals zero on `s`;
* `f` equals one on `t`;
* `0 ≤ f x ≤ 1` for all `x`.
-/
theorem exists_bounded_zero_one_of_closed {X : Type _} [TopologicalSpace X] [NormalSpace X]
    {s t : Set X} (hs : IsClosed s) (ht : IsClosed t) (hd : Disjoint s t) :
    ∃ f : X →ᵇ ℝ, EqOn f 0 s ∧ EqOn f 1 t ∧ ∀ x, f x ∈ Icc (0 : ℝ) 1 :=
  let ⟨f, hfs, hft, hf⟩ := exists_continuous_zero_one_of_closed hs ht hd
  ⟨⟨f, 1, fun x y => Real.dist_le_of_mem_Icc_01 (hf _) (hf _)⟩, hfs, hft, hf⟩
#align exists_bounded_zero_one_of_closed exists_bounded_zero_one_of_closed

/-- Urysohns lemma: if `s` and `t` are two disjoint closed sets in a normal topological space `X`,
and `a ≤ b` are two real numbers, then there exists a continuous function `f : X → ℝ` such that

* `f` equals `a` on `s`;
* `f` equals `b` on `t`;
* `a ≤ f x ≤ b` for all `x`.
-/
theorem exists_bounded_mem_Icc_of_closed_of_le {X : Type _} [TopologicalSpace X] [NormalSpace X]
    {s t : Set X} (hs : IsClosed s) (ht : IsClosed t) (hd : Disjoint s t) {a b : ℝ} (hle : a ≤ b) :
    ∃ f : X →ᵇ ℝ, EqOn f (const X a) s ∧ EqOn f (const X b) t ∧ ∀ x, f x ∈ Icc a b :=
  let ⟨f, hfs, hft, hf01⟩ := exists_bounded_zero_one_of_closed hs ht hd
  ⟨BoundedContinuousFunction.const X a + (b - a) • f, fun x hx => by simp [hfs hx], fun x hx => by
    simp [hft hx], fun x =>
    ⟨by dsimp <;> nlinarith [(hf01 x).1], by dsimp <;> nlinarith [(hf01 x).2]⟩⟩
#align exists_bounded_mem_Icc_of_closed_of_le exists_bounded_mem_Icc_of_closed_of_le


/-
### Addition and subtraction of Int
-/

import Mathlib.Data.Int.Basic
import Mathlib.Data.Int.Order.Basic
import Mathlib.Data.Int.Bitwise
import Mathlib.Data.Int.ModEq
import Mathlib.Tactic.Ring
import Mathlib.Tactic.LibrarySearch

-- Write 2^n in Int contexts instead of (2:Int)^n
instance: HPow Nat Nat Int where
  hPow x y := (x:Int)^y

namespace Int

theorem sub_add_assoc (n m k: Int): n - m + k = n + (k - m) := by
  simp [Int.sub_eq_add_neg, Int.add_assoc, Int.add_comm (-m) k]

theorem sub_assoc (n m k: Int): n - m - k = n - k - m := by
  simp [Int.sub_eq_add_neg, Int.add_assoc, Int.add_comm (-m) (-k)]

theorem add_sub (n m: Int): n + (m - n) = m := by
  rw [Int.sub_eq_add_neg, Int.add_comm m (-n), ←Int.add_assoc,
      Int.add_right_neg, Int.zero_add]

theorem sub_add_dist (n m p: Int): n - (m + p) = n - m - p := by
  rw [Int.sub_eq_add_neg, Int.neg_add, ←Int.add_assoc,
      ←Int.sub_eq_add_neg, ←Int.sub_eq_add_neg]

/-
### Multiplication and power of Int
-/

theorem mul_two (n: Int): n * 2 = n + n := by
  have h: (2:Int) = 1 + 1 := rfl
  simp;
  rw [h];
  rw [Int.mul_add];
  simp [Int.mul_one];

@[simp]
theorem pow_zero (n: Int): n^0 = 1 := by rfl

theorem pow_succ (n: Int) (m: Nat): n^(m+1) = n^m * n := by rfl

theorem two_pow_ge {n: Nat}: (2^n: Int) ≥ 0 := by
  sorry

theorem two_pow_pos {n: Nat}: (2^n: Int) > 0 := by
  sorry

theorem one_le_two_pow {n: Nat}: 1 ≤ (2^n: Int) := by
  sorry

theorem one_lt_two_pow {n: Nat}: n > 0 → 1 < (2^n: Int) := by
  sorry

theorem mod_bounds {a: Int} (b: Int): a ≥ 0 → a < b → a % b = a := by
  sorry

theorem mod_mod (a b: Int): (a % b) % b = a % b := by
  sorry

theorem mod_ge_neg {a b: Int}: a % b ≥ -b := by
  sorry

theorem mod_ge {a b: Int}: (a ≥ 0) → a % b ≥ 0 := by
  sorry

theorem mod_lt {a b: Int}: (b > 0) → a % b < b := by
  sorry

theorem add_mod_right {x z: Int}: (z > 0) → (x ≥ 0) → (x + z) % z = x % z := by
  sorry

theorem add_mod_left {x z: Int}: (z > 0) → (x ≥ 0) → (z + x) % z = x % z := by
  sorry

/-
### Order of Int
-/

theorem ge_zero_eq_nonneg (n: Int): n ≥ 0 ↔ Int.NonNeg n := by
  simp [GE.ge, LE.le, Int.le]

theorem zero_ge_neg {n: Int}: n ≥ 0 → 0 ≥ -n := by
  simp [GE.ge, LE.le, Int.le, Int.neg_neg]

-- (deterministic) timeout at 'whnf', maximum number of heartbeats (200000) has been reached (use 'set_option maxHeartbeats <num>' to set the limit)
theorem add_ge_zero (n m: Int): n ≥ 0 → m ≥ 0 → n + m ≥ 0 := by sorry
  /-
  simp [GE.ge, LE.le, Int.le, HAdd.hAdd, Add.add, Int.add]
  intros hn hm
  cases hn; cases hm; simp; constructor
  -/

theorem le_succ (n: Int): n ≤ n+1 := by
  suffices NonNeg 1 by simp [LE.le, Int.le, add_sub_assoc, add_sub]; trivial
  constructor

theorem succ_le_succ (n m: Int): n ≤ m → n + 1 ≤ m + 1 := by
  sorry

theorem ge_trans {n k} (m: Int): n ≥ m → m ≥ k → n ≥ k := by
  simp [GE.ge]
  intros h₁ h₂
  apply le_trans h₂ h₁

theorem lt_add_right (n m: Int): m > 0 → n < n + m := by
  sorry

theorem mul_ge_zero {n m: Int}: n ≥ 0 → m ≥ 0 → n * m ≥ 0 := by
  rw [Int.ge_zero_eq_nonneg, Int.ge_zero_eq_nonneg, Int.ge_zero_eq_nonneg]
  intros hn hm;
  cases hn; cases hm; constructor

theorem mul_gt_zero {n m: Int}: n > 0 → m > 0 → n * m > 0 := by
  sorry

theorem pow_ge_zero (n: Int) (m: Nat): n ≥ 0 → n^m ≥ 0 := by
  revert n; induction m <;> intros n h <;> simp
  case succ acc ih =>
    simp [pow_succ]; apply mul_ge_zero (ih _ h) h

theorem pow_gt_zero (n: Int) (m: Nat): n > 0 → n^m > 0 := by
  revert n; induction m <;> intros n h <;> simp
  case succ acc ih =>
    simp [pow_succ]; apply mul_gt_zero (ih _ h) h

theorem lt_add_lt_left (n m k: Int): n < m → k + n < k + m := by
  sorry

theorem ge_add_ge_right {n m: Int} (k: Int): n ≥ m → n + k ≥ m + k := by
  sorry

end Int

/-
### Properties on Nat
-/

theorem Nat.minus_plus_one {a: Nat} (h: a > 0): a - 1 + 1 = a := by
  cases a; simp at h; rfl

theorem Nat.lt_min_left {a b c: Nat}: c < min a b → c < a := sorry

theorem Nat.lt_min_right {a b c: Nat}: c < min a b → c < b := sorry


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen

! This file was ported from Lean 3 source module number_theory.number_field.class_number
! leanprover-community/mathlib commit d0259b01c82eed3f50390a60404c63faf9e60b1f
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.NumberTheory.ClassNumber.AdmissibleAbs
import Mathbin.NumberTheory.ClassNumber.Finite
import Mathbin.NumberTheory.NumberField.Basic

/-!
# Class numbers of number fields

This file defines the class number of a number field as the (finite) cardinality of
the class group of its ring of integers. It also proves some elementary results
on the class number.

## Main definitions
- `number_field.class_number`: the class number of a number field is the (finite)
cardinality of the class group of its ring of integers
-/

namespace NumberField

variable (K : Type _) [Field K] [NumberField K]

namespace RingOfIntegers

noncomputable instance : Fintype (ClassGroup (ringOfIntegers K)) :=
  ClassGroup.fintypeOfAdmissibleOfFinite ℚ K AbsoluteValue.absIsAdmissible

end RingOfIntegers

/-- The class number of a number field is the (finite) cardinality of the class group. -/
noncomputable def classNumber : ℕ :=
  Fintype.card (ClassGroup (ringOfIntegers K))
#align number_field.class_number NumberField.classNumber

variable {K}

/-- The class number of a number field is `1` iff the ring of integers is a PID. -/
theorem classNumber_eq_one_iff : classNumber K = 1 ↔ IsPrincipalIdealRing (ringOfIntegers K) :=
  card_classGroup_eq_one_iff
#align number_field.class_number_eq_one_iff NumberField.classNumber_eq_one_iff

end NumberField

namespace Rat

open NumberField

theorem classNumber_eq : NumberField.classNumber ℚ = 1 :=
  classNumber_eq_one_iff.mpr <| by
    convert IsPrincipalIdealRing.of_surjective
        (rat.ring_of_integers_equiv.symm : ℤ →+* ring_of_integers ℚ)
        rat.ring_of_integers_equiv.symm.surjective
#align rat.class_number_eq Rat.classNumber_eq

end Rat


/- Este tercer y último módulo trata las bases de teoría de grupos.
  Igual que el primero, este módulo se basa en el curso de Kevin Buzzard:
  https://github.com/ImperialCollegeLondon/formalising-mathematics

  Así como construimos un poco de la teoría de los numeros
  naturales a base de los axiomas de peano, construiremos
  la teoría de grupos a base de los axiomas de un grupo. -/

/- Lean nos obliga a importar todo al principio del archivo. Vamos a importar
dos paquetes que vamos a usar sólo al final -/

import group_theory.index
import set_theory.fincard
/-
Para definir un grupo vamos a usar definiciones que ya existen en Lean.
Específicamente, vamos a usar las propiedades:

 - `has_mul G`, que quiere decir que `G` tiene una multplicación, es decir
   una operación `* : G → G → G` (léase también como `G × G → G`)
 - `has_one G`, que quiere decir que `G` tiene una unidad, es decir un
   elemento `1 : G` (léase también como `1 ∈ G`)
 - `has_inv G`, que quieren decir que tiene una operación de inversa, es
   decir `⁻¹ : G → G`.

Cabe mencionar que no sabemos más de estas funciones/estos elementos que
su existencia: no tenemos propiedades como la asociatividad de la
multiplicación (magma) o la relación con la unidad (monoide).
-/

class grupo (G : Type) extends has_mul G, has_one G, has_inv G :=
(mul_asoc : ∀ (a b c : G), a * b * c = a * (b * c))
(unidad_mul : ∀ (a : G), 1 * a = a)
(mul_inv_izq : ∀ (a : G), a⁻¹ * a = 1)

/-
La definición de `grupo` quiere entonces decir que tenemos estas tres definiciones,
la multiplicación, unidad e inversa, y que estas cumplen las propiedades:
 - assocatividad   de la multiplicación: `mul_assoc`
 - `1` es unidad por la izquierda: `unidad_mul`
 - la multiplicación con el inverso por la izquierda da la unidad: `mul_inv_izq`

Tal vez le parecerá un poco extraña esta definición, generalmente tenemos
propiedades que parecen ser más fuertes (multiplicación por ambos lados).

Sin embargo, como vamos a demostrar aquí, estas propiedades alcanzan.
Entonces en Lean definimos un grupo con estas propiedades (aparentemente)
más débiles para hacer más fácil el demostrar que alguna estructura es un grupo.

Ahora vamos a demostrar que estos axiomas implican (y por tanto son equivalentes)
a la versión de ambos lados que conocemos. Es decir, vamos a demostar:

- `mul_unidad (a : G) : a * 1 = a`
- `mul_der_inv (a : G) : a * a⁻¹ = 1`

-/
namespace grupo
-- Vamos a declarar primero una variable `G` que sea un grupo. Lo hacemos así:
variables {G : Type} [grupo G]

/- Para demostrar las versiones "derechas" de los axiomas, primero tenemos
que demostrar otras propiedades útiles que son consecuencia de los axiomas,
como la siguiente: -/
lemma mul_izq_cancel (a b c : G) : a * b = a * c → b = c :=
begin
 intros Habac,
 calc b = 1 * b         : by rw unidad_mul
    ... = (a⁻¹ * a) * b : by rw mul_inv_izq
    ... = a⁻¹ * (a * b) : by sorry -- siga con `rw` + el axioma indicado
    ... = a⁻¹ * (a * c) : by sorry
    ... = (a⁻¹ * a) * c : by sorry
    ... = 1 * c         : by sorry
    ... = c             : by sorry,
end

/- Aquí vemos una nueva tátctica, `calc`, que nos sirve para calcular
una serie de equaciones, demostrando en cada paso que axioma usamos.

Demostramos otro lemma ligeramente técnico: que podemos multiplicar
por la izquierda con `x` y cancelar un producto de `a` con `a⁻¹`.
Este lo hacemos directamente con `rw`, para ver la comparación con
el `calc` arriba:  -/

lemma mul_eq_de_eq_inv_mul {a x y : G} : x = a⁻¹ * y →
 a * x = y :=
begin
  intro h,
  apply mul_izq_cancel a⁻¹,  --dexemos decire
  rw h,
  rw ← (mul_asoc (a⁻¹) a  _), -- usamos `_` para que Lean infiera el argumento
  rw (mul_inv_izq a ),
  rw unidad_mul,
end

-- Declaramos un par de variables para no estar escribiendolas cada vez:
-- Sean `a,b,c,x,y` "elementos" del grupo `G`.
variables (a b c x y : G)

/- Ahora vamos a demostrar los axiomas de grupo que faltan: -/

@[simp] theorem mul_one : a * 1 = a :=
begin
  apply mul_eq_de_eq_inv_mul, -- que pasó aquí?
  rw mul_inv_izq,
end

/- Esta demostracion es similar, hagámosla como ejercicio: -/
@[simp] theorem mul_der_inv : a * a⁻¹ = 1 :=
begin
  sorry
end

/- Vamos a discutir esta palabra clave aquí, `@[simp]`:

## El simplificador de Lean

Un humano ve `a * a⁻¹` en teoría de grupos e inmedatamente lo reemplaza por `1`.
Ahora vamos a "entrenar" una IA simple, llamada `simp`, para que haga lo mismo.
El simplificador de Lean, `simp`, es lo que se llama un sistema de
reescritura de términos. Esto quiere decir que cuando le enseñamos varios
teoremas con la forma `A = B` o `P ↔ Q` (al marcarlos con el atributo  `@[simp]`)
y le damos un objetivo complicado, como
`ejemplo : (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1`
entonces `simp` va a tratar de usar la táctica `rw` lo mas que pueda, usando
los lemmas que se le han enseñado, intendando simplificar el objetivo.
Si lo logra resolver completamente, pues excelente! Si no, pero sentimos que
lo debería poder haber hecho, entonces debemos marcar más lemmas con `@[simp]`.
`simp` sólo debe ser usado para cerrar objetivos completamente. Ahora vamos a
entrenar al simplificador para resolver el ejemplo de arriba (de hecho, lo vamos
a entrenar para que reduzca un elemento arbitrario de un grupo libre a una forma
normal única, para que pueda resolver igualdades que son verdaderas de todos los
grupos, como el ejemplo arriba).

## Nota importante
El simplificador de Lean hace una serie de reescrituras, cada una reemplaza algo
con algo mas. Pero el simplificador siempre va a reescribir de izquierda a derecha!
Si le decimos que `A = B` es un lemma `simp`, entonces va a reemplazar las `A`s
con `B`s, pero nunca va a reemplazar `B`s con `A`s. Si marcamos una demostración
de `A = B` con `@[simp]` y también marcamos una demostración de `B = A` con simp,
entonces el simplificador se va a trabar en un bucle infinito cuando encuentre un
`A`! En ese sentido, no debemos pensar en igualdad como algo simétrico aquí.
Porque el simplificador trabaja de izquierda a derecha, una regla práctica (a ojo
de buen cubero) es que si `A = B` es un lemma de `simp`, entonces `B`
probablemente debería ser más simple que `A`! No es coincidencia que en los
teoremas siguientes
`@[simp] theorem mul_unidad (a : G) : a * 1 = a`
`@[simp] theorem mul_inv_der (a : G) : a * a⁻¹ = 1`
el lado derecho es más simple que el izquierdo. Sería un desastre
el marcar `a = a * 1` con la viñeta de `@[simp]` -- puede ver por que?
Entrenemos al simplificador de Lean! Vamos a mostrarle los axiomas de
un grupo ahora. Ya los definimos, así que tenemos que marcarlos retrospectivamente
con el atributo  `@[simp]`.
-/

attribute [simp] unidad_mul mul_inv_izq mul_asoc

/-
Ahora le vamos a mostrar al simplifcador los siguientes lemmas:
`inv_mul_cancel_izq : a⁻¹ * (a * b) = b`
`mul_inv_cancel_izq : a * (a⁻¹ * b) = b`
`inv_mul : (a * b)⁻¹ = b⁻¹ * a⁻¹`
`unidad_inv : (1 : G)⁻¹ = 1`
`inv_inv : (a⁻¹)⁻¹ = a`
Note que en cada caso, el lado derecho es más simple que el izquierdo.
Trate de usar al sipmlificador en sus demostraciones. Vamos a resolver
la primera juntos:
-/

@[simp] lemma inv_mul_cancel_izq : a⁻¹ * (a * b) = b :=
begin
  rw ← mul_asoc, -- el simplificador no va a hacer esto (en esta dirección)
                  -- así que lo hacemos manualmente
  simp, -- el simplificador lo toma desde aquí,
        -- reescribiendo `a⁻¹ * a` a `1` y luego `1 * b` a `b`
end

@[simp] lemma mul_inv_cancel_izq : a * (a⁻¹ * b) = b :=
begin
  sorry
end

@[simp] lemma inv_mul : (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  sorry
end

@[simp] lemma unidad_inv : (1 : G)⁻¹ = 1 :=
begin
  sorry
end

@[simp] lemma inv_inv : a ⁻¹ ⁻¹ = a :=
begin
  sorry
end

/-
La razón por la que escogimos estos cinco lemas en particular es que
los sistemas de reescritura de términos los entendemos bien en
la ciencia de la computación. En especial, existe un algoritmo llamado
el algoritmo de Knuth-Bedix, al cual, si le damos los tres axiomas de
grupo que usamos, produce lo que se llama un "sistema de reescritura
de términos confluente y Noetheriano", que transforma cualquier término
en una forma normal única. El sistema entonces produce precisamente
los lemmas de `simp` que hemos demostrado anteriormente. Vea:
https://en.wikipedia.org/wiki/Word_problem_(mathematics)#Example:_A_term_rewriting_system_to_decide_the_word_problem_in_the_free_group
(en inglés) para más información. No vamos a hablar del algoritmo de
Knuth-Bendix por que realmente trata de ciencias de la computación,
pero al parecer, si se le aplicamos a anillos polinomiales entonces
obtenemos el algoritmo de Buchberger para computar bases de Gröbner.
-/

-- Ahora intentemos nuestro ejemplo:
example : (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 := by simp

-- El simplificador lo resuelve!

-- Intente con sus propias identidades. `simp` las resolverá todas!
/- Probos lo que hemos aprendido con una demostración más de un Lemma
 un poco mas interesante -/
@[simp] theorem inv_inj_iff {a b : G}: a⁻¹ = b⁻¹ ↔ a = b :=
begin
  sorry
end

end grupo

/- Igual que con los números naturales, Lean ya conoce la teoría de grupos
y muchas propiedades de eso. Como un ejemplo les quería mostrar el teorema de
Lagrange, que para un grupo finito `G` el orden de un subgrupo `H ≤ G` divide
 al orden de `G`. Curiosamente ese teorema no estaba en Lean exactamente,
 así que lo vamos a demostrar aquí, como un pequeño ejemplo -/

lemma card_dvd_card {G : Type*} [group G] [fintype G] (H: subgroup G):
nat.card H ∣ nat.card G :=
begin
  use H.index,
  symmetry,
  exact subgroup.card_mul_index H,
end


/-
Copyright (c) 2021 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.instances.real
import order.filter.archimedean

/-!
# Convergence of subadditive sequences

A subadditive sequence `u : ℕ → ℝ` is a sequence satisfying `u (m + n) ≤ u m + u n` for all `m, n`.
We define this notion as `subadditive u`, and prove in `subadditive.tendsto_lim` that, if `u n / n`
is bounded below, then it converges to a limit (that we denote by `subadditive.lim` for
convenience). This result is known as Fekete's lemma in the literature.
-/

noncomputable theory
open set filter
open_locale topological_space

/-- A real-valued sequence is subadditive if it satisfies the inequality `u (m + n) ≤ u m + u n`
for all `m, n`. -/
def subadditive (u : ℕ → ℝ) : Prop :=
∀ m n, u (m + n) ≤ u m + u n

namespace subadditive

variables {u : ℕ → ℝ} (h : subadditive u)
include h

/-- The limit of a bounded-below subadditive sequence. The fact that the sequence indeed tends to
this limit is given in `subadditive.tendsto_lim` -/
@[irreducible, nolint unused_arguments]
protected def lim := Inf ((λ (n : ℕ), u n / n) '' (Ici 1))

lemma lim_le_div (hbdd : bdd_below (range (λ n, u n / n))) {n : ℕ} (hn : n ≠ 0) :
  h.lim ≤ u n / n :=
begin
  rw subadditive.lim,
  apply cInf_le _ _,
  { rcases hbdd with ⟨c, hc⟩,
    exact ⟨c, λ x hx, hc (image_subset_range _ _ hx)⟩ },
  { apply mem_image_of_mem,
    exact zero_lt_iff.2 hn }
end

lemma apply_mul_add_le (k n r) : u (k * n + r) ≤ k * u n + u r :=
begin
  induction k with k IH, { simp only [nat.cast_zero, zero_mul, zero_add] },
  calc
  u ((k+1) * n + r)
      = u (n + (k * n + r)) : by { congr' 1, ring }
  ... ≤ u n + u (k * n + r) : h _ _
  ... ≤ u n + (k * u n + u r) : add_le_add_left IH _
  ... = (k+1) * u n + u r : by ring
end

lemma eventually_div_lt_of_div_lt {L : ℝ} {n : ℕ} (hn : n ≠ 0) (hL : u n / n < L) :
  ∀ᶠ p in at_top, u p / p < L :=
begin
  have I : ∀ (i : ℕ), 0 < i → (i : ℝ) ≠ 0,
  { assume i hi, simp only [hi.ne', ne.def, nat.cast_eq_zero, not_false_iff] },
  obtain ⟨w, nw, wL⟩ : ∃ w, u n / n < w ∧ w < L := exists_between hL,
  obtain ⟨x, hx⟩ : ∃ x, ∀ i < n, u i - i * w ≤ x,
  { obtain ⟨x, hx⟩ : bdd_above (↑(finset.image (λ i, u i - i * w) (finset.range n))) :=
      finset.bdd_above _,
    refine ⟨x, λ i hi, _⟩,
    simp only [upper_bounds, mem_image, and_imp, forall_exists_index, mem_set_of_eq,
      forall_apply_eq_imp_iff₂, finset.mem_range, finset.mem_coe, finset.coe_image] at hx,
    exact hx _ hi },
  have A : ∀ (p : ℕ), u p ≤ p * w + x,
  { assume p,
    let s := p / n,
    let r := p % n,
    have hp : p = s * n + r, by rw [mul_comm, nat.div_add_mod],
    calc u p = u (s * n + r) : by rw hp
    ... ≤ s * u n + u r : h.apply_mul_add_le _ _ _
    ... = s * n * (u n / n) + u r : by { field_simp [I _ hn.bot_lt], ring }
    ... ≤ s * n * w + u r : add_le_add_right
      (mul_le_mul_of_nonneg_left nw.le (mul_nonneg (nat.cast_nonneg _) (nat.cast_nonneg _))) _
    ... = (s * n + r) * w + (u r - r * w) : by ring
    ... = p * w + (u r - r * w) : by { rw hp, simp only [nat.cast_add, nat.cast_mul] }
    ... ≤ p * w + x : add_le_add_left (hx _ (nat.mod_lt _ hn.bot_lt)) _ },
  have B : ∀ᶠ p in at_top, u p / p ≤ w + x / p,
  { refine eventually_at_top.2 ⟨1, λ p hp, _⟩,
    simp only [I p hp, ne.def, not_false_iff] with field_simps,
    refine div_le_div_of_le_of_nonneg _ (nat.cast_nonneg _),
    rw mul_comm,
    exact A _ },
  have C : ∀ᶠ (p : ℕ) in at_top, w + x / p < L,
  { have : tendsto (λ (p : ℕ), w + x / p) at_top (𝓝 (w + 0)) :=
      tendsto_const_nhds.add (tendsto_const_nhds.div_at_top tendsto_coe_nat_at_top_at_top),
    rw add_zero at this,
    exact (tendsto_order.1 this).2 _ wL },
  filter_upwards [B, C],
  assume p hp h'p,
  exact hp.trans_lt h'p
end

/-- Fekete's lemma: a subadditive sequence which is bounded below converges. -/
theorem tendsto_lim (hbdd : bdd_below (range (λ n, u n / n))) :
  tendsto (λ n, u n / n) at_top (𝓝 h.lim) :=
begin
  refine tendsto_order.2 ⟨λ l hl, _, λ L hL, _⟩,
  { refine eventually_at_top.2
      ⟨1, λ n hn, hl.trans_le (h.lim_le_div hbdd ((zero_lt_one.trans_le hn).ne'))⟩ },
  { obtain ⟨n, npos, hn⟩ : ∃ (n : ℕ), 0 < n ∧ u n / n < L,
    { rw subadditive.lim at hL,
      rcases exists_lt_of_cInf_lt (by simp) hL with ⟨x, hx, xL⟩,
      rcases (mem_image _ _ _).1 hx with ⟨n, hn, rfl⟩,
      exact ⟨n, zero_lt_one.trans_le hn, xL⟩ },
    exact h.eventually_div_lt_of_div_lt npos.ne' hn }
end

end subadditive