/-
Copyright (c) 2020 Frédéric Dupuis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Frédéric Dupuis, Eric Wieser
-/

import group_theory.congruence
import linear_algebra.multilinear.tensor_product

/-!
# Tensor product of an indexed family of modules over commutative semirings

We define the tensor product of an indexed family `s : ι → Type*` of modules over commutative
semirings. We denote this space by `⨂[R] i, s i` and define it as `free_add_monoid (R × Π i, s i)`
quotiented by the appropriate equivalence relation. The treatment follows very closely that of the
binary tensor product in `linear_algebra/tensor_product.lean`.

## Main definitions

* `pi_tensor_product R s` with `R` a commutative semiring and `s : ι → Type*` is the tensor product
  of all the `s i`'s. This is denoted by `⨂[R] i, s i`.
* `tprod R f` with `f : Π i, s i` is the tensor product of the vectors `f i` over all `i : ι`.
  This is bundled as a multilinear map from `Π i, s i` to `⨂[R] i, s i`.
* `lift_add_hom` constructs an `add_monoid_hom` from `(⨂[R] i, s i)` to some space `F` from a
  function `φ : (R × Π i, s i) → F` with the appropriate properties.
* `lift φ` with `φ : multilinear_map R s E` is the corresponding linear map
  `(⨂[R] i, s i) →ₗ[R] E`. This is bundled as a linear equivalence.
* `pi_tensor_product.reindex e` re-indexes the components of `⨂[R] i : ι, M` along `e : ι ≃ ι₂`.
* `pi_tensor_product.tmul_equiv` equivalence between a `tensor_product` of `pi_tensor_product`s and
  a single `pi_tensor_product`.

## Notations

* `⨂[R] i, s i` is defined as localized notation in locale `tensor_product`
* `⨂ₜ[R] i, f i` with `f : Π i, f i` is defined globally as the tensor product of all the `f i`'s.

## Implementation notes

* We define it via `free_add_monoid (R × Π i, s i)` with the `R` representing a "hidden" tensor
  factor, rather than `free_add_monoid (Π i, s i)` to ensure that, if `ι` is an empty type,
  the space is isomorphic to the base ring `R`.
* We have not restricted the index type `ι` to be a `fintype`, as nothing we do here strictly
  requires it. However, problems may arise in the case where `ι` is infinite; use at your own
  caution.
* Instead of requiring `decidable_eq ι` as an argument to `pi_tensor_product` itself, we include it
  as an argument in the constructors of the relation. A decidability isntance still has to come
  from somewhere due to the use of `function.update`, but this hides it from the downstream user.
  See the implementation notes for `multilinear_map` for an extended discussion of this choice.

## TODO

* Define tensor powers, symmetric subspace, etc.
* API for the various ways `ι` can be split into subsets; connect this with the binary
  tensor product.
* Include connection with holors.
* Port more of the API from the binary tensor product over to this case.

## Tags

multilinear, tensor, tensor product
-/

open function

section semiring

variables {ι ι₂ ι₃ : Type*}
variables {R : Type*} [comm_semiring R]
variables {R₁ R₂ : Type*}
variables {s : ι → Type*} [∀ i, add_comm_monoid (s i)] [∀ i, module R (s i)]
variables {M : Type*} [add_comm_monoid M] [module R M]
variables {E : Type*} [add_comm_monoid E] [module R E]
variables {F : Type*} [add_comm_monoid F]

namespace pi_tensor_product
include R
variables (R) (s)

/-- The relation on `free_add_monoid (R × Π i, s i)` that generates a congruence whose quotient is
the tensor product. -/
inductive eqv : free_add_monoid (R × Π i, s i) → free_add_monoid (R × Π i, s i) → Prop
| of_zero : ∀ (r : R) (f : Π i, s i) (i : ι) (hf : f i = 0), eqv (free_add_monoid.of (r, f)) 0
| of_zero_scalar : ∀ (f : Π i, s i), eqv (free_add_monoid.of (0, f)) 0
| of_add : ∀ (inst : decidable_eq ι) (r : R) (f : Π i, s i) (i : ι) (m₁ m₂ : s i), eqv
    (free_add_monoid.of (r, update f i m₁) + free_add_monoid.of (r, update f i m₂))
    (free_add_monoid.of (r, update f i (m₁ + m₂)))
| of_add_scalar : ∀ (r r' : R) (f : Π i, s i), eqv
    (free_add_monoid.of (r, f) + free_add_monoid.of (r', f))
    (free_add_monoid.of (r + r', f))
| of_smul : ∀ (inst : decidable_eq ι) (r : R) (f : Π i, s i) (i : ι) (r' : R), eqv
    (free_add_monoid.of (r, update f i (r' • (f i))))
    (free_add_monoid.of (r' * r, f))
| add_comm : ∀ x y, eqv (x + y) (y + x)

end pi_tensor_product

variables (R) (s)

/-- `pi_tensor_product R s` with `R` a commutative semiring and `s : ι → Type*` is the tensor
  product of all the `s i`'s. This is denoted by `⨂[R] i, s i`. -/
def pi_tensor_product : Type* :=
(add_con_gen (pi_tensor_product.eqv R s)).quotient

variables {R}

/- This enables the notation `⨂[R] i : ι, s i` for the pi tensor product, given `s : ι → Type*`. -/
localized "notation (name := pi_tensor_product)
  `⨂[`:100 R `] ` binders `, ` r:(scoped:67 f, pi_tensor_product R f) := r" in tensor_product

open_locale tensor_product

namespace pi_tensor_product

section module

instance : add_comm_monoid (⨂[R] i, s i) :=
{ add_comm := λ x y, add_con.induction_on₂ x y $ λ x y, quotient.sound' $
    add_con_gen.rel.of _ _ $ eqv.add_comm _ _,
  .. (add_con_gen (pi_tensor_product.eqv R s)).add_monoid }

instance : inhabited (⨂[R] i, s i) := ⟨0⟩

variables (R) {s}

/-- `tprod_coeff R r f` with `r : R` and `f : Π i, s i` is the tensor product of the vectors `f i`
over all `i : ι`, multiplied by the coefficient `r`. Note that this is meant as an auxiliary
definition for this file alone, and that one should use `tprod` defined below for most purposes. -/
def tprod_coeff (r : R) (f : Π i, s i) : ⨂[R] i, s i := add_con.mk' _ $ free_add_monoid.of (r, f)

variables {R}

lemma zero_tprod_coeff (f : Π i, s i) : tprod_coeff R 0 f = 0 :=
quotient.sound' $ add_con_gen.rel.of _ _ $ eqv.of_zero_scalar _

lemma zero_tprod_coeff' (z : R) (f : Π i, s i) (i : ι) (hf: f i = 0) : tprod_coeff R z f = 0 :=
quotient.sound' $ add_con_gen.rel.of _ _ $ eqv.of_zero _ _ i hf

lemma add_tprod_coeff [decidable_eq ι] (z : R) (f : Π i, s i) (i : ι) (m₁ m₂ : s i) :
  tprod_coeff R z (update f i m₁) + tprod_coeff R z (update f i m₂) =
    tprod_coeff R z (update f i (m₁ + m₂)) :=
quotient.sound' $ add_con_gen.rel.of _ _ (eqv.of_add _ z f i m₁ m₂)

lemma add_tprod_coeff' (z₁ z₂ : R) (f : Π i, s i) :
  tprod_coeff R z₁ f + tprod_coeff R z₂ f = tprod_coeff R (z₁ + z₂) f :=
quotient.sound' $ add_con_gen.rel.of _ _ (eqv.of_add_scalar z₁ z₂ f)

lemma smul_tprod_coeff_aux [decidable_eq ι] (z : R) (f : Π i, s i) (i : ι) (r : R) :
  tprod_coeff R z (update f i (r • f i)) = tprod_coeff R (r * z) f :=
 quotient.sound' $ add_con_gen.rel.of _ _ $ eqv.of_smul _ _ _ _ _

lemma smul_tprod_coeff [decidable_eq ι] (z : R) (f : Π i, s i) (i : ι) (r : R₁)
  [has_smul R₁ R] [is_scalar_tower R₁ R R] [has_smul R₁ (s i)] [is_scalar_tower R₁ R (s i)] :
  tprod_coeff R z (update f i (r • f i)) = tprod_coeff R (r • z) f :=
begin
  have h₁ : r • z = (r • (1 : R)) * z := by rw [smul_mul_assoc, one_mul],
  have h₂ : r • (f i) = (r • (1 : R)) • f i := (smul_one_smul _ _ _).symm,
  rw [h₁, h₂],
  exact smul_tprod_coeff_aux z f i _,
end

/-- Construct an `add_monoid_hom` from `(⨂[R] i, s i)` to some space `F` from a function
`φ : (R × Π i, s i) → F` with the appropriate properties. -/
def lift_add_hom (φ : (R × Π i, s i) → F)
  (C0 : ∀ (r : R) (f : Π i, s i) (i : ι) (hf : f i = 0), φ (r, f) = 0)
  (C0' : ∀ (f : Π i, s i), φ (0, f) = 0)
  (C_add : ∀ [decidable_eq ι] (r : R) (f : Π i, s i) (i : ι) (m₁ m₂ : s i), by exactI
    φ (r, update f i m₁) + φ (r, update f i m₂) = φ (r, update f i (m₁ + m₂)))
  (C_add_scalar : ∀ (r r' : R) (f : Π i, s i),
    φ (r , f) + φ (r', f) = φ (r + r', f))
  (C_smul : ∀ [decidable_eq ι] (r : R) (f : Π i, s i) (i : ι) (r' : R), by exactI
    φ (r, update f i (r' • (f i))) = φ (r' * r, f))
: (⨂[R] i, s i) →+ F :=
(add_con_gen (pi_tensor_product.eqv R s)).lift (free_add_monoid.lift φ) $ add_con.add_con_gen_le $
λ x y hxy, match x, y, hxy with
| _, _, (eqv.of_zero r' f i hf)        := (add_con.ker_rel _).2 $
    by simp [free_add_monoid.lift_eval_of, C0 r' f i hf]
| _, _, (eqv.of_zero_scalar f)        := (add_con.ker_rel _).2 $
    by simp [free_add_monoid.lift_eval_of, C0']
| _, _, (eqv.of_add inst z f i m₁ m₂)      := (add_con.ker_rel _).2 $
    by simp [free_add_monoid.lift_eval_of, @C_add inst]
| _, _, (eqv.of_add_scalar z₁ z₂ f)      := (add_con.ker_rel _).2 $
    by simp [free_add_monoid.lift_eval_of, C_add_scalar]
| _, _, (eqv.of_smul inst z f i r')     := (add_con.ker_rel _).2 $
    by simp [free_add_monoid.lift_eval_of, @C_smul inst]
| _, _, (eqv.add_comm x y)         := (add_con.ker_rel _).2 $
    by simp_rw [add_monoid_hom.map_add, add_comm]
end

@[elab_as_eliminator]
protected theorem induction_on'
  {C : (⨂[R] i, s i) → Prop}
  (z : ⨂[R] i, s i)
  (C1 : ∀ {r : R} {f : Π i, s i}, C (tprod_coeff R r f))
  (Cp : ∀ {x y}, C x → C y → C (x + y)) : C z :=
begin
  have C0 : C 0,
  { have h₁ := @C1 0 0,
    rwa [zero_tprod_coeff] at h₁ },
  refine add_con.induction_on z (λ x, free_add_monoid.rec_on x C0 _),
  simp_rw add_con.coe_add,
  refine λ f y ih, Cp _ ih,
  convert @C1 f.1 f.2,
  simp only [prod.mk.eta],
end

section distrib_mul_action
variables [monoid R₁] [distrib_mul_action R₁ R] [smul_comm_class R₁ R R]
variables [monoid R₂] [distrib_mul_action R₂ R] [smul_comm_class R₂ R R]

-- Most of the time we want the instance below this one, which is easier for typeclass resolution
-- to find.
instance has_smul' : has_smul R₁ (⨂[R] i, s i) :=
⟨λ r, lift_add_hom (λ f : R × Π i, s i, tprod_coeff R (r • f.1) f.2)
  (λ r' f i hf, by simp_rw [zero_tprod_coeff' _ f i hf])
  (λ f, by simp [zero_tprod_coeff])
  (λ r' f i m₁ m₂, by simp [add_tprod_coeff])
  (λ r' r'' f, by simp [add_tprod_coeff', mul_add])
  (λ z f i r', by simp [smul_tprod_coeff, mul_smul_comm])⟩

instance : has_smul R (⨂[R] i, s i) := pi_tensor_product.has_smul'

lemma smul_tprod_coeff' (r : R₁) (z : R) (f : Π i, s i) :
  r • (tprod_coeff R z f) = tprod_coeff R (r • z) f := rfl

protected theorem smul_add (r : R₁) (x y : ⨂[R] i, s i) :
  r • (x + y) = r • x + r • y :=
add_monoid_hom.map_add _ _ _

instance distrib_mul_action' : distrib_mul_action R₁ (⨂[R] i, s i) :=
{ smul := (•),
  smul_add := λ r x y, add_monoid_hom.map_add _ _ _,
  mul_smul := λ r r' x, pi_tensor_product.induction_on' x
    (λ r'' f, by simp [smul_tprod_coeff', smul_smul])
    (λ x y ihx ihy, by simp_rw [pi_tensor_product.smul_add, ihx, ihy]),
  one_smul := λ x, pi_tensor_product.induction_on' x
    (λ f, by simp [smul_tprod_coeff' _ _])
    (λ z y ihz ihy, by simp_rw [pi_tensor_product.smul_add, ihz, ihy]),
  smul_zero := λ r, add_monoid_hom.map_zero _ }

instance smul_comm_class' [smul_comm_class R₁ R₂ R] : smul_comm_class R₁ R₂ (⨂[R] i, s i) :=
⟨λ r' r'' x, pi_tensor_product.induction_on' x
  (λ xr xf, by simp only [smul_tprod_coeff', smul_comm])
  (λ z y ihz ihy, by simp_rw [pi_tensor_product.smul_add, ihz, ihy])⟩

instance is_scalar_tower' [has_smul R₁ R₂] [is_scalar_tower R₁ R₂ R] :
  is_scalar_tower R₁ R₂ (⨂[R] i, s i) :=
⟨λ r' r'' x, pi_tensor_product.induction_on' x
  (λ xr xf, by simp only [smul_tprod_coeff', smul_assoc])
  (λ z y ihz ihy, by simp_rw [pi_tensor_product.smul_add, ihz, ihy])⟩

end distrib_mul_action

-- Most of the time we want the instance below this one, which is easier for typeclass resolution
-- to find.
instance module' [semiring R₁] [module R₁ R] [smul_comm_class R₁ R R] : module R₁ (⨂[R] i, s i) :=
{ smul := (•),
  add_smul := λ r r' x, pi_tensor_product.induction_on' x
    (λ r f, by simp [smul_tprod_coeff' _ _, add_smul, add_tprod_coeff'])
    (λ x y ihx ihy, by simp [pi_tensor_product.smul_add, ihx, ihy, add_add_add_comm]),
  zero_smul := λ x, pi_tensor_product.induction_on' x
    (λ r f, by { simp_rw [smul_tprod_coeff' _ _, zero_smul], exact zero_tprod_coeff _ })
    (λ x y ihx ihy, by rw [pi_tensor_product.smul_add, ihx, ihy, add_zero]),
  ..pi_tensor_product.distrib_mul_action' }

-- shortcut instances
instance : module R (⨂[R] i, s i) := pi_tensor_product.module'
instance : smul_comm_class R R (⨂[R] i, s i) := pi_tensor_product.smul_comm_class'
instance : is_scalar_tower R R (⨂[R] i, s i) := pi_tensor_product.is_scalar_tower'

variables {R}

variables (R)
/-- The canonical `multilinear_map R s (⨂[R] i, s i)`. -/
def tprod : multilinear_map R s (⨂[R] i, s i) :=
{ to_fun := tprod_coeff R 1,
  map_add' := λ _ f i x y, by exactI (add_tprod_coeff (1 : R) f i x y).symm,
  map_smul' := λ _ f i r x, by
    resetI; simp_rw [smul_tprod_coeff', ←smul_tprod_coeff (1 : R) _ i, update_idem, update_same] }

variables {R}

notation `⨂ₜ[`:100 R`] ` binders `, ` r:(scoped:67 f, tprod R f) := r

@[simp]
lemma tprod_coeff_eq_smul_tprod (z : R) (f : Π i, s i) : tprod_coeff R z f = z • tprod R f :=
begin
  have : z = z • (1 : R) := by simp only [mul_one, algebra.id.smul_eq_mul],
  conv_lhs { rw this },
  rw ←smul_tprod_coeff',
  refl,
end

@[elab_as_eliminator]
protected theorem induction_on
  {C : (⨂[R] i, s i) → Prop}
  (z : ⨂[R] i, s i)
  (C1 : ∀ {r : R} {f : Π i, s i}, C (r • (tprod R f)))
  (Cp : ∀ {x y}, C x → C y → C (x + y)) : C z :=
begin
  simp_rw ←tprod_coeff_eq_smul_tprod at C1,
  exact pi_tensor_product.induction_on' z @C1 @Cp,
end

@[ext]
theorem ext {φ₁ φ₂ : (⨂[R] i, s i) →ₗ[R] E}
  (H : φ₁.comp_multilinear_map (tprod R) = φ₂.comp_multilinear_map (tprod R)) : φ₁ = φ₂ :=
begin
  refine linear_map.ext _,
  refine λ z,
    (pi_tensor_product.induction_on' z _ (λ x y hx hy, by rw [φ₁.map_add, φ₂.map_add, hx, hy])),
  { intros r f,
    rw [tprod_coeff_eq_smul_tprod, φ₁.map_smul, φ₂.map_smul],
    apply _root_.congr_arg,
    exact multilinear_map.congr_fun H f }
end

end module

section multilinear
open multilinear_map
variables {s}

/-- Auxiliary function to constructing a linear map `(⨂[R] i, s i) → E` given a
`multilinear map R s E` with the property that its composition with the canonical
`multilinear_map R s (⨂[R] i, s i)` is the given multilinear map. -/
def lift_aux (φ : multilinear_map R s E) : (⨂[R] i, s i) →+ E :=
  lift_add_hom (λ (p : R × Π i, s i), p.1 • (φ p.2))
    (λ z f i hf, by rw [map_coord_zero φ i hf, smul_zero])
    (λ f, by rw [zero_smul])
    (λ _ z f i m₁ m₂, by { resetI, rw [←smul_add, φ.map_add] })
    (λ z₁ z₂ f, by rw [←add_smul])
    (λ _ z f i r, by { resetI, simp [φ.map_smul, smul_smul, mul_comm] })

lemma lift_aux_tprod (φ : multilinear_map R s E) (f : Π i, s i) : lift_aux φ (tprod R f) = φ f :=
by simp only [lift_aux, lift_add_hom, tprod, multilinear_map.coe_mk, tprod_coeff,
              free_add_monoid.lift_eval_of, one_smul, add_con.lift_mk']

lemma lift_aux_tprod_coeff (φ : multilinear_map R s E) (z : R) (f : Π i, s i) :
  lift_aux φ (tprod_coeff R z f) = z • φ f :=
by simp [lift_aux, lift_add_hom, tprod_coeff, free_add_monoid.lift_eval_of]

lemma lift_aux.smul {φ : multilinear_map R s E} (r : R) (x : ⨂[R] i, s i) :
  lift_aux φ (r • x) = r • lift_aux φ x :=
begin
  refine pi_tensor_product.induction_on' x _ _,
  { intros z f,
    rw [smul_tprod_coeff' r z f, lift_aux_tprod_coeff, lift_aux_tprod_coeff, smul_assoc] },
  { intros z y ihz ihy,
    rw [smul_add, (lift_aux φ).map_add, ihz, ihy, (lift_aux φ).map_add, smul_add] }
end

/-- Constructing a linear map `(⨂[R] i, s i) → E` given a `multilinear_map R s E` with the
property that its composition with the canonical `multilinear_map R s E` is
the given multilinear map `φ`. -/
def lift : (multilinear_map R s E) ≃ₗ[R] ((⨂[R] i, s i) →ₗ[R] E) :=
{ to_fun := λ φ, { map_smul' := lift_aux.smul, .. lift_aux φ },
  inv_fun := λ φ', φ'.comp_multilinear_map (tprod R),
  left_inv := λ φ, by { ext, simp [lift_aux_tprod, linear_map.comp_multilinear_map] },
  right_inv := λ φ, by { ext, simp [lift_aux_tprod] },
  map_add' := λ φ₁ φ₂, by { ext, simp [lift_aux_tprod] },
  map_smul' := λ r φ₂, by { ext, simp [lift_aux_tprod] } }

variables {φ : multilinear_map R s E}

@[simp] lemma lift.tprod (f : Π i, s i) : lift φ (tprod R f) = φ f := lift_aux_tprod φ f
theorem lift.unique' {φ' : (⨂[R] i, s i) →ₗ[R] E} (H : φ'.comp_multilinear_map (tprod R) = φ) :
  φ' = lift φ :=
ext $ H.symm ▸ (lift.symm_apply_apply φ).symm

@[simp]
theorem lift_symm (φ' : (⨂[R] i, s i) →ₗ[R] E) : lift.symm φ' = φ'.comp_multilinear_map (tprod R) :=
rfl

@[simp]
theorem lift_tprod : lift (tprod R : multilinear_map R s _) = linear_map.id :=
eq.symm $ lift.unique' rfl

section

variables (R M)
/-- Re-index the components of the tensor power by `e`.

For simplicity, this is defined only for homogeneously- (rather than dependently-) typed components.
-/
def reindex (e : ι ≃ ι₂) : ⨂[R] i : ι, M ≃ₗ[R] ⨂[R] i : ι₂, M :=
linear_equiv.of_linear
  (lift (dom_dom_congr e.symm (tprod R : multilinear_map R _ (⨂[R] i : ι₂, M))))
  (lift (dom_dom_congr e (tprod R : multilinear_map R _ (⨂[R] i : ι, M))))
  (by { ext, simp only [linear_map.comp_apply, linear_map.id_apply, lift_tprod,
                        linear_map.comp_multilinear_map_apply, lift.tprod,
                        dom_dom_congr_apply, equiv.apply_symm_apply] })
  (by { ext, simp only [linear_map.comp_apply, linear_map.id_apply, lift_tprod,
                        linear_map.comp_multilinear_map_apply, lift.tprod,
                        dom_dom_congr_apply, equiv.symm_apply_apply] })

end

@[simp] lemma reindex_tprod (e : ι ≃ ι₂) (f : Π i, M) :
  reindex R M e (tprod R f) = tprod R (λ i, f (e.symm i)) :=
lift_aux_tprod _ f

@[simp] lemma reindex_comp_tprod (e : ι ≃ ι₂) :
  (reindex R M e : ⨂[R] i : ι, M →ₗ[R] ⨂[R] i : ι₂, M).comp_multilinear_map (tprod R) =
    (tprod R : multilinear_map R (λ i, M) _).dom_dom_congr e.symm :=
multilinear_map.ext $ reindex_tprod e

@[simp] lemma lift_comp_reindex (e : ι ≃ ι₂) (φ : multilinear_map R (λ _ : ι₂, M) E) :
  (lift φ) ∘ₗ ↑(reindex R M e) = lift (φ.dom_dom_congr e.symm) :=
by { ext, simp, }

@[simp]
lemma lift_reindex (e : ι ≃ ι₂) (φ : multilinear_map R (λ _, M) E) (x : ⨂[R] i, M) :
  lift φ (reindex R M e x) = lift (φ.dom_dom_congr e.symm) x :=
linear_map.congr_fun (lift_comp_reindex e φ) x

@[simp] lemma reindex_trans (e : ι ≃ ι₂) (e' : ι₂ ≃ ι₃) :
  (reindex R M e).trans (reindex R M e') = reindex R M (e.trans e') :=
begin
  apply linear_equiv.to_linear_map_injective,
  ext f,
  simp only [linear_equiv.trans_apply, linear_equiv.coe_coe, reindex_tprod,
    linear_map.coe_comp_multilinear_map, function.comp_app, multilinear_map.dom_dom_congr_apply,
    reindex_comp_tprod],
  congr,
end

@[simp] lemma reindex_reindex (e : ι ≃ ι₂) (e' : ι₂ ≃ ι₃) (x : ⨂[R] i, M) :
  reindex R M e' (reindex R M e x) = reindex R M (e.trans e') x :=
linear_equiv.congr_fun (reindex_trans e e' : _ = reindex R M (e.trans e')) x

@[simp] lemma reindex_symm (e : ι ≃ ι₂) :
  (reindex R M e).symm = reindex R M e.symm := rfl

@[simp] lemma reindex_refl : reindex R M (equiv.refl ι) = linear_equiv.refl R _ :=
begin
  apply linear_equiv.to_linear_map_injective,
  ext1,
  rw [reindex_comp_tprod, linear_equiv.refl_to_linear_map, equiv.refl_symm],
  refl,
end

variables (ι)

/-- The tensor product over an empty index type `ι` is isomorphic to the base ring. -/
@[simps symm_apply]
def is_empty_equiv [is_empty ι] : ⨂[R] i : ι, M ≃ₗ[R] R :=
{ to_fun := lift (const_of_is_empty R _ 1),
  inv_fun := λ r, r • tprod R (@is_empty_elim _ _ _),
  left_inv := λ x, by
  { apply x.induction_on,
    { intros r f,
      have := subsingleton.elim f is_empty_elim,
      simp [this], },
    { simp only,
      intros x y hx hy,
      simp [add_smul, hx, hy] }},
  right_inv := λ t, by simp only [mul_one, algebra.id.smul_eq_mul, const_of_is_empty_apply,
    linear_map.map_smul, pi_tensor_product.lift.tprod],
  map_add' := linear_map.map_add _,
  map_smul' := linear_map.map_smul _, }

@[simp]
lemma is_empty_equiv_apply_tprod [is_empty ι] (f : ι → M) : is_empty_equiv ι (tprod R f) = 1 :=
lift.tprod _

variables {ι}

/-- The tensor product over an single index is isomorphic to the module -/
@[simps symm_apply]
def subsingleton_equiv [subsingleton ι] (i₀ : ι) : ⨂[R] i : ι, M ≃ₗ[R] M :=
{ to_fun := lift (multilinear_map.of_subsingleton R M i₀),
  inv_fun := λ m, tprod R (λ v, m),
  left_inv := λ x, by
  { dsimp only,
    have : ∀ (f : ι → M) (z : M), (λ i : ι, z) = update f i₀ z,
    { intros f z,
      ext i,
      rw [subsingleton.elim i i₀, function.update_same] },
    apply x.induction_on,
    { intros r f,
      simp only [linear_map.map_smul, lift.tprod, of_subsingleton_apply, function.eval,
                 this f, multilinear_map.map_smul, update_eq_self], },
    { intros x y hx hy,
      simp only [multilinear_map.map_add, this 0 (_ + _), linear_map.map_add, ←this 0 (lift _ _),
        hx, hy] } },
  right_inv := λ t, by simp only [of_subsingleton_apply, lift.tprod, function.eval_apply],
  map_add' := linear_map.map_add _,
  map_smul' := linear_map.map_smul _, }

@[simp]
lemma subsingleton_equiv_apply_tprod [subsingleton ι] (i : ι) (f : ι → M) :
  subsingleton_equiv i (tprod R f) = f i :=
lift.tprod _

section tmul

/-- Collapse a `tensor_product` of `pi_tensor_product`s. -/
private def tmul : (⨂[R] i : ι, M) ⊗[R] (⨂[R] i : ι₂, M) →ₗ[R] ⨂[R] i : ι ⊕ ι₂, M :=
tensor_product.lift
  { to_fun := λ a, pi_tensor_product.lift $ pi_tensor_product.lift
      (multilinear_map.curry_sum_equiv R _ _ M _ (tprod R)) a,
    map_add' := λ a b, by simp only [linear_equiv.map_add, linear_map.map_add],
    map_smul' := λ r a, by simp only [linear_equiv.map_smul, linear_map.map_smul,
                                      ring_hom.id_apply], }

private lemma tmul_apply (a : ι → M) (b : ι₂ → M) :
  tmul ((⨂ₜ[R] i, a i) ⊗ₜ[R] (⨂ₜ[R] i, b i)) = ⨂ₜ[R] i, sum.elim a b i :=
begin
  erw [tensor_product.lift.tmul, pi_tensor_product.lift.tprod, pi_tensor_product.lift.tprod],
  refl
end

/-- Expand `pi_tensor_product` into a `tensor_product` of two factors. -/
private def tmul_symm : ⨂[R] i : ι ⊕ ι₂, M →ₗ[R] (⨂[R] i : ι, M) ⊗[R] (⨂[R] i : ι₂, M) :=
-- by using tactic mode, we avoid the need for a lot of `@`s and `_`s
pi_tensor_product.lift $ by apply multilinear_map.dom_coprod; [exact tprod R, exact tprod R]

private lemma tmul_symm_apply (a : ι ⊕ ι₂ → M) :
  tmul_symm (⨂ₜ[R] i, a i) = (⨂ₜ[R] i, a (sum.inl i)) ⊗ₜ[R] (⨂ₜ[R] i, a (sum.inr i)) :=
pi_tensor_product.lift.tprod _

variables (R M)
local attribute [ext] tensor_product.ext

/-- Equivalence between a `tensor_product` of `pi_tensor_product`s and a single
`pi_tensor_product` indexed by a `sum` type.

For simplicity, this is defined only for homogeneously- (rather than dependently-) typed components.
-/
def tmul_equiv : (⨂[R] i : ι, M) ⊗[R] (⨂[R] i : ι₂, M) ≃ₗ[R] ⨂[R] i : ι ⊕ ι₂, M :=
linear_equiv.of_linear tmul tmul_symm
  (by { ext x,
        show tmul (tmul_symm (tprod R x)) = tprod R x, -- Speed up the call to `simp`.
        simp only [tmul_symm_apply, tmul_apply, sum.elim_comp_inl_inr], })
  (by { ext x y,
        show tmul_symm (tmul (tprod R x ⊗ₜ[R] tprod R y)) = tprod R x ⊗ₜ[R] tprod R y,
        simp only [tmul_apply, tmul_symm_apply, sum.elim_inl, sum.elim_inr], })

@[simp] lemma tmul_equiv_apply (a : ι → M) (b : ι₂ → M) :
  tmul_equiv R M ((⨂ₜ[R] i, a i) ⊗ₜ[R] (⨂ₜ[R] i, b i)) = ⨂ₜ[R] i, sum.elim a b i :=
tmul_apply a b

@[simp] lemma tmul_equiv_symm_apply (a : ι ⊕ ι₂ → M) :
  (tmul_equiv R M).symm (⨂ₜ[R] i, a i) = (⨂ₜ[R] i, a (sum.inl i)) ⊗ₜ[R] (⨂ₜ[R] i, a (sum.inr i)) :=
tmul_symm_apply a

end tmul

end multilinear

end pi_tensor_product

end semiring

section ring
namespace pi_tensor_product

open pi_tensor_product
open_locale tensor_product

variables {ι : Type*} {R : Type*} [comm_ring R]
variables {s : ι → Type*} [∀ i, add_comm_group (s i)] [∀ i, module R (s i)]

/- Unlike for the binary tensor product, we require `R` to be a `comm_ring` here, otherwise
this is false in the case where `ι` is empty. -/
instance : add_comm_group (⨂[R] i, s i) := module.add_comm_monoid_to_add_comm_group R

end pi_tensor_product
end ring


import data.real.basic

#check mul_assoc

-- BEGIN
example (a b c d e f : ℝ) (h : a * b = c * d) (h' : e = f) :
  a * (b * e) = c * (d * f) :=
begin
  rw h',
  rw ←mul_assoc,
  sorry,
end
-- END


import categories.category
import categories.isomorphism
import categories.tactics
import categories.functor
import categories.Ndefs
open categories categories.isomorphism categories.functor categories.idempotent tactic

--delaration of universes and variables
universes u v u₁ v₁
variables (C : Type u₁) [𝒞 : category.{u₁ v₁} C]
include 𝒞

lemma Split_Idem_is_Idem (X : C) (e : X ⟶ X) : (is_Split_Idempotent e) → (is_Idempotent e) :=
begin
    intro hsi,
    cases (classical.indefinite_description _ hsi) with f hf,
    cases (classical.indefinite_description _ hf) with g hg,
    exact calc
        e ≫ e = (f ≫ g) ≫ f ≫ g : by rw hg.1
        ... = f ≫ g ≫ f ≫ g : by rw category.associativity_lemma
        ... = f ≫ (g ≫ f) ≫ g : by rw category.associativity_lemma
        ... = f ≫ 𝟙X ≫ g : by rw hg.2
        ... = f ≫ g : by rw category.left_identity_lemma
        ... =  e : by rw hg.1
end


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem mathd_algebra_422
  (x : ℝ)
  (σ : equiv ℝ ℝ)
  (h₀ : ∀ x, σ.1 x = 5 * x - 12)
  (h₁ : σ.1 (x + 1) = σ.2 x) :
  x = 47 / 24 :=
begin
  field_simp [h₀, mul_add, add_mul, sub_add_cancel, mul_assoc, add_comm],
  have := congr_arg σ.to_fun h₁,
  rw h₀ at this,
  rw h₀ at this,
  symmetry,
  norm_num at this,
  linarith,
end


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

theorem amc12a_2017_p2
  (x y : ℝ)
  (h₀ : x ≠ 0)
  (h₁ : y ≠ 0)
  (h₂ : x + y = 4 * (x * y)) :
  1 / x + 1 / y = 4 :=
begin
  field_simp,
  linarith,
end


import field_theory.galois
import linear_algebra.determinant
import data.polynomial.derivative
import data.matrix.notation
import data.multiset.basic
import algebra.module.basic
section resultant
variables {R : Type*}
open matrix polynomial
open_locale polynomial
-- TODO redefine this in terms of a matrix.from_column_blocks, or maybe dont as types of
-- indices will be weird
noncomputable theory
/-- The Sylvester matrix of two polynomials -/
def sylvester_matrix [semiring R] (p q : polynomial R) :
  matrix (fin $ p.nat_degree + q.nat_degree) (fin $ p.nat_degree + q.nat_degree) R :=
λ i j, if (i : ℕ) < q.nat_degree
       then (p * X ^ (i : ℕ)).coeff j
       else (q * X ^ (i - q.nat_degree : ℕ)).coeff j
-- example : sylvester_matrix (X : polynomial ℕ) X = ![![0, 0], ![1,1]] :=
-- begin
--   norm_num [sylvester_matrix],
--   ext,
--   simp,
--   intros a h hh, fin_cases h,
--   norm_num [hh],
--   rw hh,
--   admit,
--   admit,
-- end

example : (sylvester_matrix (X : polynomial ℕ) X) ⟨1, by simp⟩ ⟨1, by simp⟩ = 1:=
by norm_num [sylvester_matrix]
example : (sylvester_matrix (X : polynomial ℕ) X) ⟨1, by simp⟩ ⟨0, by simp⟩ = 0:=
by norm_num [sylvester_matrix]
example : (sylvester_matrix (X : polynomial ℕ) X) ⟨0, by simp⟩ ⟨1, by simp⟩ = 1:=
by norm_num [sylvester_matrix]
example : (sylvester_matrix (X : polynomial ℕ) X) ⟨0, by simp⟩ ⟨0, by simp⟩ = 0:=
by norm_num [sylvester_matrix]

/-- The resultant of two polynomials -/
def resultant [comm_ring R] (p q : polynomial R) : R := det (sylvester_matrix p q)
-- include (-1)^(n(n-1)/2)/a_n part by updating sylvester first col
/-- The discriminant of a polynomial defined as the resultant of p and its derivative -/
def discriminant' [comm_ring R] (p : polynomial R) : R := resultant p p.derivative

-- translation invariance
-- scaling by a^n(n-1)
-- reversal invariance
-- degree preserving ring maps
-- disc prod is prod discs times resultant square

lemma discriminant'_C_mul_X_add_C [comm_ring R] {b c : R} (h : b ≠ 0) :
  discriminant' (C b * X + C c) = b := --big note: this used to be `discriminat' = 1`!!
begin
  have : (C b * X + C c).nat_degree = 1,
  { apply nat_degree_eq_of_degree_eq_some,
    simp only [degree_add_C, degree_C_mul_X h, ←with_bot.coe_zero,
               ←with_bot.coe_one, with_bot.coe_lt_coe, zero_lt_one] },
  -- I'm squeezing the simps to make working on the file faster, if later someone wants to put
  -- back the original one: `simp only [discriminant', resultant, sylvester_matrix]`.
  simp only [discriminant', resultant, sylvester_matrix, tsub_zero, add_zero, derivative_X,
             mul_one, derivative_add, derivative_mul, coeff_C_mul, zero_mul, if_false,
             nat_degree_C, zero_add, derivative_C, not_lt_zero'],
  convert_to det (λ (i j : fin 1), b * (X ^ ↑i).coeff ↑j) = b,
  { congr, any_goals { rw [this, nat_degree_C, add_zero] } },
  norm_num
end
-- does this work without taking n ≥ 2? be careful with signs
-- Eric: doesn't work for n=0 or n=1:
/-
  (with n = 0 substituted)
  simp only [mul_one, nat.cast_zero, add_left_neg, pow_zero],
  rw [add_right_comm, ←C_add, add_comm, discriminant'_C_mul_X_add_C]

  (with n=1 substituted)
  simp only [neg_mul_eq_neg_mul_symm, one_mul, pow_one, nat.cast_one, pow_zero],
  rw [←add_mul, ←C_add, discriminant'_C_mul_X_add_C]
-/
lemma discriminant'_mul_X_pow_add_C_mul_X_add_C [comm_ring R] {a b c : R}
  (ha : a ≠ 0) : ∀ {n : ℕ}, 2 ≤ n → discriminant' (C a * (X : polynomial R)^n + C b * X + C c) =
    -(n - 1)^(n-1) * b^n + n^n * a^(n-1) * (-c)^(n-1)
| 0     hn := (hn.not_lt zero_lt_two).rec _
| 1     hn := (hn.not_lt one_lt_two).rec _
| (n+2) _ := begin
  sorry
end

-- this should give quadratic as b^2 - 4ac and depressed cubic as
-- -4ac^3 - 27 a^2d^2 = -(n-1)^(n-1)a c ^n - n^n a^(n-1) b^(n-1)

-- example of computing this thing
example : discriminant' ((X : ℚ[X]) ^ 5 + -X + -1) = 3381 :=
begin
  have := @discriminant'_mul_X_pow_add_C_mul_X_add_C _ _ (1 : ℚ) (-1) (-1) one_ne_zero 5,
  norm_num at this,
  exact this,
end
end resultant


theorem all_nne : ∀ (α : Type 1) (p : α → Prop),
    (∀ x, p x) → ¬ (∃ x, ¬ p x) :=
begin
    assume α p,
        assume h,
        assume e,
        show false,
            apply exists.elim e,
                intros,
                have : p a := h a,
                show false, from a_1 this,
end

example : ¬ (∀ (X : Type), ∀ (p q : X → Prop),
    (∃ x, p x) ∧ (∃ x, q x) → ∃ x, p x ∧ q x) :=
begin
    assume h,
    have ne : ¬ ∃ X : Type, ¬ (∀ (p q : X → Prop),
    (∃ x, p x) ∧ (∃ x, q x) → ∃ x, p x ∧ q x),
        apply all_nne, assumption,

    have : ∃ X : Type, ¬ (∀ (p q : X → Prop),
    (∃ x, p x) ∧ (∃ x, q x) → ∃ x, p x ∧ q x),
        apply exists.intro ℕ,
        assume h2,
        have h3 : (∃ x, x = 0) ∧ (∃ x, x = 1) →
                    (∃ x, x = 0 ∧ x = 1),
            from h2 (λ a, a = 0) (λ b, b = 1),
        have pfx0 : (∃ x, x = 0) := ⟨0, rfl⟩,
        have pfx1 : (∃ x, x = 1) := ⟨1, rfl⟩,
        have : (∃ x, x = 0 ∧ x = 1) := h3 ⟨pfx0, pfx1⟩,
        apply exists.elim this,
            intros x x0x1,
            have l : x = 0 := x0x1.1,
            have r : x = 1 := x0x1.2,
            have : 0 = 1 := eq.subst r (eq.symm l),
            show false, from nat.no_confusion this,
    contradiction,
end


/-
Copyright (c) 2019 Seul Baek. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Seul Baek

! This file was ported from Lean 3 source module data.list.func
! leanprover-community/mathlib commit d11893b411025250c8e61ff2f12ccbd7ee35ab15
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Nat.Order.Basic

/-!
# Lists as Functions

Definitions for using lists as finite representations of finitely-supported functions with domain
ℕ.

These include pointwise operations on lists, as well as get and set operations.

## Notations

An index notation is introduced in this file for setting a particular element of a list. With `as`
as a list `m` as an index, and `a` as a new element, the notation is `as {m ↦ a}`.

So, for example
`[1, 3, 5] {1 ↦ 9}` would result in `[1, 9, 5]`

This notation is in the locale `list.func`.
-/

open List

universe u v w

variable {α : Type u} {β : Type v} {γ : Type w}

namespace List

namespace Func

variable {a : α}

variable {as as1 as2 as3 : List α}

/-- Elementwise negation of a list -/
def neg [Neg α] (as : List α) :=
  as.map fun a ↦ -a
#align list.func.neg List.Func.neg

variable [Inhabited α] [Inhabited β]

/-- Update element of a list by index. If the index is out of range, extend the list with default
elements
-/
@[simp]
def set (a : α) : List α → ℕ → List α
  | _ :: as, 0 => a :: as
  | [], 0 => [a]
  | h :: as, k + 1 => h :: set a as k
  | [], k + 1 => default :: set a ([] : List α) k
#align list.func.set List.Func.set

-- mathport name: list.func.set
@[inherit_doc]
scoped notation as " {" m " ↦ " a "}" => List.Func.set a as m

/-- Get element of a list by index. If the index is out of range, return the default element -/
@[simp]
def get : ℕ → List α → α
  | _, [] => default
  | 0, a :: _ => a
  | n + 1, _ :: as => get n as
#align list.func.get List.Func.get

/-- Pointwise equality of lists. If lists are different lengths, compare with the default
element.
-/
def Equiv (as1 as2 : List α) : Prop :=
  ∀ m : Nat, get m as1 = get m as2
#align list.func.equiv List.Func.Equiv

/-- Pointwise operations on lists. If lists are different lengths, use the default element. -/
@[simp]
def pointwise (f : α → β → γ) : List α → List β → List γ
  | [], [] => []
  | [], b :: bs => map (f default) (b :: bs)
  | a :: as, [] => map (fun x ↦ f x default) (a :: as)
  | a :: as, b :: bs => f a b :: pointwise f as bs
#align list.func.pointwise List.Func.pointwise

/-- Pointwise addition on lists. If lists are different lengths, use zero. -/
def add {α : Type u} [Zero α] [Add α] : List α → List α → List α :=
  @pointwise α α α ⟨0⟩ ⟨0⟩ (· + ·)
#align list.func.add List.Func.add

/-- Pointwise subtraction on lists. If lists are different lengths, use zero. -/
def sub {α : Type u} [Zero α] [Sub α] : List α → List α → List α :=
  @pointwise α α α ⟨0⟩ ⟨0⟩ (@Sub.sub α _)
#align list.func.sub List.Func.sub

-- set
theorem length_set : ∀ {m : ℕ} {as : List α}, as {m ↦ a}.length = max as.length (m + 1)
  | 0, [] => rfl
  | 0, a :: as => by
    rw [max_eq_left]
    · rfl
    · simp [Nat.le_add_right]
      exact Nat.succ_le_succ (Nat.zero_le _)
  | m + 1, [] => by
    have := @length_set m []
    simp [set, length, @length_set m, Nat.zero_max]
  | m + 1, _ :: as => by
    simp [set, length, @length_set m, Nat.max_succ_succ]

#align list.func.length_set List.Func.length_set

-- porting note : @[simp] has been removed since `#lint` says this is
theorem get_nil {k : ℕ} : (get k [] : α) = default := by cases k <;> rfl
#align list.func.get_nil List.Func.get_nil

theorem get_eq_default_of_le : ∀ (k : ℕ) {as : List α}, as.length ≤ k → get k as = default
  | 0, [], _ => rfl
  | 0, a :: as, h1 => by cases h1
  | k + 1, [], _ => rfl
  | k + 1, _ :: as, h1 => by
    apply get_eq_default_of_le k
    rw [← Nat.succ_le_succ_iff]; apply h1
#align list.func.get_eq_default_of_le List.Func.get_eq_default_of_le

@[simp]
theorem get_set {a : α} : ∀ {k : ℕ} {as : List α}, get k (as {k ↦ a}) = a
  | 0, as => by cases as <;> rfl
  | k + 1, as => by cases as <;> simp [get_set]
#align list.func.get_set List.Func.get_set

theorem eq_get_of_mem {a : α} : ∀ {as : List α}, a ∈ as → ∃ n : Nat, a = get n as
  | [], h => by cases h
  | b :: as, h => by
    rw [mem_cons] at h -- porting note : `mem_cons_iff` is now named `mem_cons`
    cases h with
    | inl h => exact ⟨0, h⟩
    | inr h =>
      rcases eq_get_of_mem h with ⟨n, h⟩
      exact ⟨n + 1, h⟩
#noalign list.func.eq_get_of_mem
-- porting note : the signature has been changed to correct what was presumably a bug,
-- hence the #noalign

theorem mem_get_of_le : ∀ {n : ℕ} {as : List α}, n < as.length → get n as ∈ as
  | _, [], h1 => by cases h1
  -- porting note : needed to add to `rw [mem_cons] here` in the two cases below
  -- and in other lemmas (presumably because previously lean could see through the def of `mem` ?)
  | 0, a :: as, _ => by rw [mem_cons]; exact Or.inl rfl
  | n + 1, a :: as, h1 => by
    rw [mem_cons]; apply Or.inr; unfold get
    apply mem_get_of_le
    apply Nat.lt_of_succ_lt_succ h1
#align list.func.mem_get_of_le List.Func.mem_get_of_le

theorem mem_get_of_ne_zero : ∀ {n : ℕ} {as : List α}, get n as ≠ default → get n as ∈ as
  | _, [], h1 => by exfalso; apply h1; rw [get_nil]
  | 0, a :: as, _ => by rw [mem_cons]; exact Or.inl rfl
  | n + 1, a :: as, h1 => by
    rw [mem_cons]; unfold get
    apply Or.inr (mem_get_of_ne_zero _)
    apply h1
#align list.func.mem_get_of_ne_zero List.Func.mem_get_of_ne_zero

theorem get_set_eq_of_ne {a : α} :
    ∀ {as : List α} (k : ℕ) (m : ℕ), m ≠ k → get m (as {k ↦ a}) = get m as
  | as, 0, m, h1 => by
    cases m
    contradiction
    cases as <;> simp only [set, get, get_nil]
  | as, k + 1, m, h1 => by
    -- porting note : I somewhat rearranged the case split
    cases as <;> cases m
    case nil =>
      simp only [set, get]
    case nil m =>
      have h3 : get m (nil {k ↦ a}) = default := by
        rw [get_set_eq_of_ne k m, get_nil]
        intro hc
        apply h1
        simp [hc]
      apply h3
    case zero =>
      simp only [set, get]
    case _ _ m =>
      apply get_set_eq_of_ne k m
      intro hc
      apply h1
      simp [hc]
#align list.func.get_set_eq_of_ne List.Func.get_set_eq_of_ne

theorem get_map {f : α → β} :
    ∀ {n : ℕ} {as : List α}, n < as.length → get n (as.map f) = f (get n as)
  | _, [], h => by cases h
  | 0, a :: as, _ => rfl
  | n + 1, _ :: as, h1 => by
    have h2 : n < length as := by
      rw [← Nat.succ_le_iff, ← Nat.lt_succ_iff]
      apply h1
    apply get_map h2
#align list.func.get_map List.Func.get_map

theorem forall_val_of_forall_mem {as : List α} {p : α → Prop} :
    p default → (∀ x ∈ as, p x) → ∀ n, p (get n as) := by
  intro h1 h2 n
  by_cases h3 : n < as.length
  · apply h2 _ (mem_get_of_le h3)
  · rw [not_lt] at h3
    rw [get_eq_default_of_le _ h3]
    apply h1
#align list.func.forall_val_of_forall_mem List.Func.forall_val_of_forall_mem

-- equiv
theorem equiv_refl : Equiv as as := fun _ ↦ rfl
#align list.func.equiv_refl List.Func.equiv_refl

theorem equiv_symm : Equiv as1 as2 → Equiv as2 as1 := fun h1 k ↦ (h1 k).symm
#align list.func.equiv_symm List.Func.equiv_symm

theorem equiv_trans : Equiv as1 as2 → Equiv as2 as3 → Equiv as1 as3 := fun h1 h2 k ↦
  Eq.trans (h1 k) (h2 k)
#align list.func.equiv_trans List.Func.equiv_trans

theorem equiv_of_eq : as1 = as2 → Equiv as1 as2 := by intro h1; rw [h1]; apply equiv_refl
#align list.func.equiv_of_eq List.Func.equiv_of_eq

theorem eq_of_equiv : ∀ {as1 as2 : List α}, as1.length = as2.length → Equiv as1 as2 → as1 = as2
  | [], [], _, _ => rfl
  | _ :: _, [], h1, _ => by cases h1
  | [], _ :: _, h1, _ => by cases h1
  | a1 :: as1, a2 :: as2, h1, h2 => by
    congr
    · apply h2 0
    · have h3 : as1.length = as2.length := by simpa [add_left_inj, add_comm, length] using h1
      apply eq_of_equiv h3
      intro m
      apply h2 (m + 1)
#align list.func.eq_of_equiv List.Func.eq_of_equiv

end Func

-- We want to drop the `inhabited` instances for a moment,
-- so we close and open the namespace
namespace Func

-- neg
@[simp]
theorem get_neg [AddGroup α] {k : ℕ} {as : List α} : @get α ⟨0⟩ k (neg as) = -@get α ⟨0⟩ k as := by
  unfold neg
  rw [@get_map' α α ⟨0⟩ ⟨0⟩] -- porting note: had to add a `⟨0⟩` b/c of instance troubles
  apply neg_zero
#align list.func.get_neg List.Func.get_neg

@[simp]
theorem length_neg [Neg α] (as : List α) : (neg as).length = as.length := by
  simp only [neg, length_map]
#align list.func.length_neg List.Func.length_neg

variable [Inhabited α] [Inhabited β]

-- pointwise
theorem nil_pointwise {f : α → β → γ} : ∀ bs : List β, pointwise f [] bs = bs.map (f default)
  | [] => rfl
  | b :: bs => by simp only [nil_pointwise bs, pointwise, eq_self_iff_true, and_self_iff, map]
#align list.func.nil_pointwise List.Func.nil_pointwise

theorem pointwise_nil {f : α → β → γ} :
    ∀ as : List α, pointwise f as [] = as.map fun a ↦ f a default
  | [] => rfl
  | a :: as => by simp only [pointwise_nil as, pointwise, eq_self_iff_true, and_self_iff, List.map]
#align list.func.pointwise_nil List.Func.pointwise_nil

theorem get_pointwise [Inhabited γ] {f : α → β → γ} (h1 : f default default = default) :
    ∀ (k : Nat) (as : List α) (bs : List β), get k (pointwise f as bs) = f (get k as) (get k bs)
  | k, [], [] => by simp only [h1, get_nil, pointwise, get]
  | 0, [], b :: _ => by simp only [get_pointwise, get_nil, pointwise, get, Nat.zero_eq, map]
  | k + 1, [], b :: bs => by
    have : get k (map (f default) bs) = f default (get k bs) := by
      simpa [nil_pointwise, get_nil] using get_pointwise h1 k [] bs
    simpa [get, get_nil, pointwise, map]
  | 0, a :: _, [] => by simp only [get_pointwise, get_nil, pointwise, get, Nat.zero_eq, map]
  | k + 1, a :: as, [] => by
    simpa [get, get_nil, pointwise, map, pointwise_nil, get_nil] using get_pointwise h1 k as []
  | 0, a :: _, b :: _ => by simp only [pointwise, get]
  | k + 1, _ :: as, _ :: bs => by
    simp only [get, Nat.add_eq, add_zero, get_pointwise h1 k as bs]
#align list.func.get_pointwise List.Func.get_pointwise

theorem length_pointwise {f : α → β → γ} :
    ∀ {as : List α} {bs : List β}, (pointwise f as bs).length = max as.length bs.length
  | [], [] => rfl
  | [], _ :: bs => by
    simp only [pointwise, length, length_map, max_eq_right (Nat.zero_le (length bs + 1))]
  | _ :: as, [] => by
    simp only [pointwise, length, length_map, max_eq_left (Nat.zero_le (length as + 1))]
  | _ :: as, _ :: bs => by
    simp only [pointwise, length, Nat.max_succ_succ, @length_pointwise _ as bs]
#align list.func.length_pointwise List.Func.length_pointwise

end Func

namespace Func

-- add
@[simp]
theorem get_add {α : Type u} [AddMonoid α] {k : ℕ} {xs ys : List α} :
    -- porting note : `@` and `⟨0⟩`s added b/c of instance troubles
    -- (similarly at other places below)
    @get α ⟨0⟩ k (add xs ys) = @get α ⟨0⟩ k xs + @get α ⟨0⟩ k ys := by
  apply @get_pointwise _ _ _ ⟨0⟩ ⟨0⟩ ⟨0⟩
  apply zero_add
#align list.func.get_add List.Func.get_add

@[simp]
theorem length_add {α : Type u} [Zero α] [Add α] {xs ys : List α} :
    (add xs ys).length = max xs.length ys.length :=
  @length_pointwise α α α ⟨0⟩ ⟨0⟩ _ _ _
#align list.func.length_add List.Func.length_add

@[simp]
theorem nil_add {α : Type u} [AddMonoid α] (as : List α) : add [] as = as := by
  rw [add, @nil_pointwise α α α ⟨0⟩ ⟨0⟩]
  apply Eq.trans _ (map_id as)
  congr with x
  exact zero_add x
  -- porting note: instead of `zero_add`, it was the commented `rw` below
  -- (similarly at other places below)
  --rw [zero_add, id]
#align list.func.nil_add List.Func.nil_add

@[simp]
theorem add_nil {α : Type u} [AddMonoid α] (as : List α) : add as [] = as := by
  rw [add, @pointwise_nil α α α ⟨0⟩ ⟨0⟩]
  apply Eq.trans _ (map_id as)
  congr with x
  exact add_zero x
#align list.func.add_nil List.Func.add_nil

theorem map_add_map {α : Type u} [AddMonoid α] (f g : α → α) {as : List α} :
    add (as.map f) (as.map g) = as.map fun x ↦ f x + g x := by
  apply @eq_of_equiv _ (⟨0⟩ : Inhabited α)
  · rw [length_map, length_add, max_eq_left, length_map]
    apply le_of_eq
    rw [length_map, length_map]
  intro m
  rw [get_add]
  by_cases h : m < length as
  · repeat' rw [@get_map α α ⟨0⟩ ⟨0⟩ _ _ _ h]
  rw [not_lt] at h
  repeat' rw [@get_eq_default_of_le _ ⟨0⟩ m] <;> try rw [length_map]; apply h
  exact zero_add _
#align list.func.map_add_map List.Func.map_add_map

-- sub
@[simp]
theorem get_sub {α : Type u} [AddGroup α] {k : ℕ} {xs ys : List α} :
    @get α ⟨0⟩ k (sub xs ys) = @get α ⟨0⟩ k xs - @get α ⟨0⟩ k ys := by
  apply @get_pointwise _ _ _ ⟨0⟩ ⟨0⟩ ⟨0⟩
  apply sub_zero
#align list.func.get_sub List.Func.get_sub

@[simp]
theorem length_sub [Zero α] [Sub α] {xs ys : List α} :
    (sub xs ys).length = max xs.length ys.length :=
  @length_pointwise α α α ⟨0⟩ ⟨0⟩ _ _ _
#align list.func.length_sub List.Func.length_sub

@[simp]
theorem nil_sub {α : Type _} [AddGroup α] (as : List α) : sub [] as = neg as := by
  rw [sub, @nil_pointwise _ _ _ ⟨0⟩ ⟨0⟩]
  congr with x
  exact zero_sub x
#align list.func.nil_sub List.Func.nil_sub

@[simp]
theorem sub_nil {α : Type _} [AddGroup α] (as : List α) : sub as [] = as := by
  rw [sub, @pointwise_nil _ _ _ ⟨0⟩ ⟨0⟩]
  apply Eq.trans _ (map_id as)
  congr with x
  exact sub_zero x
#align list.func.sub_nil List.Func.sub_nil

end Func

end List


/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner
-/
prelude
import init.data.nat init.data.array.basic init.data.nat.lemmas
universes u
variables {α : Type u} {n : nat}

namespace array

def slice (a : array n α) (k l : nat) (h₁ : k ≤ l) (h₂ : l ≤ n) : array (l - k) α :=
⟨ λ ⟨ i, hi ⟩, a.read ⟨ i + k,
  calc i + k < (l - k) + k : nat.add_lt_add_right hi _
         ... = l : nat.sub_add_cancel h₁
         ... ≤ n : h₂⟩ ⟩

def take (a : array n α) (m : nat) (h : m ≤ n) : array m α :=
cast (by simp) $ a.slice 0 m (nat.zero_le _) h

def drop (a : array n α) (m : nat) (h : m ≤ n) : array (n-m) α :=
a.slice m n h (le_refl _)

private lemma sub_sub_cancel (m n : ℕ) (h : m ≤ n) : n - (n - m) = m :=
calc n - (n - m) = (n - m) + m - (n - m) : by rw nat.sub_add_cancel; assumption
             ... = m : nat.add_sub_cancel_left _ _

def take_right (a : array n α) (m : nat) (h : m ≤ n) : array m α :=
cast (by simp [*, sub_sub_cancel]) $ a.drop (n - m) (nat.sub_le _ _)

def reverse (a : array n α) : array n α :=
⟨ λ ⟨ i, hi ⟩, a.read ⟨ n - (i + 1),
  begin apply nat.sub_lt_of_pos_le, apply nat.zero_lt_succ, assumption end ⟩ ⟩

end array


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl

Binder elimination
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.order.default
import Mathlib.PostPort

universes u v

namespace Mathlib

namespace old_conv

/- congr should forward data! -/

end old_conv

/- Binder elimination:

We assume a binder `B : p → Π (α : Sort u), (α → t) → t`, where `t` is a type depending on `p`.
Examples:
  ∃: there is no `p` and `t` is `Prop`.
  ⨅, ⨆: here p is `β` and `[complete_lattice β]`, `p` is `β`

Problem: ∀x, _ should be a binder, but is not a constant!

Provide a mechanism to rewrite:

  B (x : α) ..x.. (h : x = t), p x  =  B ..x/t.., p t

Here ..x.. are binders, maybe also some constants which provide commutativity rules with `B`.

-/

theorem exists_elim_eq_left {α : Sort u} (a : α) (p : (a' : α) → a' = a → Prop) : (∃ (a' : α), ∃ (h : a' = a), p a' h) ↔ p a rfl := sorry

theorem exists_elim_eq_right {α : Sort u} (a : α) (p : (a' : α) → a = a' → Prop) : (∃ (a' : α), ∃ (h : a = a'), p a' h) ↔ p a rfl := sorry

theorem forall_comm {α : Sort u} {β : Sort v} (p : α → β → Prop) : (∀ (a : α) (b : β), p a b) ↔ ∀ (b : β) (a : α), p a b :=
  { mp := fun (h : ∀ (a : α) (b : β), p a b) (b : β) (a : α) => h a b,
    mpr := fun (h : ∀ (b : β) (a : α), p a b) (b : α) (a : β) => h a b }

theorem forall_elim_eq_left {α : Sort u} (a : α) (p : (a' : α) → a' = a → Prop) : (∀ (a' : α) (h : a' = a), p a' h) ↔ p a rfl := sorry

theorem forall_elim_eq_right {α : Sort u} (a : α) (p : (a' : α) → a = a' → Prop) : (∀ (a' : α) (h : a = a'), p a' h) ↔ p a rfl := sorry


import GMLInit.Data.Nat.Extended.Basic
import GMLInit.Data.Nat.Extended.Finite

namespace ENat
variable (p : Nat → Bool)

private def first.isLEAux : Nat → Bool
| 0 => p 0
| x+1 => p (x+1) || isLEAux x

def first : ENat where
  isLE := first.isLEAux p
  step := by
    intro x hx
    clean unfold first.isLEAux
    simp [hx]

variable {p}
theorem first.isLE_of (x : Nat) : p x → (first p).isLE x := by
  match x with
  | 0 => intro h; exact h
  | x+1 => intro h; simp [first, first.isLEAux, h]

theorem first.exists_le_of_isLE (x : Nat) : (first p).isLE x → ∃ y, y ≤ x ∧ p y := by
  induction x with
  | zero =>
    intro h
    exists (0:Nat)
  | succ x H =>
    intro h
    clean unfold first first.isLEAux at h
    rw [Bool.or_eq_true] at h
    cases h with
    | inl h =>
      exists (x+1)
      constr
      · reflexivity
      · exact h
    | inr h =>
      match H h with
      | ⟨y, hle, hy⟩ =>
        exists y
        constr
        · transitivity x
          · exact hle
          · exact Nat.le_succ x
        · exact hy

@[simp] theorem first.isLE_iff_exists_le (x) : (first p).isLE x ↔ ∃ y, y ≤ x ∧ p y := by
  constr
  · exact first.exists_le_of_isLE x
  · intro ⟨y, hle, hy⟩
    apply mono _ hle
    exact first.isLE_of _ hy

theorem first.isFinite_of {x} : p x → Finite (first p) := by
  intro h
  exists x
  exact first.isLE_of _ h

theorem first.isFinite_of_exists : (∃ x, p x) → Finite (first p) := by
  intro ⟨_,h⟩
  exact first.isFinite_of h

end ENat


/-
Copyright (c) 2022 Michael Stoll. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Michael Stoll

! This file was ported from Lean 3 source module number_theory.legendre_symbol.gauss_sum
! leanprover-community/mathlib commit d11893b411025250c8e61ff2f12ccbd7ee35ab15
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.NumberTheory.LegendreSymbol.AddCharacter
import Mathbin.NumberTheory.LegendreSymbol.ZmodChar
import Mathbin.Algebra.CharP.CharAndCard

/-!
# Gauss sums

We define the Gauss sum associated to a multiplicative and an additive
character of a finite field and prove some results about them.

## Main definition

Let `R` be a finite commutative ring and let `R'` be another commutative ring.
If `χ` is a multiplicative character `R → R'` (type `mul_char R R'`) and `ψ`
is an additive character `R → R'` (type `add_char R R'`, which abbreviates
`(multiplicative R) →* R'`), then the *Gauss sum* of `χ` and `ψ` is `∑ a, χ a * ψ a`.

## Main results

Some important results are as follows.

* `gauss_sum_mul_gauss_sum_eq_card`: The product of the Gauss
  sums of `χ` and `ψ` and that of `χ⁻¹` and `ψ⁻¹` is the cardinality
  of the source ring `R` (if `χ` is nontrivial, `ψ` is primitive and `R` is a field).
* `gauss_sum_sq`: The square of the Gauss sum is `χ(-1)` times
  the cardinality of `R` if in addition `χ` is a quadratic character.
* `quad_gauss_sum_frob`: For a quadratic character `χ`, raising
  the Gauss sum to the `p`th power (where `p` is the characteristic of
  the target ring `R'`) multiplies it by `χ p`.
* `char.card_pow_card`: When `F` and `F'` are finite fields and `χ : F → F'`
  is a nontrivial quadratic character, then `(χ (-1) * #F)^(#F'/2) = χ (#F')`.
* `finite_field.two_pow_card`: For every finite field `F` of odd characteristic,
  we have `2^(#F/2) = χ₈(#F)` in `F`.

This machinery can be used to derive (a generalization of) the Law of
Quadratic Reciprocity.

## Tags

additive character, multiplicative character, Gauss sum
-/

universe u v

open BigOperators

open AddChar MulChar

section GaussSumDef

-- `R` is the domain of the characters
variable {R : Type u} [CommRing R] [Fintype R]

-- `R'` is the target of the characters
variable {R' : Type v} [CommRing R']

/-!
### Definition and first properties
-/

/-- Definition of the Gauss sum associated to a multiplicative and an additive character. -/
def gaussSum (χ : MulChar R R') (ψ : AddChar R R') : R' :=
  ∑ a, χ a * ψ a
#align gauss_sum gaussSum

/-- Replacing `ψ` by `mul_shift ψ a` and multiplying the Gauss sum by `χ a` does not change it. -/
theorem gaussSum_mulShift (χ : MulChar R R') (ψ : AddChar R R') (a : Rˣ) :
    χ a * gaussSum χ (mulShift ψ a) = gaussSum χ ψ :=
  by
  simp only [gaussSum, mul_shift_apply, Finset.mul_sum]
  simp_rw [← mul_assoc, ← map_mul]
  exact Fintype.sum_bijective _ a.mul_left_bijective _ _ fun x => rfl
#align gauss_sum_mul_shift gaussSum_mulShift

end GaussSumDef

/-!
### The product of two Gauss sums
-/

section GaussSumProd

-- In the following, we need `R` to be a finite field and `R'` to be a domain.
variable {R : Type u} [Field R] [Fintype R] {R' : Type v} [CommRing R'] [IsDomain R']

-- A helper lemma for `gauss_sum_mul_gauss_sum_eq_card` below
-- Is this useful enough in other contexts to be public?
private theorem gauss_sum_mul_aux {χ : MulChar R R'} (hχ : IsNontrivial χ) (ψ : AddChar R R')
    (b : R) : (∑ a, χ (a * b⁻¹) * ψ (a - b)) = ∑ c, χ c * ψ (b * (c - 1)) :=
  by
  cases' eq_or_ne b 0 with hb hb
  · -- case `b = 0`
    simp only [hb, inv_zero, MulZeroClass.mul_zero, MulChar.map_zero, MulZeroClass.zero_mul,
      Finset.sum_const_zero, map_zero_one, mul_one]
    exact hχ.sum_eq_zero.symm
  · -- case `b ≠ 0`
    refine' (Fintype.sum_bijective _ (Equiv.mulLeft_bijective₀ b hb) _ _ fun x => _).symm
    rw [mul_assoc, mul_comm x, ← mul_assoc, mul_inv_cancel hb, one_mul, mul_sub, mul_one]
#align gauss_sum_mul_aux gauss_sum_mul_aux

/-- We have `gauss_sum χ ψ * gauss_sum χ⁻¹ ψ⁻¹ = fintype.card R`
when `χ` is nontrivial and `ψ` is primitive (and `R` is a field). -/
theorem gaussSum_mul_gaussSum_eq_card {χ : MulChar R R'} (hχ : IsNontrivial χ) {ψ : AddChar R R'}
    (hψ : IsPrimitive ψ) : gaussSum χ ψ * gaussSum χ⁻¹ ψ⁻¹ = Fintype.card R :=
  by
  simp only [gaussSum, AddChar.inv_apply, Finset.sum_mul, Finset.mul_sum, MulChar.inv_apply']
  conv in _ * _ * (_ * _) => rw [mul_mul_mul_comm, ← map_mul, ← map_add_mul, ← sub_eq_add_neg]
  simp_rw [gauss_sum_mul_aux hχ ψ]
  rw [Finset.sum_comm]
  classical
    -- to get `[decidable_eq R]` for `sum_mul_shift`
    simp_rw [← Finset.mul_sum, sum_mul_shift _ hψ, sub_eq_zero, mul_ite, MulZeroClass.mul_zero]
    rw [Finset.sum_ite_eq' Finset.univ (1 : R)]
    simp only [Finset.mem_univ, map_one, one_mul, if_true]
#align gauss_sum_mul_gauss_sum_eq_card gaussSum_mul_gaussSum_eq_card

/-- When `χ` is a nontrivial quadratic character, then the square of `gauss_sum χ ψ`
is `χ(-1)` times the cardinality of `R`. -/
theorem gaussSum_sq {χ : MulChar R R'} (hχ₁ : IsNontrivial χ) (hχ₂ : IsQuadratic χ)
    {ψ : AddChar R R'} (hψ : IsPrimitive ψ) : gaussSum χ ψ ^ 2 = χ (-1) * Fintype.card R :=
  by
  rw [pow_two, ← gaussSum_mul_gaussSum_eq_card hχ₁ hψ, hχ₂.inv, mul_rotate']
  congr
  rw [mul_comm, ← gaussSum_mulShift _ _ (-1 : Rˣ), inv_mul_shift]
  rfl
#align gauss_sum_sq gaussSum_sq

end GaussSumProd

/-!
### Gauss sums and Frobenius
-/

section gaussSum_frob

variable {R : Type u} [CommRing R] [Fintype R] {R' : Type v} [CommRing R']

-- We assume that the target ring `R'` has prime characteristic `p`.
variable (p : ℕ) [fp : Fact p.Prime] [hch : CharP R' p]

include fp hch

/-- When `R'` has prime characteristic `p`, then the `p`th power of the Gauss sum
of `χ` and `ψ` is the Gauss sum of `χ^p` and `ψ^p`. -/
theorem gaussSum_frob (χ : MulChar R R') (ψ : AddChar R R') :
    gaussSum χ ψ ^ p = gaussSum (χ ^ p) (ψ ^ p) :=
  by
  rw [← frobenius_def, gaussSum, gaussSum, map_sum]
  simp_rw [pow_apply' χ fp.1.Pos, map_mul, frobenius_def]
  rfl
#align gauss_sum_frob gaussSum_frob

/-- For a quadratic character `χ` and when the characteristic `p` of the target ring
is a unit in the source ring, the `p`th power of the Gauss sum of`χ` and `ψ` is
`χ p` times the original Gauss sum. -/
theorem MulChar.IsQuadratic.gaussSum_frob (hp : IsUnit (p : R)) {χ : MulChar R R'}
    (hχ : IsQuadratic χ) (ψ : AddChar R R') : gaussSum χ ψ ^ p = χ p * gaussSum χ ψ := by
  rw [gaussSum_frob, pow_mul_shift, hχ.pow_char p, ← gaussSum_mulShift χ ψ hp.unit, ← mul_assoc,
    hp.unit_spec, ← pow_two, ← pow_apply' _ (by norm_num : 0 < 2), hχ.sq_eq_one, ← hp.unit_spec,
    one_apply_coe, one_mul]
#align mul_char.is_quadratic.gauss_sum_frob MulChar.IsQuadratic.gaussSum_frob

/-- For a quadratic character `χ` and when the characteristic `p` of the target ring
is a unit in the source ring and `n` is a natural number, the `p^n`th power of the Gauss
sum of`χ` and `ψ` is `χ (p^n)` times the original Gauss sum. -/
theorem MulChar.IsQuadratic.gaussSum_frob_iter (n : ℕ) (hp : IsUnit (p : R)) {χ : MulChar R R'}
    (hχ : IsQuadratic χ) (ψ : AddChar R R') : gaussSum χ ψ ^ p ^ n = χ (p ^ n) * gaussSum χ ψ :=
  by
  induction' n with n ih
  · rw [pow_zero, pow_one, pow_zero, MulChar.map_one, one_mul]
  ·
    rw [pow_succ, mul_comm p, pow_mul, ih, mul_pow, hχ.gauss_sum_frob _ hp, ← mul_assoc, pow_succ,
      mul_comm (p : R), map_mul, ← pow_apply' χ fp.1.Pos (p ^ n), hχ.pow_char p]
#align mul_char.is_quadratic.gauss_sum_frob_iter MulChar.IsQuadratic.gaussSum_frob_iter

end gaussSum_frob

/-!
### Values of quadratic characters
-/

section GaussSumValues

variable {R : Type u} [CommRing R] [Fintype R] {R' : Type v} [CommRing R'] [IsDomain R']

/-- If the square of the Gauss sum of a quadratic character is `χ(-1) * #R`,
then we get, for all `n : ℕ`, the relation `(χ(-1) * #R) ^ (p^n/2) = χ(p^n)`,
where `p` is the (odd) characteristic of the target ring `R'`.
This version can be used when `R` is not a field, e.g., `ℤ/8ℤ`. -/
theorem Char.card_pow_char_pow {χ : MulChar R R'} (hχ : IsQuadratic χ) (ψ : AddChar R R') (p n : ℕ)
    [fp : Fact p.Prime] [hch : CharP R' p] (hp : IsUnit (p : R)) (hp' : p ≠ 2)
    (hg : gaussSum χ ψ ^ 2 = χ (-1) * Fintype.card R) :
    (χ (-1) * Fintype.card R) ^ (p ^ n / 2) = χ (p ^ n) :=
  by
  have : gaussSum χ ψ ≠ 0 := by
    intro hf
    rw [hf, zero_pow (by norm_num : 0 < 2), eq_comm, mul_eq_zero] at hg
    exact
      not_isUnit_prime_of_dvd_card p
        ((CharP.cast_eq_zero_iff R' p _).mp <| hg.resolve_left (is_unit_one.neg.map χ).NeZero) hp
  rw [← hg]
  apply mul_right_cancel₀ this
  rw [← hχ.gauss_sum_frob_iter p n hp ψ, ← pow_mul, mul_comm, ← pow_succ,
    Nat.two_mul_div_two_add_one_of_odd (fp.1.eq_two_or_odd'.resolve_left hp').pow]
#align char.card_pow_char_pow Char.card_pow_char_pow

/-- When `F` and `F'` are finite fields and `χ : F → F'` is a nontrivial quadratic character,
then `(χ(-1) * #F)^(#F'/2) = χ(#F')`. -/
theorem Char.card_pow_card {F : Type _} [Field F] [Fintype F] {F' : Type _} [Field F'] [Fintype F']
    {χ : MulChar F F'} (hχ₁ : IsNontrivial χ) (hχ₂ : IsQuadratic χ)
    (hch₁ : ringChar F' ≠ ringChar F) (hch₂ : ringChar F' ≠ 2) :
    (χ (-1) * Fintype.card F) ^ (Fintype.card F' / 2) = χ (Fintype.card F') :=
  by
  obtain ⟨n, hp, hc⟩ := FiniteField.card F (ringChar F)
  obtain ⟨n', hp', hc'⟩ := FiniteField.card F' (ringChar F')
  let ψ := primitive_char_finite_field F F' hch₁
  let FF' := CyclotomicField ψ.n F'
  have hchar := Algebra.ringChar_eq F' FF'
  apply (algebraMap F' FF').Injective
  rw [map_pow, map_mul, map_natCast, hc', hchar, Nat.cast_pow]
  simp only [← MulChar.ringHomComp_apply]
  haveI := Fact.mk hp'
  haveI := Fact.mk (hchar.subst hp')
  rw [Ne, ← Nat.prime_dvd_prime_iff_eq hp' hp, ← isUnit_iff_not_dvd_char, hchar] at hch₁
  exact
    Char.card_pow_char_pow (hχ₂.comp _) ψ.char (ringChar FF') n' hch₁ (hchar ▸ hch₂)
      (gaussSum_sq (hχ₁.comp <| RingHom.injective _) (hχ₂.comp _) ψ.prim)
#align char.card_pow_card Char.card_pow_card

end GaussSumValues

section GaussSumTwo

/-!
### The quadratic character of 2

This section proves the following result.

For every finite field `F` of odd characteristic, we have `2^(#F/2) = χ₈(#F)` in `F`.
This can be used to show that the quadratic character of `F` takes the value
`χ₈(#F)` at `2`.

The proof uses the Gauss sum of `χ₈` and a primitive additive character on `ℤ/8ℤ`;
in this way, the result is reduced to `card_pow_char_pow`.
-/

open ZMod

/-- For every finite field `F` of odd characteristic, we have `2^(#F/2) = χ₈(#F)` in `F`. -/
theorem FiniteField.two_pow_card {F : Type _} [Fintype F] [Field F] (hF : ringChar F ≠ 2) :
    (2 : F) ^ (Fintype.card F / 2) = χ₈ (Fintype.card F) :=
  by
  have hp2 : ∀ n : ℕ, (2 ^ n : F) ≠ 0 := fun n => pow_ne_zero n (Ring.two_ne_zero hF)
  obtain ⟨n, hp, hc⟩ := FiniteField.card F (ringChar F)
  -- we work in `FF`, the eighth cyclotomic field extension of `F`
  let FF := (Polynomial.cyclotomic 8 F).SplittingField
  haveI : FiniteDimensional F FF :=
    Polynomial.IsSplittingField.finiteDimensional FF (Polynomial.cyclotomic 8 F)
  haveI : Fintype FF := FiniteDimensional.fintypeOfFintype F FF
  have hchar := Algebra.ringChar_eq F FF
  have FFp := hchar.subst hp
  haveI := Fact.mk FFp
  have hFF := ne_of_eq_of_ne hchar.symm hF
  -- `ring_char FF ≠ 2`
  have hu : IsUnit (ringChar FF : ZMod 8) :=
    by
    rw [isUnit_iff_not_dvd_char, ring_char_zmod_n]
    rw [Ne, ← Nat.prime_dvd_prime_iff_eq FFp Nat.prime_two] at hFF
    change ¬_ ∣ 2 ^ 3
    exact mt FFp.dvd_of_dvd_pow hFF
  -- there is a primitive additive character `ℤ/8ℤ → FF`, sending `a + 8ℤ ↦ τ^a`
  -- with a primitive eighth root of unity `τ`
  let ψ₈ := primitive_zmod_char 8 F (by convert hp2 3 <;> norm_num)
  let τ : FF := ψ₈.char 1
  have τ_spec : τ ^ 4 = -1 := by
    refine' (sq_eq_one_iff.1 _).resolve_left _ <;>
      · simp only [τ, ← map_nsmul_pow]
        erw [AddChar.IsPrimitive.zMod_char_eq_one_iff 8 ψ₈.prim]
        decide
  -- we consider `χ₈` as a multiplicative character `ℤ/8ℤ → FF`
  let χ := χ₈.ring_hom_comp (Int.castRingHom FF)
  have hχ : χ (-1) = 1 := NormNum.int_cast_one
  have hq : is_quadratic χ := is_quadratic_χ₈.comp _
  -- we now show that the Gauss sum of `χ` and `ψ₈` has the relevant property
  have hg : gaussSum χ ψ₈.char ^ 2 = χ (-1) * Fintype.card (ZMod 8) :=
    by
    rw [hχ, one_mul, card, gaussSum]
    convert← congr_arg (· ^ 2) (Fin.sum_univ_eight fun x => (χ₈ x : FF) * τ ^ x.val)
    · ext
      congr
      apply pow_one
    convert_to(0 + 1 * τ ^ 1 + 0 + -1 * τ ^ 3 + 0 + -1 * τ ^ 5 + 0 + 1 * τ ^ 7) ^ 2 = _
    · simp only [χ₈_apply, Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.head_cons,
        Matrix.cons_vec_bit0_eq_alt0, Matrix.cons_vec_bit1_eq_alt1, Matrix.cons_vecAppend,
        Matrix.cons_vecAlt0, Matrix.cons_vecAlt1, Int.cast_zero, Int.cast_one, Int.cast_neg,
        MulZeroClass.zero_mul]
      rfl
    convert_to 8 + (τ ^ 4 + 1) * (τ ^ 10 - 2 * τ ^ 8 - 2 * τ ^ 6 + 6 * τ ^ 4 + τ ^ 2 - 8) = _
    · ring
    · rw [τ_spec]
      norm_num
  -- this allows us to apply `card_pow_char_pow` to our situation
  have h := Char.card_pow_char_pow hq ψ₈.char (ringChar FF) n hu hFF hg
  rw [card, ← hchar, hχ, one_mul, ← hc, ← Nat.cast_pow (ringChar F), ← hc] at h
  -- finally, we change `2` to `8` on the left hand side
  convert_to(8 : F) ^ (Fintype.card F / 2) = _
  ·
    rw [(by norm_num : (8 : F) = 2 ^ 2 * 2), mul_pow,
      (FiniteField.isSquare_iff hF <| hp2 2).mp ⟨2, pow_two 2⟩, one_mul]
  apply (algebraMap F FF).Injective
  simp only [map_pow, map_bit0, map_one, map_intCast]
  convert h
  norm_num
#align finite_field.two_pow_card FiniteField.two_pow_card

end GaussSumTwo


/-
Copyright (c) 2018 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.linear_ordered_comm_group_with_zero
import Mathlib.algebra.big_operators.ring
import Mathlib.data.real.basic
import Mathlib.data.indicator_function
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
# Nonnegative real numbers

In this file we define `nnreal` (notation: `ℝ≥0`) to be the type of non-negative real numbers,
a.k.a. the interval `[0, ∞)`. We also define the following operations and structures on `ℝ≥0`:

* the order on `ℝ≥0` is the restriction of the order on `ℝ`; these relations define a conditionally
  complete linear order with a bottom element, `conditionally_complete_linear_order_bot`;

* `a + b` and `a * b` are the restrictions of addition and multiplication of real numbers to `ℝ≥0`;
  these operations together with `0 = ⟨0, _⟩` and `1 = ⟨1, _⟩` turn `ℝ≥0` into a linear ordered
  archimedean commutative semifield; we have no typeclass for this in `mathlib` yet, so we define
  the following instances instead:

  - `linear_ordered_semiring ℝ≥0`;
  - `comm_semiring ℝ≥0`;
  - `canonically_ordered_comm_semiring ℝ≥0`;
  - `linear_ordered_comm_group_with_zero ℝ≥0`;
  - `archimedean ℝ≥0`.

* `nnreal.of_real x` is defined as `⟨max x 0, _⟩`, i.e. `↑(nnreal.of_real x) = x` when `0 ≤ x` and
  `↑(nnreal.of_real x) = 0` otherwise.

We also define an instance `can_lift ℝ ℝ≥0`. This instance can be used by the `lift` tactic to
replace `x : ℝ` and `hx : 0 ≤ x` in the proof context with `x : ℝ≥0` while replacing all occurences
of `x` with `↑x`. This tactic also works for a function `f : α → ℝ` with a hypothesis
`hf : ∀ x, 0 ≤ f x`.

## Notations

This file defines `ℝ≥0` as a localized notation for `nnreal`.
-/

/-- Nonnegative real numbers. -/
def nnreal := Subtype fun (r : ℝ) => 0 ≤ r

namespace nnreal

protected instance real.has_coe : has_coe nnreal ℝ := has_coe.mk subtype.val

/- Simp lemma to put back `n.val` into the normal form given by the coercion. -/

@[simp] theorem val_eq_coe (n : nnreal) : subtype.val n = ↑n := rfl

protected instance can_lift : can_lift ℝ nnreal := can_lift.mk coe (fun (r : ℝ) => 0 ≤ r) sorry

protected theorem eq {n : nnreal} {m : nnreal} : ↑n = ↑m → n = m := subtype.eq

protected theorem eq_iff {n : nnreal} {m : nnreal} : ↑n = ↑m ↔ n = m :=
  { mp := nnreal.eq, mpr := congr_arg coe }

theorem ne_iff {x : nnreal} {y : nnreal} : ↑x ≠ ↑y ↔ x ≠ y := not_iff_not_of_iff nnreal.eq_iff

/-- Reinterpret a real number `r` as a non-negative real number. Returns `0` if `r < 0`. -/
protected def of_real (r : ℝ) : nnreal := { val := max r 0, property := sorry }

theorem coe_of_real (r : ℝ) (hr : 0 ≤ r) : ↑(nnreal.of_real r) = r := max_eq_left hr

theorem le_coe_of_real (r : ℝ) : r ≤ ↑(nnreal.of_real r) := le_max_left r 0

theorem coe_nonneg (r : nnreal) : 0 ≤ ↑r := subtype.property r

theorem coe_mk (a : ℝ) (ha : 0 ≤ a) : ↑{ val := a, property := ha } = a := rfl

protected instance has_zero : HasZero nnreal := { zero := { val := 0, property := sorry } }

protected instance has_one : HasOne nnreal := { one := { val := 1, property := zero_le_one } }

protected instance has_add : Add nnreal :=
  { add := fun (a b : nnreal) => { val := ↑a + ↑b, property := sorry } }

protected instance has_sub : Sub nnreal := { sub := fun (a b : nnreal) => nnreal.of_real (↑a - ↑b) }

protected instance has_mul : Mul nnreal :=
  { mul := fun (a b : nnreal) => { val := ↑a * ↑b, property := sorry } }

protected instance has_inv : has_inv nnreal :=
  has_inv.mk fun (a : nnreal) => { val := subtype.val a⁻¹, property := sorry }

protected instance has_le : HasLessEq nnreal := { LessEq := fun (r s : nnreal) => ↑r ≤ ↑s }

protected instance has_bot : has_bot nnreal := has_bot.mk 0

protected instance inhabited : Inhabited nnreal := { default := 0 }

protected theorem injective_coe : function.injective coe := subtype.coe_injective

@[simp] protected theorem coe_eq {r₁ : nnreal} {r₂ : nnreal} : ↑r₁ = ↑r₂ ↔ r₁ = r₂ :=
  function.injective.eq_iff nnreal.injective_coe

@[simp] protected theorem coe_zero : ↑0 = 0 := rfl

@[simp] protected theorem coe_one : ↑1 = 1 := rfl

@[simp] protected theorem coe_add (r₁ : nnreal) (r₂ : nnreal) : ↑(r₁ + r₂) = ↑r₁ + ↑r₂ := rfl

@[simp] protected theorem coe_mul (r₁ : nnreal) (r₂ : nnreal) : ↑(r₁ * r₂) = ↑r₁ * ↑r₂ := rfl

@[simp] protected theorem coe_inv (r : nnreal) : ↑(r⁻¹) = (↑r⁻¹) := rfl

@[simp] protected theorem coe_bit0 (r : nnreal) : ↑(bit0 r) = bit0 ↑r := rfl

@[simp] protected theorem coe_bit1 (r : nnreal) : ↑(bit1 r) = bit1 ↑r := rfl

@[simp] protected theorem coe_sub {r₁ : nnreal} {r₂ : nnreal} (h : r₂ ≤ r₁) :
    ↑(r₁ - r₂) = ↑r₁ - ↑r₂ :=
  sorry

-- TODO: setup semifield!

@[simp] protected theorem coe_eq_zero (r : nnreal) : ↑r = 0 ↔ r = 0 := sorry

theorem coe_ne_zero {r : nnreal} : ↑r ≠ 0 ↔ r ≠ 0 := sorry

protected instance comm_semiring : comm_semiring nnreal :=
  comm_semiring.mk Add.add sorry 0 sorry sorry sorry Mul.mul sorry 1 sorry sorry sorry sorry sorry
    sorry sorry

/-- Coercion `ℝ≥0 → ℝ` as a `ring_hom`. -/
def to_real_hom : nnreal →+* ℝ :=
  ring_hom.mk coe nnreal.coe_one nnreal.coe_mul nnreal.coe_zero nnreal.coe_add

@[simp] theorem coe_to_real_hom : ⇑to_real_hom = coe := rfl

protected instance comm_group_with_zero : comm_group_with_zero nnreal :=
  comm_group_with_zero.mk comm_semiring.mul comm_semiring.mul_assoc comm_semiring.one
    comm_semiring.one_mul comm_semiring.mul_one comm_semiring.mul_comm comm_semiring.zero
    comm_semiring.zero_mul comm_semiring.mul_zero has_inv.inv
    (group_with_zero.div._default comm_semiring.mul comm_semiring.mul_assoc comm_semiring.one
      comm_semiring.one_mul comm_semiring.mul_one has_inv.inv)
    sorry sorry sorry

@[simp] theorem coe_indicator {α : Type u_1} (s : set α) (f : α → nnreal) (a : α) :
    ↑(set.indicator s f a) = set.indicator s (fun (x : α) => ↑(f x)) a :=
  add_monoid_hom.map_indicator (↑to_real_hom) s f a

@[simp] protected theorem coe_div (r₁ : nnreal) (r₂ : nnreal) : ↑(r₁ / r₂) = ↑r₁ / ↑r₂ := rfl

@[simp] theorem coe_pow (r : nnreal) (n : ℕ) : ↑(r ^ n) = ↑r ^ n := ring_hom.map_pow to_real_hom r n

theorem coe_list_sum (l : List nnreal) : ↑(list.sum l) = list.sum (list.map coe l) :=
  ring_hom.map_list_sum to_real_hom l

theorem coe_list_prod (l : List nnreal) : ↑(list.prod l) = list.prod (list.map coe l) :=
  ring_hom.map_list_prod to_real_hom l

theorem coe_multiset_sum (s : multiset nnreal) :
    ↑(multiset.sum s) = multiset.sum (multiset.map coe s) :=
  ring_hom.map_multiset_sum to_real_hom s

theorem coe_multiset_prod (s : multiset nnreal) :
    ↑(multiset.prod s) = multiset.prod (multiset.map coe s) :=
  ring_hom.map_multiset_prod to_real_hom s

theorem coe_sum {α : Type u_1} {s : finset α} {f : α → nnreal} :
    ↑(finset.sum s fun (a : α) => f a) = finset.sum s fun (a : α) => ↑(f a) :=
  ring_hom.map_sum to_real_hom (fun (a : α) => f a) s

theorem of_real_sum_of_nonneg {α : Type u_1} {s : finset α} {f : α → ℝ}
    (hf : ∀ (a : α), a ∈ s → 0 ≤ f a) :
    nnreal.of_real (finset.sum s fun (a : α) => f a) =
        finset.sum s fun (a : α) => nnreal.of_real (f a) :=
  sorry

theorem coe_prod {α : Type u_1} {s : finset α} {f : α → nnreal} :
    ↑(finset.prod s fun (a : α) => f a) = finset.prod s fun (a : α) => ↑(f a) :=
  ring_hom.map_prod to_real_hom (fun (a : α) => f a) s

theorem of_real_prod_of_nonneg {α : Type u_1} {s : finset α} {f : α → ℝ}
    (hf : ∀ (a : α), a ∈ s → 0 ≤ f a) :
    nnreal.of_real (finset.prod s fun (a : α) => f a) =
        finset.prod s fun (a : α) => nnreal.of_real (f a) :=
  sorry

theorem nsmul_coe (r : nnreal) (n : ℕ) : ↑(n •ℕ r) = n •ℕ ↑r :=
  add_monoid_hom.map_nsmul (ring_hom.to_add_monoid_hom to_real_hom) r n

@[simp] protected theorem coe_nat_cast (n : ℕ) : ↑↑n = ↑n := ring_hom.map_nat_cast to_real_hom n

protected instance linear_order : linear_order nnreal := linear_order.lift coe nnreal.injective_coe

@[simp] protected theorem coe_le_coe {r₁ : nnreal} {r₂ : nnreal} : ↑r₁ ≤ ↑r₂ ↔ r₁ ≤ r₂ := iff.rfl

@[simp] protected theorem coe_lt_coe {r₁ : nnreal} {r₂ : nnreal} : ↑r₁ < ↑r₂ ↔ r₁ < r₂ := iff.rfl

@[simp] protected theorem coe_pos {r : nnreal} : 0 < ↑r ↔ 0 < r := iff.rfl

protected theorem coe_mono : monotone coe := fun (_x _x_1 : nnreal) => iff.mpr nnreal.coe_le_coe

protected theorem of_real_mono : monotone nnreal.of_real :=
  fun (x y : ℝ) (h : x ≤ y) => max_le_max h (le_refl 0)

@[simp] theorem of_real_coe {r : nnreal} : nnreal.of_real ↑r = r :=
  nnreal.eq (max_eq_left (subtype.property r))

@[simp] theorem mk_coe_nat (n : ℕ) : { val := ↑n, property := nat.cast_nonneg n } = ↑n :=
  nnreal.eq (Eq.symm (nnreal.coe_nat_cast n))

@[simp] theorem of_real_coe_nat (n : ℕ) : nnreal.of_real ↑n = ↑n := sorry

/-- `nnreal.of_real` and `coe : ℝ≥0 → ℝ` form a Galois insertion. -/
protected def gi : galois_insertion nnreal.of_real coe :=
  galois_insertion.monotone_intro nnreal.coe_mono nnreal.of_real_mono le_coe_of_real sorry

protected instance order_bot : order_bot nnreal :=
  order_bot.mk ⊥ linear_order.le linear_order.lt linear_order.le_refl linear_order.le_trans
    linear_order.le_antisymm sorry

protected instance canonically_ordered_add_monoid : canonically_ordered_add_monoid nnreal :=
  canonically_ordered_add_monoid.mk comm_semiring.add comm_semiring.add_assoc comm_semiring.zero
    comm_semiring.zero_add comm_semiring.add_zero comm_semiring.add_comm order_bot.le order_bot.lt
    order_bot.le_refl order_bot.le_trans order_bot.le_antisymm sorry sorry order_bot.bot
    order_bot.bot_le sorry

protected instance distrib_lattice : distrib_lattice nnreal :=
  Mathlib.distrib_lattice_of_linear_order

protected instance semilattice_inf_bot : semilattice_inf_bot nnreal :=
  semilattice_inf_bot.mk order_bot.bot order_bot.le order_bot.lt order_bot.le_refl
    order_bot.le_trans order_bot.le_antisymm order_bot.bot_le distrib_lattice.inf
    distrib_lattice.inf_le_left distrib_lattice.inf_le_right distrib_lattice.le_inf

protected instance semilattice_sup_bot : semilattice_sup_bot nnreal :=
  semilattice_sup_bot.mk order_bot.bot order_bot.le order_bot.lt order_bot.le_refl
    order_bot.le_trans order_bot.le_antisymm order_bot.bot_le distrib_lattice.sup
    distrib_lattice.le_sup_left distrib_lattice.le_sup_right distrib_lattice.sup_le

protected instance linear_ordered_semiring : linear_ordered_semiring nnreal :=
  linear_ordered_semiring.mk canonically_ordered_add_monoid.add
    canonically_ordered_add_monoid.add_assoc canonically_ordered_add_monoid.zero
    canonically_ordered_add_monoid.zero_add canonically_ordered_add_monoid.add_zero
    canonically_ordered_add_monoid.add_comm comm_semiring.mul comm_semiring.mul_assoc
    comm_semiring.one comm_semiring.one_mul comm_semiring.mul_one comm_semiring.zero_mul
    comm_semiring.mul_zero comm_semiring.left_distrib comm_semiring.right_distrib sorry sorry
    linear_order.le linear_order.lt linear_order.le_refl linear_order.le_trans
    linear_order.le_antisymm canonically_ordered_add_monoid.add_le_add_left sorry zero_le_one sorry
    sorry linear_order.le_total linear_order.decidable_le linear_order.decidable_eq
    linear_order.decidable_lt sorry

protected instance linear_ordered_comm_group_with_zero :
    linear_ordered_comm_group_with_zero nnreal :=
  linear_ordered_comm_group_with_zero.mk linear_ordered_semiring.le linear_ordered_semiring.lt
    linear_ordered_semiring.le_refl linear_ordered_semiring.le_trans
    linear_ordered_semiring.le_antisymm linear_ordered_semiring.le_total
    linear_ordered_semiring.decidable_le linear_ordered_semiring.decidable_eq
    linear_ordered_semiring.decidable_lt linear_ordered_semiring.mul
    linear_ordered_semiring.mul_assoc linear_ordered_semiring.one linear_ordered_semiring.one_mul
    linear_ordered_semiring.mul_one comm_group_with_zero.mul_comm linear_ordered_semiring.zero
    linear_ordered_semiring.zero_mul linear_ordered_semiring.mul_zero sorry sorry sorry
    comm_group_with_zero.inv comm_group_with_zero.div linear_ordered_semiring.exists_pair_ne
    comm_group_with_zero.inv_zero comm_group_with_zero.mul_inv_cancel

protected instance canonically_ordered_comm_semiring : canonically_ordered_comm_semiring nnreal :=
  canonically_ordered_comm_semiring.mk canonically_ordered_add_monoid.add
    canonically_ordered_add_monoid.add_assoc canonically_ordered_add_monoid.zero
    canonically_ordered_add_monoid.zero_add canonically_ordered_add_monoid.add_zero
    canonically_ordered_add_monoid.add_comm canonically_ordered_add_monoid.le
    canonically_ordered_add_monoid.lt canonically_ordered_add_monoid.le_refl
    canonically_ordered_add_monoid.le_trans canonically_ordered_add_monoid.le_antisymm
    canonically_ordered_add_monoid.add_le_add_left
    canonically_ordered_add_monoid.lt_of_add_lt_add_left canonically_ordered_add_monoid.bot
    canonically_ordered_add_monoid.bot_le canonically_ordered_add_monoid.le_iff_exists_add
    comm_semiring.mul comm_semiring.mul_assoc comm_semiring.one comm_semiring.one_mul
    comm_semiring.mul_one comm_semiring.zero_mul comm_semiring.mul_zero comm_semiring.left_distrib
    comm_semiring.right_distrib comm_semiring.mul_comm sorry

protected instance densely_ordered : densely_ordered nnreal :=
  densely_ordered.mk fun (a b : nnreal) (h : ↑a < ↑b) => sorry

protected instance no_top_order : no_top_order nnreal := no_top_order.mk fun (a : nnreal) => sorry

theorem bdd_above_coe {s : set nnreal} : bdd_above (coe '' s) ↔ bdd_above s := sorry

theorem bdd_below_coe (s : set nnreal) : bdd_below (coe '' s) := sorry

protected instance has_Sup : has_Sup nnreal :=
  has_Sup.mk fun (s : set nnreal) => { val := Sup (coe '' s), property := sorry }

protected instance has_Inf : has_Inf nnreal :=
  has_Inf.mk fun (s : set nnreal) => { val := Inf (coe '' s), property := sorry }

theorem coe_Sup (s : set nnreal) : ↑(Sup s) = Sup (coe '' s) := rfl

theorem coe_Inf (s : set nnreal) : ↑(Inf s) = Inf (coe '' s) := rfl

protected instance conditionally_complete_linear_order_bot :
    conditionally_complete_linear_order_bot nnreal :=
  conditionally_complete_linear_order_bot.mk lattice.sup linear_ordered_semiring.le
    linear_ordered_semiring.lt linear_ordered_semiring.le_refl linear_ordered_semiring.le_trans
    linear_ordered_semiring.le_antisymm sorry sorry sorry lattice.inf sorry sorry sorry Sup Inf
    sorry sorry sorry sorry linear_ordered_semiring.le_total
    (id fun (x y : nnreal) => classical.dec (x ≤ y)) linear_ordered_semiring.decidable_eq
    linear_ordered_semiring.decidable_lt order_bot.bot order_bot.bot_le sorry

protected instance archimedean : archimedean nnreal :=
  archimedean.mk fun (x y : nnreal) (pos_y : 0 < y) => sorry

theorem le_of_forall_pos_le_add {a : nnreal} {b : nnreal} (h : ∀ (ε : nnreal), 0 < ε → a ≤ b + ε) :
    a ≤ b :=
  sorry

theorem lt_iff_exists_rat_btwn (a : nnreal) (b : nnreal) :
    a < b ↔ ∃ (q : ℚ), 0 ≤ q ∧ a < nnreal.of_real ↑q ∧ nnreal.of_real ↑q < b :=
  sorry

theorem bot_eq_zero : ⊥ = 0 := rfl

theorem mul_sup (a : nnreal) (b : nnreal) (c : nnreal) : a * (b ⊔ c) = a * b ⊔ a * c := sorry

theorem mul_finset_sup {α : Type u_1} {f : α → nnreal} {s : finset α} (r : nnreal) :
    r * finset.sup s f = finset.sup s fun (a : α) => r * f a :=
  sorry

@[simp] theorem coe_max (x : nnreal) (y : nnreal) : ↑(max x y) = max ↑x ↑y := sorry

@[simp] theorem coe_min (x : nnreal) (y : nnreal) : ↑(min x y) = min ↑x ↑y := sorry

@[simp] theorem zero_le_coe {q : nnreal} : 0 ≤ ↑q := subtype.property q

@[simp] theorem of_real_zero : nnreal.of_real 0 = 0 := sorry

@[simp] theorem of_real_one : nnreal.of_real 1 = 1 := sorry

@[simp] theorem of_real_pos {r : ℝ} : 0 < nnreal.of_real r ↔ 0 < r := sorry

@[simp] theorem of_real_eq_zero {r : ℝ} : nnreal.of_real r = 0 ↔ r ≤ 0 := sorry

theorem of_real_of_nonpos {r : ℝ} : r ≤ 0 → nnreal.of_real r = 0 := iff.mpr of_real_eq_zero

@[simp] theorem of_real_le_of_real_iff {r : ℝ} {p : ℝ} (hp : 0 ≤ p) :
    nnreal.of_real r ≤ nnreal.of_real p ↔ r ≤ p :=
  sorry

@[simp] theorem of_real_lt_of_real_iff' {r : ℝ} {p : ℝ} :
    nnreal.of_real r < nnreal.of_real p ↔ r < p ∧ 0 < p :=
  sorry

theorem of_real_lt_of_real_iff {r : ℝ} {p : ℝ} (h : 0 < p) :
    nnreal.of_real r < nnreal.of_real p ↔ r < p :=
  iff.trans of_real_lt_of_real_iff' (and_iff_left h)

theorem of_real_lt_of_real_iff_of_nonneg {r : ℝ} {p : ℝ} (hr : 0 ≤ r) :
    nnreal.of_real r < nnreal.of_real p ↔ r < p :=
  iff.trans of_real_lt_of_real_iff'
    { mp := and.left, mpr := fun (h : r < p) => { left := h, right := lt_of_le_of_lt hr h } }

@[simp] theorem of_real_add {r : ℝ} {p : ℝ} (hr : 0 ≤ r) (hp : 0 ≤ p) :
    nnreal.of_real (r + p) = nnreal.of_real r + nnreal.of_real p :=
  sorry

theorem of_real_add_of_real {r : ℝ} {p : ℝ} (hr : 0 ≤ r) (hp : 0 ≤ p) :
    nnreal.of_real r + nnreal.of_real p = nnreal.of_real (r + p) :=
  Eq.symm (of_real_add hr hp)

theorem of_real_le_of_real {r : ℝ} {p : ℝ} (h : r ≤ p) : nnreal.of_real r ≤ nnreal.of_real p :=
  nnreal.of_real_mono h

theorem of_real_add_le {r : ℝ} {p : ℝ} :
    nnreal.of_real (r + p) ≤ nnreal.of_real r + nnreal.of_real p :=
  iff.mp nnreal.coe_le_coe (max_le (add_le_add (le_max_left r 0) (le_max_left p 0)) zero_le_coe)

theorem of_real_le_iff_le_coe {r : ℝ} {p : nnreal} : nnreal.of_real r ≤ p ↔ r ≤ ↑p :=
  galois_insertion.gc nnreal.gi r p

theorem le_of_real_iff_coe_le {r : nnreal} {p : ℝ} (hp : 0 ≤ p) : r ≤ nnreal.of_real p ↔ ↑r ≤ p :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (r ≤ nnreal.of_real p ↔ ↑r ≤ p)) (Eq.symm (propext nnreal.coe_le_coe))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑r ≤ ↑(nnreal.of_real p) ↔ ↑r ≤ p)) (coe_of_real p hp)))
      (iff.refl (↑r ≤ p)))

theorem le_of_real_iff_coe_le' {r : nnreal} {p : ℝ} (hr : 0 < r) : r ≤ nnreal.of_real p ↔ ↑r ≤ p :=
  sorry

theorem of_real_lt_iff_lt_coe {r : ℝ} {p : nnreal} (ha : 0 ≤ r) : nnreal.of_real r < p ↔ r < ↑p :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (nnreal.of_real r < p ↔ r < ↑p)) (Eq.symm (propext nnreal.coe_lt_coe))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑(nnreal.of_real r) < ↑p ↔ r < ↑p)) (coe_of_real r ha)))
      (iff.refl (r < ↑p)))

theorem lt_of_real_iff_coe_lt {r : nnreal} {p : ℝ} : r < nnreal.of_real p ↔ ↑r < p := sorry

theorem mul_eq_mul_left {a : nnreal} {b : nnreal} {c : nnreal} (h : a ≠ 0) :
    a * b = a * c ↔ b = c :=
  sorry

theorem of_real_mul {p : ℝ} {q : ℝ} (hp : 0 ≤ p) :
    nnreal.of_real (p * q) = nnreal.of_real p * nnreal.of_real q :=
  sorry

theorem sub_def {r : nnreal} {p : nnreal} : r - p = nnreal.of_real (↑r - ↑p) := rfl

theorem sub_eq_zero {r : nnreal} {p : nnreal} (h : r ≤ p) : r - p = 0 := sorry

@[simp] theorem sub_self {r : nnreal} : r - r = 0 := sub_eq_zero (le_refl r)

@[simp] theorem sub_zero {r : nnreal} : r - 0 = r := sorry

theorem sub_pos {r : nnreal} {p : nnreal} : 0 < r - p ↔ p < r :=
  iff.trans of_real_pos (iff.trans sub_pos nnreal.coe_lt_coe)

protected theorem sub_lt_self {r : nnreal} {p : nnreal} : 0 < r → 0 < p → r - p < r := sorry

@[simp] theorem sub_le_iff_le_add {r : nnreal} {p : nnreal} {q : nnreal} : r - p ≤ q ↔ r ≤ q + p :=
  sorry

@[simp] theorem sub_le_self {r : nnreal} {p : nnreal} : r - p ≤ r :=
  iff.mpr sub_le_iff_le_add (le_add_right (le_refl r))

theorem add_sub_cancel {r : nnreal} {p : nnreal} : p + r - r = p := sorry

theorem add_sub_cancel' {r : nnreal} {p : nnreal} : r + p - r = p :=
  eq.mpr (id (Eq._oldrec (Eq.refl (r + p - r = p)) (add_comm r p)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (p + r - r = p)) add_sub_cancel)) (Eq.refl p))

theorem sub_add_eq_max {r : nnreal} {p : nnreal} : r - p + p = max r p := sorry

theorem add_sub_eq_max {r : nnreal} {p : nnreal} : p + (r - p) = max p r :=
  eq.mpr (id (Eq._oldrec (Eq.refl (p + (r - p) = max p r)) (add_comm p (r - p))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (r - p + p = max p r)) sub_add_eq_max))
      (eq.mpr (id (Eq._oldrec (Eq.refl (max r p = max p r)) (max_comm r p))) (Eq.refl (max p r))))

@[simp] theorem sub_add_cancel_of_le {a : nnreal} {b : nnreal} (h : b ≤ a) : a - b + b = a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a - b + b = a)) sub_add_eq_max))
    (eq.mpr (id (Eq._oldrec (Eq.refl (max a b = a)) (max_eq_left h))) (Eq.refl a))

theorem sub_sub_cancel_of_le {r : nnreal} {p : nnreal} (h : r ≤ p) : p - (p - r) = r := sorry

theorem lt_sub_iff_add_lt {p : nnreal} {q : nnreal} {r : nnreal} : p < q - r ↔ p + r < q := sorry

theorem sub_lt_iff_lt_add {a : nnreal} {b : nnreal} {c : nnreal} (h : b ≤ a) :
    a - b < c ↔ a < b + c :=
  sorry

theorem sub_eq_iff_eq_add {a : nnreal} {b : nnreal} {c : nnreal} (h : b ≤ a) :
    a - b = c ↔ a = c + b :=
  sorry

theorem sum_div {ι : Type u_1} (s : finset ι) (f : ι → nnreal) (b : nnreal) :
    (finset.sum s fun (i : ι) => f i) / b = finset.sum s fun (i : ι) => f i / b :=
  sorry

@[simp] theorem inv_pos {r : nnreal} : 0 < (r⁻¹) ↔ 0 < r := sorry

theorem div_pos {r : nnreal} {p : nnreal} (hr : 0 < r) (hp : 0 < p) : 0 < r / p := sorry

protected theorem mul_inv {r : nnreal} {p : nnreal} : r * p⁻¹ = p⁻¹ * (r⁻¹) :=
  nnreal.eq (mul_inv_rev' ↑r ↑p)

theorem div_self_le (r : nnreal) : r / r ≤ 1 := sorry

@[simp] theorem inv_le {r : nnreal} {p : nnreal} (h : r ≠ 0) : r⁻¹ ≤ p ↔ 1 ≤ r * p :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (r⁻¹ ≤ p ↔ 1 ≤ r * p))
        (Eq.symm (propext (mul_le_mul_left (iff.mpr pos_iff_ne_zero h))))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (r * (r⁻¹) ≤ r * p ↔ 1 ≤ r * p)) (mul_inv_cancel h)))
      (iff.refl (1 ≤ r * p)))

theorem inv_le_of_le_mul {r : nnreal} {p : nnreal} (h : 1 ≤ r * p) : r⁻¹ ≤ p := sorry

@[simp] theorem le_inv_iff_mul_le {r : nnreal} {p : nnreal} (h : p ≠ 0) : r ≤ (p⁻¹) ↔ r * p ≤ 1 :=
  sorry

@[simp] theorem lt_inv_iff_mul_lt {r : nnreal} {p : nnreal} (h : p ≠ 0) : r < (p⁻¹) ↔ r * p < 1 :=
  sorry

theorem mul_le_iff_le_inv {a : nnreal} {b : nnreal} {r : nnreal} (hr : r ≠ 0) :
    r * a ≤ b ↔ a ≤ r⁻¹ * b :=
  sorry

theorem le_div_iff_mul_le {a : nnreal} {b : nnreal} {r : nnreal} (hr : r ≠ 0) :
    a ≤ b / r ↔ a * r ≤ b :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a ≤ b / r ↔ a * r ≤ b)) div_eq_inv_mul))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (a ≤ r⁻¹ * b ↔ a * r ≤ b)) (Eq.symm (propext (mul_le_iff_le_inv hr)))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (r * a ≤ b ↔ a * r ≤ b)) (mul_comm r a)))
        (iff.refl (a * r ≤ b))))

theorem div_le_iff {a : nnreal} {b : nnreal} {r : nnreal} (hr : r ≠ 0) : a / r ≤ b ↔ a ≤ b * r :=
  div_le_iff (iff.mpr pos_iff_ne_zero hr)

theorem lt_div_iff {a : nnreal} {b : nnreal} {r : nnreal} (hr : r ≠ 0) : a < b / r ↔ a * r < b :=
  lt_iff_lt_of_le_iff_le (div_le_iff hr)

theorem mul_lt_of_lt_div {a : nnreal} {b : nnreal} {r : nnreal} (h : a < b / r) : a * r < b := sorry

theorem le_of_forall_lt_one_mul_le {x : nnreal} {y : nnreal}
    (h : ∀ (a : nnreal), a < 1 → a * x ≤ y) : x ≤ y :=
  sorry

theorem div_add_div_same (a : nnreal) (b : nnreal) (c : nnreal) : a / c + b / c = (a + b) / c :=
  Eq.symm (right_distrib a b (c⁻¹))

theorem half_pos {a : nnreal} (h : 0 < a) : 0 < a / bit0 1 := div_pos h zero_lt_two

theorem add_halves (a : nnreal) : a / bit0 1 + a / bit0 1 = a := nnreal.eq (add_halves ↑a)

theorem half_lt_self {a : nnreal} (h : a ≠ 0) : a / bit0 1 < a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (a / bit0 1 < a)) (Eq.symm (propext nnreal.coe_lt_coe))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (↑(a / bit0 1) < ↑a)) (nnreal.coe_div a (bit0 1))))
      (half_lt_self (iff.mpr bot_lt_iff_ne_bot h)))

theorem two_inv_lt_one : bit0 1⁻¹ < 1 := sorry

theorem div_lt_iff {a : nnreal} {b : nnreal} {c : nnreal} (hc : c ≠ 0) : b / c < a ↔ b < a * c :=
  lt_iff_lt_of_le_iff_le (le_div_iff_mul_le hc)

theorem div_lt_one_of_lt {a : nnreal} {b : nnreal} (h : a < b) : a / b < 1 :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (a / b < 1))
        (propext (div_lt_iff (ne_of_gt (lt_of_le_of_lt (zero_le a) h))))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (a < 1 * b)) (one_mul b))) h)

theorem div_add_div (a : nnreal) {b : nnreal} (c : nnreal) {d : nnreal} (hb : b ≠ 0) (hd : d ≠ 0) :
    a / b + c / d = (a * d + b * c) / (b * d) :=
  sorry

theorem add_div' (a : nnreal) (b : nnreal) (c : nnreal) (hc : c ≠ 0) :
    b + a / c = (b * c + a) / c :=
  sorry

theorem div_add' (a : nnreal) (b : nnreal) (c : nnreal) (hc : c ≠ 0) :
    a / c + b = (a + b * c) / c :=
  sorry

theorem of_real_inv {x : ℝ} : nnreal.of_real (x⁻¹) = (nnreal.of_real x⁻¹) := sorry

theorem of_real_div {x : ℝ} {y : ℝ} (hx : 0 ≤ x) :
    nnreal.of_real (x / y) = nnreal.of_real x / nnreal.of_real y :=
  sorry

theorem of_real_div' {x : ℝ} {y : ℝ} (hy : 0 ≤ y) :
    nnreal.of_real (x / y) = nnreal.of_real x / nnreal.of_real y :=
  sorry

@[simp] theorem abs_eq (x : nnreal) : abs ↑x = ↑x := abs_of_nonneg (subtype.property x)

end nnreal

/-- The absolute value on `ℝ` as a map to `ℝ≥0`. -/
def real.nnabs (x : ℝ) : nnreal := { val := abs x, property := abs_nonneg x }

@[simp] theorem nnreal.coe_nnabs (x : ℝ) : ↑(real.nnabs x) = abs x := sorry

end Mathlib


import data.real.basic

def fn_ub (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, f x ≤ a
def fn_lb (f : ℝ → ℝ) (a : ℝ) : Prop := ∀ x, a ≤ f x

variables (f g : ℝ → ℝ) (a b : ℝ)

#check add_le_add

-- BEGIN
example (hfa : fn_ub f a) (hgb : fn_ub g b) :
  fn_ub (λ x, f x + g x) (a + b) :=
begin
  intro x,
  dsimp,
  apply add_le_add,
  sorry,
end
-- END


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

! This file was ported from Lean 3 source module analysis.normed_space.banach_steinhaus
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.OperatorNorm
import Mathbin.Topology.MetricSpace.Baire
import Mathbin.Topology.Algebra.Module.Basic

/-!
# The Banach-Steinhaus theorem: Uniform Boundedness Principle

Herein we prove the Banach-Steinhaus theorem: any collection of bounded linear maps
from a Banach space into a normed space which is pointwise bounded is uniformly bounded.

## TODO

For now, we only prove the standard version by appeal to the Baire category theorem.
Much more general versions exist (in particular, for maps from barrelled spaces to locally
convex spaces), but these are not yet in `mathlib`.
-/

open Set

variable {E F 𝕜 𝕜₂ : Type _} [SeminormedAddCommGroup E] [SeminormedAddCommGroup F]
  [NontriviallyNormedField 𝕜] [NontriviallyNormedField 𝕜₂] [NormedSpace 𝕜 E] [NormedSpace 𝕜₂ F]
  {σ₁₂ : 𝕜 →+* 𝕜₂} [RingHomIsometric σ₁₂]

/-- This is the standard Banach-Steinhaus theorem, or Uniform Boundedness Principle.
If a family of continuous linear maps from a Banach space into a normed space is pointwise
bounded, then the norms of these linear maps are uniformly bounded. -/
theorem banach_steinhaus {ι : Type _} [CompleteSpace E] {g : ι → E →SL[σ₁₂] F}
    (h : ∀ x, ∃ C, ∀ i, ‖g i x‖ ≤ C) : ∃ C', ∀ i, ‖g i‖ ≤ C' :=
  by
  -- sequence of subsets consisting of those `x : E` with norms `‖g i x‖` bounded by `n`
  let e : ℕ → Set E := fun n => ⋂ i : ι, { x : E | ‖g i x‖ ≤ n }
  -- each of these sets is closed
  have hc : ∀ n : ℕ, IsClosed (e n) := fun i =>
    isClosed_interᵢ fun i => isClosed_le (Continuous.norm (g i).cont) continuous_const
  -- the union is the entire space; this is where we use `h`
  have hU : (⋃ n : ℕ, e n) = univ :=
    by
    refine' eq_univ_of_forall fun x => _
    cases' h x with C hC
    obtain ⟨m, hm⟩ := exists_nat_ge C
    exact ⟨e m, mem_range_self m, mem_Inter.mpr fun i => le_trans (hC i) hm⟩
  -- apply the Baire category theorem to conclude that for some `m : ℕ`, `e m` contains some `x`
  rcases nonempty_interior_of_unionᵢ_of_closed hc hU with ⟨m, x, hx⟩
  rcases metric.is_open_iff.mp isOpen_interior x hx with ⟨ε, ε_pos, hε⟩
  obtain ⟨k, hk⟩ := NormedField.exists_one_lt_norm 𝕜
  -- show all elements in the ball have norm bounded by `m` after applying any `g i`
  have real_norm_le : ∀ z : E, z ∈ Metric.ball x ε → ∀ i : ι, ‖g i z‖ ≤ m :=
    by
    intro z hz i
    replace hz := mem_Inter.mp (interior_interᵢ_subset _ (hε hz)) i
    apply interior_subset hz
  have εk_pos : 0 < ε / ‖k‖ := div_pos ε_pos (zero_lt_one.trans hk)
  refine' ⟨(m + m : ℕ) / (ε / ‖k‖), fun i => ContinuousLinearMap.op_norm_le_of_shell ε_pos _ hk _⟩
  · exact div_nonneg (Nat.cast_nonneg _) εk_pos.le
  intro y le_y y_lt
  calc
    ‖g i y‖ = ‖g i (y + x) - g i x‖ := by rw [ContinuousLinearMap.map_add, add_sub_cancel]
    _ ≤ ‖g i (y + x)‖ + ‖g i x‖ := (norm_sub_le _ _)
    _ ≤ m + m :=
      (add_le_add (real_norm_le (y + x) (by rwa [add_comm, add_mem_ball_iff_norm]) i)
        (real_norm_le x (Metric.mem_ball_self ε_pos) i))
    _ = (m + m : ℕ) := (m.cast_add m).symm
    _ ≤ (m + m : ℕ) * (‖y‖ / (ε / ‖k‖)) :=
      (le_mul_of_one_le_right (Nat.cast_nonneg _)
        ((one_le_div <| div_pos ε_pos (zero_lt_one.trans hk)).2 le_y))
    _ = (m + m : ℕ) / (ε / ‖k‖) * ‖y‖ := (mul_comm_div _ _ _).symm

#align banach_steinhaus banach_steinhaus

open ENNReal

open ENNReal

/-- This version of Banach-Steinhaus is stated in terms of suprema of `↑‖⬝‖₊ : ℝ≥0∞`
for convenience. -/
theorem banach_steinhaus_supᵢ_nnnorm {ι : Type _} [CompleteSpace E] {g : ι → E →SL[σ₁₂] F}
    (h : ∀ x, (⨆ i, ↑‖g i x‖₊) < ∞) : (⨆ i, ↑‖g i‖₊) < ∞ :=
  by
  have h' : ∀ x : E, ∃ C : ℝ, ∀ i : ι, ‖g i x‖ ≤ C :=
    by
    intro x
    rcases lt_iff_exists_coe.mp (h x) with ⟨p, hp₁, _⟩
    refine' ⟨p, fun i => _⟩
    exact_mod_cast
      calc
        (‖g i x‖₊ : ℝ≥0∞) ≤ ⨆ j, ‖g j x‖₊ := le_supᵢ _ i
        _ = p := hp₁

  cases' banach_steinhaus h' with C' hC'
  refine' (supᵢ_le fun i => _).trans_lt (@coe_lt_top C'.to_nnreal)
  rw [← norm_toNNReal]
  exact coe_mono (Real.toNNReal_le_toNNReal <| hC' i)
#align banach_steinhaus_supr_nnnorm banach_steinhaus_supᵢ_nnnorm

open Topology

open Filter

/-- Given a *sequence* of continuous linear maps which converges pointwise and for which the
domain is complete, the Banach-Steinhaus theorem is used to guarantee that the limit map
is a *continuous* linear map as well. -/
def continuousLinearMapOfTendsto [CompleteSpace E] [T2Space F] (g : ℕ → E →SL[σ₁₂] F) {f : E → F}
    (h : Tendsto (fun n x => g n x) atTop (𝓝 f)) : E →SL[σ₁₂] F
    where
  toFun := f
  map_add' := (linearMapOfTendsto _ _ h).map_add'
  map_smul' := (linearMapOfTendsto _ _ h).map_smul'
  cont :=
    by
    -- show that the maps are pointwise bounded and apply `banach_steinhaus`
    have h_point_bdd : ∀ x : E, ∃ C : ℝ, ∀ n : ℕ, ‖g n x‖ ≤ C :=
      by
      intro x
      rcases cauchySeq_bdd (tendsto_pi_nhds.mp h x).CauchySeq with ⟨C, C_pos, hC⟩
      refine' ⟨C + ‖g 0 x‖, fun n => _⟩
      simp_rw [dist_eq_norm] at hC
      calc
        ‖g n x‖ ≤ ‖g 0 x‖ + ‖g n x - g 0 x‖ := norm_le_insert' _ _
        _ ≤ C + ‖g 0 x‖ := by linarith [hC n 0]

    cases' banach_steinhaus h_point_bdd with C' hC'
    /- show the uniform bound from `banach_steinhaus` is a norm bound of the limit map
             by allowing "an `ε` of room." -/
    refine'
      AddMonoidHomClass.continuous_of_bound (linearMapOfTendsto _ _ h) C' fun x =>
        le_of_forall_pos_lt_add fun ε ε_pos => _
    cases' metric.tendsto_at_top.mp (tendsto_pi_nhds.mp h x) ε ε_pos with n hn
    have lt_ε : ‖g n x - f x‖ < ε := by
      rw [← dist_eq_norm]
      exact hn n (le_refl n)
    calc
      ‖f x‖ ≤ ‖g n x‖ + ‖g n x - f x‖ := norm_le_insert _ _
      _ < ‖g n‖ * ‖x‖ + ε := by linarith [lt_ε, (g n).le_op_norm x]
      _ ≤ C' * ‖x‖ + ε := by nlinarith [hC' n, norm_nonneg x]

#align continuous_linear_map_of_tendsto continuousLinearMapOfTendsto


import .Preloaded

/-
universes u

inductive mytree (A : Type u) : Type u
| leaf : A → mytree
| branch : A → mytree → mytree → mytree

def flip_mytree {A : Type u} : mytree A → mytree A
| t@(mytree.leaf _)     := t
| (mytree.branch a l r) := mytree.branch a (flip_mytree r) (flip_mytree l)
-/

universe u

theorem flip_flip {A : Type u} {t : mytree A} : flip_mytree (flip_mytree t) = t :=
begin
  induction t with _ a t1 t2 ih1 ih2; unfold flip_mytree,
  rw [ih1, ih2],
end