/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies
-/
import algebra.big_operators.multiset.basic
import data.fun_like.basic

/-!
# Freiman homomorphisms

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

In this file, we define Freiman homomorphisms. A `n`-Freiman homomorphism on `A` is a function
`f : α → β` such that `f (x₁) * ... * f (xₙ) = f (y₁) * ... * f (yₙ)` for all
`x₁, ..., xₙ, y₁, ..., yₙ ∈ A` such that `x₁ * ... * xₙ = y₁ * ... * yₙ`. In particular, any
`mul_hom` is a Freiman homomorphism.

They are of interest in additive combinatorics.

## Main declaration

* `freiman_hom`: Freiman homomorphism.
* `add_freiman_hom`: Additive Freiman homomorphism.

## Notation

* `A →*[n] β`: Multiplicative `n`-Freiman homomorphism on `A`
* `A →+[n] β`: Additive `n`-Freiman homomorphism on `A`

## Implementation notes

In the context of combinatorics, we are interested in Freiman homomorphisms over sets which are not
necessarily closed under addition/multiplication. This means we must parametrize them with a set in
an `add_monoid`/`monoid` instead of the `add_monoid`/`monoid` itself.

## References

[Yufei Zhao, *18.225: Graph Theory and Additive Combinatorics*](https://yufeizhao.com/gtac/)

## TODO

`monoid_hom.to_freiman_hom` could be relaxed to `mul_hom.to_freiman_hom` by proving
`(s.map f).prod = (t.map f).prod` directly by induction instead of going through `f s.prod`.

Define `n`-Freiman isomorphisms.

Affine maps induce Freiman homs. Concretely, provide the `add_freiman_hom_class (α →ₐ[𝕜] β) A β n`
instance.
-/

open multiset

variables {F α β γ δ G : Type*}

/-- An additive `n`-Freiman homomorphism is a map which preserves sums of `n` elements. -/
structure add_freiman_hom (A : set α) (β : Type*) [add_comm_monoid α] [add_comm_monoid β] (n : ℕ) :=
(to_fun : α → β)
(map_sum_eq_map_sum' {s t : multiset α} (hsA : ∀ ⦃x⦄, x ∈ s → x ∈ A) (htA : ∀ ⦃x⦄, x ∈ t → x ∈ A)
  (hs : s.card = n) (ht : t.card = n) (h : s.sum = t.sum) :
  (s.map to_fun).sum = (t.map to_fun).sum)

/-- A `n`-Freiman homomorphism on a set `A` is a map which preserves products of `n` elements. -/
@[to_additive add_freiman_hom]
structure freiman_hom (A : set α) (β : Type*) [comm_monoid α] [comm_monoid β] (n : ℕ) :=
(to_fun : α → β)
(map_prod_eq_map_prod' {s t : multiset α} (hsA : ∀ ⦃x⦄, x ∈ s → x ∈ A) (htA : ∀ ⦃x⦄, x ∈ t → x ∈ A)
  (hs : s.card = n) (ht : t.card = n) (h : s.prod = t.prod) :
  (s.map to_fun).prod = (t.map to_fun).prod)

notation (name := add_freiman_hom) A ` →+[`:25 n:25 `] `:0 β:0 := add_freiman_hom A β n
notation (name := freiman_hom) A ` →*[`:25 n:25 `] `:0 β:0 := freiman_hom A β n

/-- `add_freiman_hom_class F s β n` states that `F` is a type of `n`-ary sums-preserving morphisms.
You should extend this class when you extend `add_freiman_hom`. -/
class add_freiman_hom_class (F : Type*) (A : out_param $ set α) (β : out_param $ Type*)
  [add_comm_monoid α] [add_comm_monoid β] (n : ℕ) [fun_like F α (λ _, β)] : Prop :=
(map_sum_eq_map_sum' (f : F) {s t : multiset α} (hsA : ∀ ⦃x⦄, x ∈ s → x ∈ A)
  (htA : ∀ ⦃x⦄, x ∈ t → x ∈ A) (hs : s.card = n) (ht : t.card = n) (h : s.sum = t.sum) :
  (s.map f).sum = (t.map f).sum)

/-- `freiman_hom_class F A β n` states that `F` is a type of `n`-ary products-preserving morphisms.
You should extend this class when you extend `freiman_hom`. -/
@[to_additive add_freiman_hom_class
"`add_freiman_hom_class F A β n` states that `F` is a type of `n`-ary sums-preserving morphisms.
You should extend this class when you extend `add_freiman_hom`."]
class freiman_hom_class (F : Type*) (A : out_param $ set α) (β : out_param $ Type*) [comm_monoid α]
  [comm_monoid β] (n : ℕ) [fun_like F α (λ _, β)] : Prop :=
(map_prod_eq_map_prod' (f : F) {s t : multiset α} (hsA : ∀ ⦃x⦄, x ∈ s → x ∈ A)
  (htA : ∀ ⦃x⦄, x ∈ t → x ∈ A) (hs : s.card = n) (ht : t.card = n) (h : s.prod = t.prod) :
  (s.map f).prod = (t.map f).prod)

variables [fun_like F α (λ _, β)]

section comm_monoid
variables [comm_monoid α] [comm_monoid β] [comm_monoid γ] [comm_monoid δ] [comm_group G] {A : set α}
  {B : set β} {C : set γ} {n : ℕ} {a b c d : α}

@[to_additive]
lemma map_prod_eq_map_prod [freiman_hom_class F A β n] (f : F) {s t : multiset α}
  (hsA : ∀ ⦃x⦄, x ∈ s → x ∈ A) (htA : ∀ ⦃x⦄, x ∈ t → x ∈ A) (hs : s.card = n) (ht : t.card = n)
  (h : s.prod = t.prod) :
  (s.map f).prod = (t.map f).prod :=
freiman_hom_class.map_prod_eq_map_prod' f hsA htA hs ht h

@[to_additive]
lemma map_mul_map_eq_map_mul_map [freiman_hom_class F A β 2] (f : F) (ha : a ∈ A) (hb : b ∈ A)
  (hc : c ∈ A) (hd : d ∈ A) (h : a * b = c * d) :
  f a * f b = f c * f d :=
begin
  simp_rw ←prod_pair at ⊢ h,
  refine map_prod_eq_map_prod f _ _ (card_pair _ _) (card_pair _ _) h; simp [ha, hb, hc, hd],
end

namespace freiman_hom

@[to_additive]
instance fun_like : fun_like (A →*[n] β) α (λ _, β) :=
{ coe := to_fun,
  coe_injective' := λ f g h, by cases f; cases g; congr' }

@[to_additive]
instance freiman_hom_class : freiman_hom_class (A →*[n] β) A β n :=
{ map_prod_eq_map_prod' := map_prod_eq_map_prod' }

/-- Helper instance for when there's too many metavariables to apply `fun_like.has_coe_to_fun`
directly. -/
@[to_additive "Helper instance for when there's too many metavariables to apply
`fun_like.has_coe_to_fun` directly."]
instance : has_coe_to_fun (A →*[n] β) (λ _, α → β) := ⟨to_fun⟩

initialize_simps_projections freiman_hom (to_fun → apply)

@[simp, to_additive]
lemma to_fun_eq_coe (f : A →*[n] β) : f.to_fun = f := rfl

@[ext, to_additive]
lemma ext ⦃f g : A →*[n] β⦄ (h : ∀ x, f x = g x) : f = g := fun_like.ext f g h

@[simp, to_additive]
lemma coe_mk (f : α → β) (h : ∀ s t : multiset α, (∀ ⦃x⦄, x ∈ s → x ∈ A) → (∀ ⦃x⦄, x ∈ t → x ∈ A) →
  s.card = n → t.card = n → s.prod = t.prod → (s.map f).prod = (t.map f).prod) :
  ⇑(mk f h) = f := rfl

@[simp, to_additive] lemma mk_coe (f : A →*[n] β) (h) : mk f h = f := ext $ λ _, rfl

/-- The identity map from a commutative monoid to itself. -/
@[to_additive "The identity map from an additive commutative monoid to itself.", simps]
protected def id (A : set α) (n : ℕ) : A →*[n] α  :=
{ to_fun := λ x, x, map_prod_eq_map_prod' := λ s t _ _ _ _ h, by rw [map_id', map_id', h] }

/-- Composition of Freiman homomorphisms as a Freiman homomorphism. -/
@[to_additive "Composition of additive Freiman homomorphisms as an additive Freiman homomorphism."]
protected def comp (f : B →*[n] γ) (g : A →*[n] β) (hAB : A.maps_to g B) : A →*[n] γ :=
{ to_fun := f ∘ g,
  map_prod_eq_map_prod' := λ s t hsA htA hs ht h, begin
    rw [←map_map,
    map_prod_eq_map_prod f _ _ ((s.card_map _).trans hs) ((t.card_map _).trans ht)
      (map_prod_eq_map_prod g hsA htA hs ht h), map_map],
    { simpa using (λ a h, hAB (hsA h)) },
    { simpa using (λ a h, hAB (htA h)) }
  end }

@[simp, to_additive]
lemma coe_comp (f : B →*[n] γ) (g : A →*[n] β) {hfg} : ⇑(f.comp g hfg) = f ∘ g := rfl

@[to_additive]
lemma comp_apply (f : B →*[n] γ) (g : A →*[n] β) {hfg} (x : α) : f.comp g hfg x = f (g x) := rfl

@[to_additive]
lemma comp_assoc (f : A →*[n] β) (g : B →*[n] γ) (h : C →*[n] δ) {hf hhg hgf}
  {hh : A.maps_to (g.comp f hgf) C} :
  (h.comp g hhg).comp f hf = h.comp (g.comp f hgf) hh := rfl

@[to_additive]
lemma cancel_right {g₁ g₂ : B →*[n] γ} {f : A →*[n] β} (hf : function.surjective f) {hg₁ hg₂} :
  g₁.comp f hg₁ = g₂.comp f hg₂ ↔ g₁ = g₂ :=
⟨λ h, ext $ hf.forall.2 $ fun_like.ext_iff.1 h, λ h, h ▸ rfl⟩

@[to_additive]
lemma cancel_right_on {g₁ g₂ : B →*[n] γ} {f : A →*[n] β} (hf : A.surj_on f B) {hf'} :
  A.eq_on (g₁.comp f hf') (g₂.comp f hf') ↔ B.eq_on g₁ g₂ :=
hf.cancel_right hf'

@[to_additive]
lemma cancel_left_on {g : B →*[n] γ} {f₁ f₂ : A →*[n] β} (hg : B.inj_on g) {hf₁ hf₂} :
  A.eq_on (g.comp f₁ hf₁) (g.comp f₂ hf₂) ↔ A.eq_on f₁ f₂ :=
hg.cancel_left hf₁ hf₂

@[simp, to_additive] lemma comp_id (f : A →*[n] β) {hf} : f.comp (freiman_hom.id A n) hf = f :=
ext $ λ x, rfl

@[simp, to_additive] lemma id_comp (f : A →*[n] β) {hf} : (freiman_hom.id B n).comp f hf = f :=
ext $ λ x, rfl

/-- `freiman_hom.const A n b` is the Freiman homomorphism sending everything to `b`. -/
@[to_additive "`add_freiman_hom.const n b` is the Freiman homomorphism sending everything to `b`."]
def const (A : set α) (n : ℕ) (b : β) : A →*[n] β :=
{ to_fun := λ _, b,
  map_prod_eq_map_prod' := λ s t _ _ hs ht _,
    by rw [multiset.map_const, multiset.map_const, prod_replicate, prod_replicate, hs, ht] }

@[simp, to_additive] lemma const_apply (n : ℕ) (b : β) (x : α) : const A n b x = b := rfl

@[simp, to_additive]
lemma const_comp (n : ℕ) (c : γ) (f : A →*[n] β) {hf} : (const B n c).comp f hf = const A n c := rfl

/-- `1` is the Freiman homomorphism sending everything to `1`. -/
@[to_additive "`0` is the Freiman homomorphism sending everything to `0`."]
instance : has_one (A →*[n] β) := ⟨const A n 1⟩

@[simp, to_additive] lemma one_apply (x : α) : (1 : A →*[n] β) x = 1 := rfl

@[simp, to_additive] lemma one_comp (f : A →*[n] β) {hf} : (1 : B →*[n] γ).comp f hf = 1 := rfl

@[to_additive] instance : inhabited (A →*[n] β) := ⟨1⟩

/-- `f * g` is the Freiman homomorphism  sends `x` to `f x * g x`. -/
@[to_additive "`f + g` is the Freiman homomorphism sending `x` to `f x + g x`."]
instance : has_mul (A →*[n] β) :=
⟨λ f g, { to_fun := λ x, f x * g x,
  map_prod_eq_map_prod' := λ s t hsA htA hs ht h,
    by rw [prod_map_mul, prod_map_mul, map_prod_eq_map_prod f hsA htA hs ht h,
           map_prod_eq_map_prod g hsA htA hs ht h] }⟩

@[simp, to_additive] lemma mul_apply (f g : A →*[n] β) (x : α) : (f * g) x = f x * g x := rfl

@[to_additive] lemma mul_comp (g₁ g₂ : B →*[n] γ) (f : A →*[n] β) {hg hg₁ hg₂} :
  (g₁ * g₂).comp f hg = g₁.comp f hg₁ * g₂.comp f hg₂ := rfl

/-- If `f` is a Freiman homomorphism to a commutative group, then `f⁻¹` is the Freiman homomorphism
sending `x` to `(f x)⁻¹`. -/
@[to_additive "If `f` is a Freiman homomorphism to an additive commutative group, then `-f` is the
Freiman homomorphism sending `x` to `-f x`."]
instance : has_inv (A →*[n] G) :=
⟨λ f, { to_fun := λ x, (f x)⁻¹,
  map_prod_eq_map_prod' := λ s t hsA htA hs ht h,
    by rw [prod_map_inv, prod_map_inv, map_prod_eq_map_prod f hsA htA hs ht h] }⟩

@[simp, to_additive] lemma inv_apply (f : A →*[n] G) (x : α) : f⁻¹ x = (f x)⁻¹ := rfl

@[simp, to_additive] lemma inv_comp (f : B →*[n] G) (g : A →*[n] β) {hf hf'} :
  f⁻¹.comp g hf = (f.comp g hf')⁻¹ :=
ext $ λ x, rfl

/-- If `f` and `g` are Freiman homomorphisms to a commutative group, then `f / g` is the Freiman
homomorphism sending `x` to `f x / g x`. -/
@[to_additive "If `f` and `g` are additive Freiman homomorphisms to an additive commutative group,
then `f - g` is the additive Freiman homomorphism sending `x` to `f x - g x`"]
instance : has_div (A →*[n] G) :=
⟨λ f g, { to_fun := λ x, f x / g x,
  map_prod_eq_map_prod' := λ s t hsA htA hs ht h,
    by rw [prod_map_div, prod_map_div, map_prod_eq_map_prod f hsA htA hs ht h,
           map_prod_eq_map_prod g hsA htA hs ht h] }⟩

@[simp, to_additive] lemma div_apply (f g : A →*[n] G) (x : α) : (f / g) x = f x / g x := rfl

@[simp, to_additive] lemma div_comp (f₁ f₂ : B →*[n] G) (g : A →*[n] β) {hf hf₁ hf₂} :
  (f₁ / f₂).comp g hf = f₁.comp g hf₁ / f₂.comp g hf₂ :=
ext $ λ x, rfl

/-! ### Instances -/

/-- `A →*[n] β` is a `comm_monoid`. -/
@[to_additive "`α →+[n] β` is an `add_comm_monoid`."]
instance : comm_monoid (A →*[n] β) :=
{ mul := (*),
  mul_assoc := λ a b c, by { ext, apply mul_assoc },
  one := 1,
  one_mul := λ a, by { ext, apply one_mul },
  mul_one := λ a, by { ext, apply mul_one },
  mul_comm :=  λ a b, by { ext, apply mul_comm },
  npow := λ m f,
  { to_fun := λ x, f x ^ m,
    map_prod_eq_map_prod' := λ s t hsA htA hs ht h,
      by rw [prod_map_pow, prod_map_pow, map_prod_eq_map_prod f hsA htA hs ht h] },
  npow_zero' := λ f, by { ext x, exact pow_zero _ },
  npow_succ' := λ n f, by { ext x, exact pow_succ _ _ } }

/-- If `β` is a commutative group, then `A →*[n] β` is a commutative group too. -/
@[to_additive "If `β` is an additive commutative group, then `A →*[n] β` is an additive commutative
group too."]
instance {β} [comm_group β] : comm_group (A →*[n] β) :=
{ inv := has_inv.inv,
  div := has_div.div,
  div_eq_mul_inv := by { intros, ext, apply div_eq_mul_inv },
  mul_left_inv := by { intros, ext, apply mul_left_inv },
  zpow := λ n f, { to_fun := λ x, (f x) ^ n,
    map_prod_eq_map_prod' := λ s t hsA htA hs ht h,
      by rw [prod_map_zpow, prod_map_zpow, map_prod_eq_map_prod f hsA htA hs ht h] },
  zpow_zero' := λ f, by { ext x, exact zpow_zero _ },
  zpow_succ' := λ n f, by { ext x, simp_rw [zpow_of_nat, pow_succ, mul_apply, coe_mk] },
  zpow_neg'  := λ n f, by { ext x, simp_rw [zpow_neg_succ_of_nat, zpow_coe_nat], refl },
  ..freiman_hom.comm_monoid }

end freiman_hom

/-! ### Hom hierarchy -/

--TODO: change to `monoid_hom_class F A β → freiman_hom_class F A β n` once `map_multiset_prod` is
-- generalized
/-- A monoid homomorphism is naturally a `freiman_hom` on its entire domain.

We can't leave the domain `A : set α` of the `freiman_hom` a free variable, since it wouldn't be
inferrable. -/
@[to_additive " An additive monoid homomorphism is naturally an `add_freiman_hom` on its entire
domain.

We can't leave the domain `A : set α` of the `freiman_hom` a free variable, since it wouldn't be
inferrable."]
instance monoid_hom.freiman_hom_class : freiman_hom_class (α →* β) set.univ β n :=
{ map_prod_eq_map_prod' := λ f s t _ _ _ _ h, by rw [←f.map_multiset_prod, h, f.map_multiset_prod] }

/-- A `monoid_hom` is naturally a `freiman_hom`. -/
@[to_additive add_monoid_hom.to_add_freiman_hom "An `add_monoid_hom` is naturally an
`add_freiman_hom`"]
def monoid_hom.to_freiman_hom (A : set α) (n : ℕ) (f : α →* β) : A →*[n] β :=
{ to_fun := f,
  map_prod_eq_map_prod' := λ s t hsA htA, map_prod_eq_map_prod f
    (λ _ _, set.mem_univ _) (λ _ _, set.mem_univ _) }

@[simp, to_additive]
lemma monoid_hom.to_freiman_hom_coe (f : α →* β) : (f.to_freiman_hom A n : α → β) = f := rfl

@[to_additive]
lemma monoid_hom.to_freiman_hom_injective :
  function.injective (monoid_hom.to_freiman_hom A n : (α →* β) → A →*[n] β) :=
λ f g h, monoid_hom.ext $ show _, from fun_like.ext_iff.mp h

end comm_monoid

section cancel_comm_monoid
variables [comm_monoid α] [cancel_comm_monoid β] {A : set α} {m n : ℕ}

@[to_additive]
lemma map_prod_eq_map_prod_of_le [freiman_hom_class F A β n] (f : F) {s t : multiset α}
  (hsA : ∀ x ∈ s, x ∈ A) (htA : ∀ x ∈ t, x ∈ A) (hs : s.card = m)
  (ht : t.card = m) (hst : s.prod = t.prod) (h : m ≤ n) :
  (s.map f).prod = (t.map f).prod :=
begin
  obtain rfl | hm := m.eq_zero_or_pos,
  { rw card_eq_zero at hs ht,
    rw [hs, ht] },
  rw [←hs, card_pos_iff_exists_mem] at hm,
  obtain ⟨a, ha⟩ := hm,
  suffices : ((s + replicate (n - m) a).map f).prod = ((t + replicate (n - m) a).map f).prod,
  { simp_rw [multiset.map_add, prod_add] at this,
    exact mul_right_cancel this },
  replace ha := hsA _ ha,
  refine map_prod_eq_map_prod f (λ x hx, _) (λ x hx, _) _ _ _,
  rotate 2, assumption, -- Can't infer `A` and `n` from the context, so do it manually.
  { rw mem_add at hx,
    refine hx.elim (hsA _) (λ h, _),
    rwa eq_of_mem_replicate h },
  { rw mem_add at hx,
    refine hx.elim (htA _) (λ h, _),
    rwa eq_of_mem_replicate h },
  { rw [card_add, hs, card_replicate, add_tsub_cancel_of_le h] },
  { rw [card_add, ht, card_replicate, add_tsub_cancel_of_le h] },
  { rw [prod_add, prod_add, hst] }
end

/-- `α →*[n] β` is naturally included in  `A →*[m] β` for any `m ≤ n`. -/
@[to_additive add_freiman_hom.to_add_freiman_hom "`α →+[n] β` is naturally included in  `α →+[m] β`
for any `m ≤ n`"]
def freiman_hom.to_freiman_hom (h : m ≤ n) (f : A →*[n] β) : A →*[m] β :=
{ to_fun := f,
  map_prod_eq_map_prod' := λ s t hsA htA hs ht hst,
    map_prod_eq_map_prod_of_le f hsA htA hs ht hst h }

/-- A `n`-Freiman homomorphism is also a `m`-Freiman homomorphism for any `m ≤ n`. -/
@[to_additive add_freiman_hom.add_freiman_hom_class_of_le "An additive `n`-Freiman homomorphism is
also an additive `m`-Freiman homomorphism for any `m ≤ n`."]
lemma freiman_hom.freiman_hom_class_of_le [freiman_hom_class F A β n] (h : m ≤ n) :
  freiman_hom_class F A β m :=
{ map_prod_eq_map_prod' := λ f s t hsA htA hs ht hst,
    map_prod_eq_map_prod_of_le f hsA htA hs ht hst h }

@[simp, to_additive add_freiman_hom.to_add_freiman_hom_coe]
lemma freiman_hom.to_freiman_hom_coe (h : m ≤ n) (f : A →*[n] β) :
  (f.to_freiman_hom h : α → β) = f := rfl

@[to_additive]
lemma freiman_hom.to_freiman_hom_injective (h : m ≤ n) :
  function.injective (freiman_hom.to_freiman_hom h : (A →*[n] β) → A →*[m] β) :=
λ f g hfg, freiman_hom.ext $ by convert fun_like.ext_iff.1 hfg

end cancel_comm_monoid


/-
Copyright (c) 2020 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau
-/
import algebra.group_ring_action.basic
import algebra.hom.group_action
import data.polynomial.algebra_map
import data.polynomial.monic
import group_theory.group_action.quotient

/-!
# Group action on rings applied to polynomials

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file contains instances and definitions relating `mul_semiring_action` to `polynomial`.
-/

variables (M : Type*) [monoid M]

open_locale polynomial
namespace polynomial

variables (R : Type*) [semiring R]

variables {M}

lemma smul_eq_map [mul_semiring_action M R] (m : M) :
  ((•) m) = map (mul_semiring_action.to_ring_hom M R m) :=
begin
  suffices :
    distrib_mul_action.to_add_monoid_hom R[X] m =
      (map_ring_hom (mul_semiring_action.to_ring_hom M R m)).to_add_monoid_hom,
  { ext1 r, exact add_monoid_hom.congr_fun this r, },
  ext n r : 2,
  change m • monomial n r = map (mul_semiring_action.to_ring_hom M R m) (monomial n r),
  simpa only [polynomial.map_monomial, polynomial.smul_monomial],
end

variables (M)

noncomputable instance [mul_semiring_action M R] : mul_semiring_action M R[X] :=
{ smul := (•),
  smul_one := λ m,
    (smul_eq_map R m).symm ▸ polynomial.map_one (mul_semiring_action.to_ring_hom M R m),
  smul_mul := λ m p q,
    (smul_eq_map R m).symm ▸ polynomial.map_mul (mul_semiring_action.to_ring_hom M R m),
  ..polynomial.distrib_mul_action }

variables {M R}

variables [mul_semiring_action M R]

@[simp] lemma smul_X (m : M) : (m • X : R[X]) = X :=
(smul_eq_map R m).symm ▸ map_X _

variables (S : Type*) [comm_semiring S] [mul_semiring_action M S]

theorem smul_eval_smul (m : M) (f : S[X]) (x : S) :
  (m • f).eval (m • x) = m • f.eval x :=
polynomial.induction_on f
  (λ r, by rw [smul_C, eval_C, eval_C])
  (λ f g ihf ihg, by rw [smul_add, eval_add, ihf, ihg, eval_add, smul_add])
  (λ n r ih, by rw [smul_mul', smul_pow', smul_C, smul_X, eval_mul, eval_C, eval_pow, eval_X,
      eval_mul, eval_C, eval_pow, eval_X, smul_mul', smul_pow'])

variables (G : Type*) [group G]

theorem eval_smul' [mul_semiring_action G S] (g : G) (f : S[X]) (x : S) :
  f.eval (g • x) = g • (g⁻¹ • f).eval x :=
by rw [← smul_eval_smul, smul_inv_smul]

theorem smul_eval [mul_semiring_action G S] (g : G) (f : S[X]) (x : S) :
  (g • f).eval x = g • f.eval (g⁻¹ • x) :=
by rw [← smul_eval_smul, smul_inv_smul]

end polynomial

section comm_ring

variables (G : Type*) [group G] [fintype G]
variables (R : Type*) [comm_ring R] [mul_semiring_action G R]
open mul_action
open_locale classical

/-- the product of `(X - g • x)` over distinct `g • x`. -/
noncomputable def prod_X_sub_smul (x : R) : R[X] :=
(finset.univ : finset (G ⧸ mul_action.stabilizer G x)).prod $
λ g, polynomial.X - polynomial.C (of_quotient_stabilizer G x g)

theorem prod_X_sub_smul.monic (x : R) : (prod_X_sub_smul G R x).monic :=
polynomial.monic_prod_of_monic _ _ $ λ g _, polynomial.monic_X_sub_C _

theorem prod_X_sub_smul.eval (x : R) : (prod_X_sub_smul G R x).eval x = 0 :=
(monoid_hom.map_prod
  ((polynomial.aeval x).to_ring_hom.to_monoid_hom : R[X] →* R) _ _).trans $
  finset.prod_eq_zero (finset.mem_univ $ quotient_group.mk 1) $
  by simp

theorem prod_X_sub_smul.smul (x : R) (g : G) :
  g • prod_X_sub_smul G R x = prod_X_sub_smul G R x :=
finset.smul_prod.trans $ fintype.prod_bijective _ (mul_action.bijective g) _ _
  (λ g', by rw [of_quotient_stabilizer_smul, smul_sub, polynomial.smul_X, polynomial.smul_C])

theorem prod_X_sub_smul.coeff (x : R) (g : G) (n : ℕ) :
  g • (prod_X_sub_smul G R x).coeff n =
  (prod_X_sub_smul G R x).coeff n :=
by rw [← polynomial.coeff_smul, prod_X_sub_smul.smul]

end comm_ring

namespace mul_semiring_action_hom
variables {M}
variables {P : Type*} [comm_semiring P] [mul_semiring_action M P]
variables {Q : Type*} [comm_semiring Q] [mul_semiring_action M Q]
open polynomial

/-- An equivariant map induces an equivariant map on polynomials. -/
protected noncomputable def polynomial (g : P →+*[M] Q) : P[X] →+*[M] Q[X] :=
{ to_fun := map g,
  map_smul' := λ m p, polynomial.induction_on p
    (λ b, by rw [smul_C, map_C, coe_fn_coe, g.map_smul, map_C, coe_fn_coe, smul_C])
    (λ p q ihp ihq, by rw [smul_add, polynomial.map_add, ihp, ihq, polynomial.map_add, smul_add])
    (λ n b ih, by rw [smul_mul', smul_C, smul_pow', smul_X, polynomial.map_mul, map_C,
        polynomial.map_pow, map_X, coe_fn_coe, g.map_smul, polynomial.map_mul, map_C,
        polynomial.map_pow, map_X, smul_mul', smul_C, smul_pow', smul_X, coe_fn_coe]),
  map_zero' := polynomial.map_zero g,
  map_add' := λ p q, polynomial.map_add g,
  map_one' := polynomial.map_one g,
  map_mul' := λ p q, polynomial.map_mul g }

@[simp] theorem coe_polynomial (g : P →+*[M] Q) :
  (g.polynomial : P[X] → Q[X]) = map g :=
rfl

end mul_semiring_action_hom


/-
Copyright (c) 2020 Kevin Kappelmann. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Kappelmann

! This file was ported from Lean 3 source module algebra.continued_fractions.computation.translations
! leanprover-community/mathlib commit a7e36e48519ab281320c4d192da6a7b348ce40ad
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.ContinuedFractions.Computation.Basic
import Mathbin.Algebra.ContinuedFractions.Translations

/-!
# Basic Translation Lemmas Between Structures Defined for Computing Continued Fractions

## Summary

This is a collection of simple lemmas between the different structures used for the computation
of continued fractions defined in `algebra.continued_fractions.computation.basic`. The file consists
of three sections:
1. Recurrences and inversion lemmas for `int_fract_pair.stream`: these lemmas give us inversion
   rules and recurrences for the computation of the stream of integer and fractional parts of
   a value.
2. Translation lemmas for the head term: these lemmas show us that the head term of the computed
   continued fraction of a value `v` is `⌊v⌋` and how this head term is moved along the structures
   used in the computation process.
3. Translation lemmas for the sequence: these lemmas show how the sequences of the involved
   structures (`int_fract_pair.stream`, `int_fract_pair.seq1`, and
   `generalized_continued_fraction.of`) are connected, i.e. how the values are moved along the
   structures and the termination of one sequence implies the termination of another sequence.

## Main Theorems

- `succ_nth_stream_eq_some_iff` gives as a recurrence to compute the `n + 1`th value of the sequence
  of integer and fractional parts of a value in case of non-termination.
- `succ_nth_stream_eq_none_iff` gives as a recurrence to compute the `n + 1`th value of the sequence
  of integer and fractional parts of a value in case of termination.
- `nth_of_eq_some_of_succ_nth_int_fract_pair_stream` and
  `nth_of_eq_some_of_nth_int_fract_pair_stream_fr_ne_zero` show how the entries of the sequence
  of the computed continued fraction can be obtained from the stream of integer and fractional
  parts.
-/

namespace GeneralizedContinuedFraction

open GeneralizedContinuedFraction (of)

/- ./././Mathport/Syntax/Translate/Command.lean:224:11: unsupported: unusual advanced open style -/
-- Fix a discrete linear ordered floor field and a value `v`.
variable {K : Type _} [LinearOrderedField K] [FloorRing K] {v : K}

namespace IntFractPair

/-!
### Recurrences and Inversion Lemmas for `int_fract_pair.stream`

Here we state some lemmas that give us inversion rules and recurrences for the computation of the
stream of integer and fractional parts of a value.
-/

theorem stream_zero (v : K) : IntFractPair.stream v 0 = some (IntFractPair.of v) :=
  rfl
#align generalized_continued_fraction.int_fract_pair.stream_zero GeneralizedContinuedFraction.IntFractPair.stream_zero

variable {n : ℕ}

theorem stream_eq_none_of_fr_eq_zero {ifp_n : IntFractPair K}
    (stream_nth_eq : IntFractPair.stream v n = some ifp_n) (nth_fr_eq_zero : ifp_n.fr = 0) :
    IntFractPair.stream v (n + 1) = none :=
  by
  cases' ifp_n with _ fr
  change fr = 0 at nth_fr_eq_zero
  simp [int_fract_pair.stream, stream_nth_eq, nth_fr_eq_zero]
#align generalized_continued_fraction.int_fract_pair.stream_eq_none_of_fr_eq_zero GeneralizedContinuedFraction.IntFractPair.stream_eq_none_of_fr_eq_zero

/-- Gives a recurrence to compute the `n + 1`th value of the sequence of integer and fractional
parts of a value in case of termination.
-/
theorem succ_nth_stream_eq_none_iff :
    IntFractPair.stream v (n + 1) = none ↔
      IntFractPair.stream v n = none ∨ ∃ ifp, IntFractPair.stream v n = some ifp ∧ ifp.fr = 0 :=
  by
  rw [int_fract_pair.stream]
  cases int_fract_pair.stream v n <;> simp [imp_false]
#align generalized_continued_fraction.int_fract_pair.succ_nth_stream_eq_none_iff GeneralizedContinuedFraction.IntFractPair.succ_nth_stream_eq_none_iff

/-- Gives a recurrence to compute the `n + 1`th value of the sequence of integer and fractional
parts of a value in case of non-termination.
-/
theorem succ_nth_stream_eq_some_iff {ifp_succ_n : IntFractPair K} :
    IntFractPair.stream v (n + 1) = some ifp_succ_n ↔
      ∃ ifp_n : IntFractPair K,
        IntFractPair.stream v n = some ifp_n ∧
          ifp_n.fr ≠ 0 ∧ IntFractPair.of ifp_n.fr⁻¹ = ifp_succ_n :=
  by simp [int_fract_pair.stream, ite_eq_iff]
#align generalized_continued_fraction.int_fract_pair.succ_nth_stream_eq_some_iff GeneralizedContinuedFraction.IntFractPair.succ_nth_stream_eq_some_iff

/-- An easier to use version of one direction of
`generalized_continued_fraction.int_fract_pair.succ_nth_stream_eq_some_iff`.
-/
theorem stream_succ_of_some {p : IntFractPair K} (h : IntFractPair.stream v n = some p)
    (h' : p.fr ≠ 0) : IntFractPair.stream v (n + 1) = some (IntFractPair.of p.fr⁻¹) :=
  succ_nth_stream_eq_some_iff.mpr ⟨p, h, h', rfl⟩
#align generalized_continued_fraction.int_fract_pair.stream_succ_of_some GeneralizedContinuedFraction.IntFractPair.stream_succ_of_some

/-- The stream of `int_fract_pair`s of an integer stops after the first term.
-/
theorem stream_succ_of_int (a : ℤ) (n : ℕ) : IntFractPair.stream (a : K) (n + 1) = none :=
  by
  induction' n with n ih
  · refine' int_fract_pair.stream_eq_none_of_fr_eq_zero (int_fract_pair.stream_zero (a : K)) _
    simp only [int_fract_pair.of, Int.fract_intCast]
  · exact int_fract_pair.succ_nth_stream_eq_none_iff.mpr (Or.inl ih)
#align generalized_continued_fraction.int_fract_pair.stream_succ_of_int GeneralizedContinuedFraction.IntFractPair.stream_succ_of_int

theorem exists_succ_nth_stream_of_fr_zero {ifp_succ_n : IntFractPair K}
    (stream_succ_nth_eq : IntFractPair.stream v (n + 1) = some ifp_succ_n)
    (succ_nth_fr_eq_zero : ifp_succ_n.fr = 0) :
    ∃ ifp_n : IntFractPair K, IntFractPair.stream v n = some ifp_n ∧ ifp_n.fr⁻¹ = ⌊ifp_n.fr⁻¹⌋ :=
  by
  -- get the witness from `succ_nth_stream_eq_some_iff` and prove that it has the additional
  -- properties
  rcases succ_nth_stream_eq_some_iff.mp stream_succ_nth_eq with
    ⟨ifp_n, seq_nth_eq, nth_fr_ne_zero, rfl⟩
  refine' ⟨ifp_n, seq_nth_eq, _⟩
  simpa only [int_fract_pair.of, Int.fract, sub_eq_zero] using succ_nth_fr_eq_zero
#align generalized_continued_fraction.int_fract_pair.exists_succ_nth_stream_of_fr_zero GeneralizedContinuedFraction.IntFractPair.exists_succ_nth_stream_of_fr_zero

/-- A recurrence relation that expresses the `(n+1)`th term of the stream of `int_fract_pair`s
of `v` for non-integer `v` in terms of the `n`th term of the stream associated to
the inverse of the fractional part of `v`.
-/
theorem stream_succ (h : Int.fract v ≠ 0) (n : ℕ) :
    IntFractPair.stream v (n + 1) = IntFractPair.stream (Int.fract v)⁻¹ n :=
  by
  induction' n with n ih
  · have H : (int_fract_pair.of v).fr = Int.fract v := rfl
    rw [stream_zero, stream_succ_of_some (stream_zero v) (ne_of_eq_of_ne H h), H]
  · cases' eq_or_ne (int_fract_pair.stream (Int.fract v)⁻¹ n) none with hnone hsome
    · rw [hnone] at ih
      rw [succ_nth_stream_eq_none_iff.mpr (Or.inl hnone),
        succ_nth_stream_eq_none_iff.mpr (Or.inl ih)]
    · obtain ⟨p, hp⟩ := option.ne_none_iff_exists'.mp hsome
      rw [hp] at ih
      cases' eq_or_ne p.fr 0 with hz hnz
      · rw [stream_eq_none_of_fr_eq_zero hp hz, stream_eq_none_of_fr_eq_zero ih hz]
      · rw [stream_succ_of_some hp hnz, stream_succ_of_some ih hnz]
#align generalized_continued_fraction.int_fract_pair.stream_succ GeneralizedContinuedFraction.IntFractPair.stream_succ

end IntFractPair

section Head

/-!
### Translation of the Head Term

Here we state some lemmas that show us that the head term of the computed continued fraction of a
value `v` is `⌊v⌋` and how this head term is moved along the structures used in the computation
process.
-/

/-- The head term of the sequence with head of `v` is just the integer part of `v`. -/
@[simp]
theorem IntFractPair.seq1_fst_eq_of : (IntFractPair.seq1 v).fst = IntFractPair.of v :=
  rfl
#align generalized_continued_fraction.int_fract_pair.seq1_fst_eq_of GeneralizedContinuedFraction.IntFractPair.seq1_fst_eq_of

theorem of_h_eq_intFractPair_seq1_fst_b : (of v).h = (IntFractPair.seq1 v).fst.b :=
  by
  cases aux_seq_eq : int_fract_pair.seq1 v
  simp [of, aux_seq_eq]
#align generalized_continued_fraction.of_h_eq_int_fract_pair_seq1_fst_b GeneralizedContinuedFraction.of_h_eq_intFractPair_seq1_fst_b

/-- The head term of the gcf of `v` is `⌊v⌋`. -/
@[simp]
theorem of_h_eq_floor : (of v).h = ⌊v⌋ := by
  simp [of_h_eq_int_fract_pair_seq1_fst_b, int_fract_pair.of]
#align generalized_continued_fraction.of_h_eq_floor GeneralizedContinuedFraction.of_h_eq_floor

end Head

section sequence

/-!
### Translation of the Sequences

Here we state some lemmas that show how the sequences of the involved structures
(`int_fract_pair.stream`, `int_fract_pair.seq1`, and `generalized_continued_fraction.of`) are
connected, i.e. how the values are moved along the structures and how the termination of one
sequence implies the termination of another sequence.
-/

variable {n : ℕ}

theorem IntFractPair.nth_seq1_eq_succ_nth_stream :
    (IntFractPair.seq1 v).snd.get? n = (IntFractPair.stream v) (n + 1) :=
  rfl
#align generalized_continued_fraction.int_fract_pair.nth_seq1_eq_succ_nth_stream GeneralizedContinuedFraction.IntFractPair.nth_seq1_eq_succ_nth_stream

section Termination

/-!
#### Translation of the Termination of the Sequences

Let's first show how the termination of one sequence implies the termination of another sequence.
-/

theorem of_terminatedAt_iff_intFractPair_seq1_terminatedAt :
    (of v).TerminatedAt n ↔ (IntFractPair.seq1 v).snd.TerminatedAt n :=
  Option.map_eq_none
#align generalized_continued_fraction.of_terminated_at_iff_int_fract_pair_seq1_terminated_at GeneralizedContinuedFraction.of_terminatedAt_iff_intFractPair_seq1_terminatedAt

theorem of_terminatedAt_n_iff_succ_nth_intFractPair_stream_eq_none :
    (of v).TerminatedAt n ↔ IntFractPair.stream v (n + 1) = none := by
  rw [of_terminated_at_iff_int_fract_pair_seq1_terminated_at, Stream'.Seq.TerminatedAt,
    int_fract_pair.nth_seq1_eq_succ_nth_stream]
#align generalized_continued_fraction.of_terminated_at_n_iff_succ_nth_int_fract_pair_stream_eq_none GeneralizedContinuedFraction.of_terminatedAt_n_iff_succ_nth_intFractPair_stream_eq_none

end Termination

section Values

/-!
#### Translation of the Values of the Sequence

Now let's show how the values of the sequences correspond to one another.
-/

theorem IntFractPair.exists_succ_nth_stream_of_gcf_of_nth_eq_some {gp_n : Pair K}
    (s_nth_eq : (of v).s.get? n = some gp_n) :
    ∃ ifp : IntFractPair K, IntFractPair.stream v (n + 1) = some ifp ∧ (ifp.b : K) = gp_n.b :=
  by
  obtain ⟨ifp, stream_succ_nth_eq, gp_n_eq⟩ :
    ∃ ifp, int_fract_pair.stream v (n + 1) = some ifp ∧ pair.mk 1 (ifp.b : K) = gp_n :=
    by
    unfold of int_fract_pair.seq1 at s_nth_eq
    rwa [seq.map_tail, seq.nth_tail, seq.map_nth, Option.map_eq_some'] at s_nth_eq
  cases gp_n_eq
  injection gp_n_eq with _ ifp_b_eq_gp_n_b
  exists ifp
  exact ⟨stream_succ_nth_eq, ifp_b_eq_gp_n_b⟩
#align generalized_continued_fraction.int_fract_pair.exists_succ_nth_stream_of_gcf_of_nth_eq_some GeneralizedContinuedFraction.IntFractPair.exists_succ_nth_stream_of_gcf_of_nth_eq_some

/-- Shows how the entries of the sequence of the computed continued fraction can be obtained by the
integer parts of the stream of integer and fractional parts.
-/
theorem nth_of_eq_some_of_succ_nth_intFractPair_stream {ifp_succ_n : IntFractPair K}
    (stream_succ_nth_eq : IntFractPair.stream v (n + 1) = some ifp_succ_n) :
    (of v).s.get? n = some ⟨1, ifp_succ_n.b⟩ :=
  by
  unfold of int_fract_pair.seq1
  rw [seq.map_tail, seq.nth_tail, seq.map_nth]
  simp [seq.nth, stream_succ_nth_eq]
#align generalized_continued_fraction.nth_of_eq_some_of_succ_nth_int_fract_pair_stream GeneralizedContinuedFraction.nth_of_eq_some_of_succ_nth_intFractPair_stream

/-- Shows how the entries of the sequence of the computed continued fraction can be obtained by the
fractional parts of the stream of integer and fractional parts.
-/
theorem nth_of_eq_some_of_nth_intFractPair_stream_fr_ne_zero {ifp_n : IntFractPair K}
    (stream_nth_eq : IntFractPair.stream v n = some ifp_n) (nth_fr_ne_zero : ifp_n.fr ≠ 0) :
    (of v).s.get? n = some ⟨1, (IntFractPair.of ifp_n.fr⁻¹).b⟩ :=
  have : IntFractPair.stream v (n + 1) = some (IntFractPair.of ifp_n.fr⁻¹) :=
    by
    cases ifp_n
    simp [int_fract_pair.stream, stream_nth_eq, nth_fr_ne_zero]
  nth_of_eq_some_of_succ_nth_intFractPair_stream this
#align generalized_continued_fraction.nth_of_eq_some_of_nth_int_fract_pair_stream_fr_ne_zero GeneralizedContinuedFraction.nth_of_eq_some_of_nth_intFractPair_stream_fr_ne_zero

open Int IntFractPair

theorem of_s_head_aux (v : K) :
    (of v).s.get? 0 =
      (IntFractPair.stream v 1).bind
        (some ∘ fun p =>
          { a := 1
            b := p.b }) :=
  by
  rw [of, int_fract_pair.seq1, of._match_1]
  simp only [seq.map_tail, seq.map, seq.tail, seq.head, seq.nth, Stream'.map]
  rw [← Stream'.nth_succ, Stream'.nth, Option.map]
#align generalized_continued_fraction.of_s_head_aux GeneralizedContinuedFraction.of_s_head_aux

/-- This gives the first pair of coefficients of the continued fraction of a non-integer `v`.
-/
theorem of_s_head (h : fract v ≠ 0) : (of v).s.headI = some ⟨1, ⌊(fract v)⁻¹⌋⟩ :=
  by
  change (of v).s.get? 0 = _
  rw [of_s_head_aux, stream_succ_of_some (stream_zero v) h, Option.bind]
  rfl
#align generalized_continued_fraction.of_s_head GeneralizedContinuedFraction.of_s_head

variable (K)

/-- If `a` is an integer, then the coefficient sequence of its continued fraction is empty.
-/
theorem of_s_of_int (a : ℤ) : (of (a : K)).s = Seq.nil :=
  haveI h : ∀ n, (of (a : K)).s.get? n = none :=
    by
    intro n
    induction' n with n ih
    · rw [of_s_head_aux, stream_succ_of_int, Option.bind]
    · exact (of (a : K)).s.Prop ih
  seq.ext fun n => (h n).trans (seq.nth_nil n).symm
#align generalized_continued_fraction.of_s_of_int GeneralizedContinuedFraction.of_s_of_int

variable {K} (v)

/-- Recurrence for the `generalized_continued_fraction.of` an element `v` of `K` in terms of
that of the inverse of the fractional part of `v`.
-/
theorem of_s_succ (n : ℕ) : (of v).s.get? (n + 1) = (of (fract v)⁻¹).s.get? n :=
  by
  cases' eq_or_ne (fract v) 0 with h h
  · obtain ⟨a, rfl⟩ : ∃ a : ℤ, v = a := ⟨⌊v⌋, eq_of_sub_eq_zero h⟩
    rw [fract_int_cast, inv_zero, of_s_of_int, ← cast_zero, of_s_of_int, seq.nth_nil, seq.nth_nil]
  cases' eq_or_ne ((of (fract v)⁻¹).s.get? n) none with h₁ h₁
  ·
    rwa [h₁, ← terminated_at_iff_s_none,
      of_terminated_at_n_iff_succ_nth_int_fract_pair_stream_eq_none, stream_succ h, ←
      of_terminated_at_n_iff_succ_nth_int_fract_pair_stream_eq_none, terminated_at_iff_s_none]
  · obtain ⟨p, hp⟩ := option.ne_none_iff_exists'.mp h₁
    obtain ⟨p', hp'₁, _⟩ := exists_succ_nth_stream_of_gcf_of_nth_eq_some hp
    have Hp := nth_of_eq_some_of_succ_nth_int_fract_pair_stream hp'₁
    rw [← stream_succ h] at hp'₁
    rw [Hp, nth_of_eq_some_of_succ_nth_int_fract_pair_stream hp'₁]
#align generalized_continued_fraction.of_s_succ GeneralizedContinuedFraction.of_s_succ

/-- This expresses the tail of the coefficient sequence of the `generalized_continued_fraction.of`
an element `v` of `K` as the coefficient sequence of that of the inverse of the
fractional part of `v`.
-/
theorem of_s_tail : (of v).s.tail = (of (fract v)⁻¹).s :=
  Seq.ext fun n => Seq.nth_tail (of v).s n ▸ of_s_succ v n
#align generalized_continued_fraction.of_s_tail GeneralizedContinuedFraction.of_s_tail

variable (K) (n)

/-- If `a` is an integer, then the `convergents'` of its continued fraction expansion
are all equal to `a`.
-/
theorem convergents'_of_int (a : ℤ) : (of (a : K)).convergents' n = a :=
  by
  induction' n with n ih
  · simp only [zeroth_convergent'_eq_h, of_h_eq_floor, floor_int_cast]
  · rw [convergents', of_h_eq_floor, floor_int_cast, add_right_eq_self]
    exact convergents'_aux_succ_none ((of_s_of_int K a).symm ▸ seq.nth_nil 0) _
#align generalized_continued_fraction.convergents'_of_int GeneralizedContinuedFraction.convergents'_of_int

variable {K} (v)

/-- The recurrence relation for the `convergents'` of the continued fraction expansion
of an element `v` of `K` in terms of the convergents of the inverse of its fractional part.
-/
theorem convergents'_succ :
    (of v).convergents' (n + 1) = ⌊v⌋ + 1 / (of (fract v)⁻¹).convergents' n :=
  by
  cases' eq_or_ne (fract v) 0 with h h
  · obtain ⟨a, rfl⟩ : ∃ a : ℤ, v = a := ⟨⌊v⌋, eq_of_sub_eq_zero h⟩
    rw [convergents'_of_int, fract_int_cast, inv_zero, ← cast_zero, convergents'_of_int, cast_zero,
      div_zero, add_zero, floor_int_cast]
  · rw [convergents', of_h_eq_floor, add_right_inj, convergents'_aux_succ_some (of_s_head h)]
    exact congr_arg ((· / ·) 1) (by rw [convergents', of_h_eq_floor, add_right_inj, of_s_tail])
#align generalized_continued_fraction.convergents'_succ GeneralizedContinuedFraction.convergents'_succ

end Values

end sequence

end GeneralizedContinuedFraction


/-
Copyright (c) 2020 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.arrow
import Mathlib.PostPort

universes v u l

namespace Mathlib

/-!
# Strong epimorphisms

In this file, we define strong epimorphisms. A strong epimorphism is an epimorphism `f`, such
that for every commutative square with `f` at the top and a monomorphism at the bottom, there is
a diagonal morphism making the two triangles commute. This lift is necessarily unique (as shown in
`comma.lean`).

## Main results

Besides the definition, we show that
* the composition of two strong epimorphisms is a strong epimorphism,
* if `f ≫ g` is a strong epimorphism, then so is `g`,
* if `f` is both a strong epimorphism and a monomorphism, then it is an isomorphism

## Future work

There is also the dual notion of strong monomorphism.

## References

* [F. Borceux, *Handbook of Categorical Algebra 1*][borceux-vol1]
-/

namespace category_theory

/-- A strong epimorphism `f` is an epimorphism such that every commutative square with `f` at the
    top and a monomorphism at the bottom has a lift. -/
class strong_epi {C : Type u} [category C] {P : C} {Q : C} (f : P ⟶ Q)
where
  epi : epi f
  has_lift : ∀ {X Y : C} {u : P ⟶ X} {v : Q ⟶ Y} {z : X ⟶ Y} [_inst_2 : mono z] (h : u ≫ z = f ≫ v), arrow.has_lift (arrow.hom_mk' h)

protected instance epi_of_strong_epi {C : Type u} [category C] {P : C} {Q : C} (f : P ⟶ Q) [strong_epi f] : epi f :=
  strong_epi.epi

/-- The composition of two strong epimorphisms is a strong epimorphism. -/
theorem strong_epi_comp {C : Type u} [category C] {P : C} {Q : C} {R : C} (f : P ⟶ Q) (g : Q ⟶ R) [strong_epi f] [strong_epi g] : strong_epi (f ≫ g) := sorry

/-- If `f ≫ g` is a strong epimorphism, then so is g. -/
theorem strong_epi_of_strong_epi {C : Type u} [category C] {P : C} {Q : C} {R : C} (f : P ⟶ Q) (g : Q ⟶ R) [strong_epi (f ≫ g)] : strong_epi g := sorry

/-- An isomorphism is in particular a strong epimorphism. -/
protected instance strong_epi_of_is_iso {C : Type u} [category C] {P : C} {Q : C} (f : P ⟶ Q) [is_iso f] : strong_epi f := sorry

/-- A strong epimorphism that is a monomorphism is an isomorphism. -/
def is_iso_of_mono_of_strong_epi {C : Type u} [category C] {P : C} {Q : C} (f : P ⟶ Q) [mono f] [strong_epi f] : is_iso f :=
  is_iso.mk (arrow.lift (arrow.hom_mk' sorry))


/- Tactic : rwa

`rwa` uses the rewrite tactic and then uses `exact` with all
the assumptions of the proof.

-/

/-
The next tactic we will learn is `rwa` which applies rewrite tactic and then uses `exact` with all the hypotheses.
-/

variables {X : Type} -- hide

/- Lemma : no-side-bar
Let A be a set and x ∈ A, using the assumption A ∪ A = A, we obtain that x ∈ B.
-/
lemma example_on_rwa (A : set X) (x : X) (h1 : x ∈ A) (h2 : A ∪ A = A) : x ∈ A ∪ A :=
begin
  rwa h2,

end


/-
Copyright (c) 2022 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import analysis.analytic.linear
import analysis.analytic.composition
import analysis.normed_space.completion

/-!
# Uniqueness principle for analytic functions

We show that two analytic functions which coincide around a point coincide on whole connected sets,
in `analytic_on.eq_on_of_preconnected_of_eventually_eq`.
-/

variables {𝕜 : Type*} [nontrivially_normed_field 𝕜]
{E : Type*} [normed_add_comm_group E] [normed_space 𝕜 E]
{F : Type*} [normed_add_comm_group F] [normed_space 𝕜 F]

open set
open_locale topology ennreal

namespace analytic_on

/-- If an analytic function vanishes around a point, then it is uniformly zero along
a connected set. Superseded by `eq_on_zero_of_preconnected_of_locally_zero` which does not assume
completeness of the target space. -/
theorem eq_on_zero_of_preconnected_of_eventually_eq_zero_aux [complete_space F]
  {f : E → F} {U : set E} (hf : analytic_on 𝕜 f U) (hU : is_preconnected U) {z₀ : E}
  (h₀ : z₀ ∈ U) (hfz₀ : f =ᶠ[𝓝 z₀] 0) : eq_on f 0 U :=
begin
  /- Let `u` be the set of points around which `f` vanishes. It is clearly open. We have to show
  that its limit points in `U` still belong to it, from which the inclusion `U ⊆ u` will follow
  by connectedness. -/
  let u := {x | f =ᶠ[𝓝 x] 0},
  suffices main : closure u ∩ U ⊆ u,
  { have Uu : U ⊆ u, from
      hU.subset_of_closure_inter_subset is_open_set_of_eventually_nhds ⟨z₀, h₀, hfz₀⟩ main,
    assume z hz,
    simpa using mem_of_mem_nhds (Uu hz) },
  /- Take a limit point `x`, then a ball `B (x, r)` on which it has a power series expansion, and
  then `y ∈ B (x, r/2) ∩ u`. Then `f` has a power series expansion on `B (y, r/2)` as it is
  contained in `B (x, r)`. All the coefficients in this series expansion vanish, as `f` is zero on a
  neighborhood of `y`. Therefore, `f` is zero on `B (y, r/2)`. As this ball contains `x`, it follows
  that `f` vanishes on a neighborhood of `x`, proving the claim. -/
  rintros x ⟨xu, xU⟩,
  rcases hf x xU with ⟨p, r, hp⟩,
  obtain ⟨y, yu, hxy⟩ : ∃ y ∈ u, edist x y < r / 2,
    from emetric.mem_closure_iff.1 xu (r / 2) (ennreal.half_pos hp.r_pos.ne'),
  let q := p.change_origin (y - x),
  have has_series : has_fpower_series_on_ball f q y (r / 2),
  { have A : (‖y - x‖₊ : ℝ≥0∞) < r / 2, by rwa [edist_comm, edist_eq_coe_nnnorm_sub] at hxy,
    have := hp.change_origin (A.trans_le ennreal.half_le_self),
    simp only [add_sub_cancel'_right] at this,
    apply this.mono (ennreal.half_pos hp.r_pos.ne'),
    apply ennreal.le_sub_of_add_le_left ennreal.coe_ne_top,
    apply (add_le_add (A.le) (le_refl (r / 2))).trans (le_of_eq _),
    exact ennreal.add_halves _ },
  have M : emetric.ball y (r / 2) ∈ 𝓝 x, from emetric.is_open_ball.mem_nhds hxy,
  filter_upwards [M] with z hz,
  have A : has_sum (λ (n : ℕ), q n (λ (i : fin n), z - y)) (f z) := has_series.has_sum_sub hz,
  have B : has_sum (λ (n : ℕ), q n (λ (i : fin n), z - y)) (0),
  { have : has_fpower_series_at 0 q y, from has_series.has_fpower_series_at.congr yu,
    convert has_sum_zero,
    ext n,
    exact this.apply_eq_zero n _ },
  exact has_sum.unique A B
end

/-- The *identity principle* for analytic functions: If an analytic function vanishes in a whole
neighborhood of a point `z₀`, then it is uniformly zero along a connected set. For a one-dimensional
version assuming only that the function vanishes at some points arbitrarily close to `z₀`, see
`eq_on_zero_of_preconnected_of_frequently_eq_zero`. -/
theorem eq_on_zero_of_preconnected_of_eventually_eq_zero
  {f : E → F} {U : set E} (hf : analytic_on 𝕜 f U) (hU : is_preconnected U) {z₀ : E}
  (h₀ : z₀ ∈ U) (hfz₀ : f =ᶠ[𝓝 z₀] 0) :
  eq_on f 0 U :=
begin
  let F' := uniform_space.completion F,
  set e : F →L[𝕜] F' := uniform_space.completion.to_complL,
  have : analytic_on 𝕜 (e ∘ f) U := λ x hx, (e.analytic_at _).comp (hf x hx),
  have A : eq_on (e ∘ f) 0 U,
  { apply eq_on_zero_of_preconnected_of_eventually_eq_zero_aux this hU h₀,
    filter_upwards [hfz₀] with x hx,
    simp only [hx, function.comp_app, pi.zero_apply, map_zero] },
  assume z hz,
  have : e (f z) = e 0, by simpa only using A hz,
  exact uniform_space.completion.coe_injective F this,
end

/-- The *identity principle* for analytic functions: If two analytic function coincide in a whole
neighborhood of a point `z₀`, then they coincide globally along a connected set.
For a one-dimensional version assuming only that the functions coincide at some points
arbitrarily close to `z₀`, see `eq_on_of_preconnected_of_frequently_eq`. -/
theorem eq_on_of_preconnected_of_eventually_eq
  {f g : E → F} {U : set E} (hf : analytic_on 𝕜 f U) (hg : analytic_on 𝕜 g U)
  (hU : is_preconnected U) {z₀ : E} (h₀ : z₀ ∈ U) (hfg : f =ᶠ[𝓝 z₀] g) :
  eq_on f g U :=
begin
  have hfg' : (f - g) =ᶠ[𝓝 z₀] 0 := hfg.mono (λ z h, by simp [h]),
  simpa [sub_eq_zero] using
    λ z hz, (hf.sub hg).eq_on_zero_of_preconnected_of_eventually_eq_zero hU h₀ hfg' hz,
end

end analytic_on


/-
Copyright (c) 2018 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Jeremy Avigad, Mario Carneiro, Simon Hudon
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.fin2
import Mathlib.logic.function.basic
import Mathlib.tactic.basic
import Mathlib.PostPort

universes u_1 u u_2 u_3 u_4

namespace Mathlib

/-!

# Tuples of types, and their categorical structure.

## Features

* `typevec n` - n-tuples of types
* `α ⟹ β`    - n-tuples of maps
* `f ⊚ g`     - composition

Also, support functions for operating with n-tuples of types, such as:

* `append1 α β`    - append type `β` to n-tuple `α` to obtain an (n+1)-tuple
* `drop α`         - drops the last element of an (n+1)-tuple
* `last α`         - returns the last element of an (n+1)-tuple
* `append_fun f g` - appends a function g to an n-tuple of functions
* `drop_fun f`     - drops the last function from an n+1-tuple
* `last_fun f`     - returns the last function of a tuple.

Since e.g. `append1 α.drop α.last` is propositionally equal to `α` but not definitionally equal
to it, we need support functions and lemmas to mediate between constructions.
-/

/--
n-tuples of types, as a category
-/
def typevec (n : ℕ) :=
  fin2 n → Type u_1

protected instance typevec.inhabited {n : ℕ} : Inhabited (typevec n) :=
  { default := fun (_x : fin2 n) => PUnit }

namespace typevec

/-- arrow in the category of `typevec` -/
def arrow {n : ℕ} (α : typevec n) (β : typevec n) :=
  (i : fin2 n) → α i → β i

protected instance arrow.inhabited {n : ℕ} (α : typevec n) (β : typevec n) [(i : fin2 n) → Inhabited (β i)] : Inhabited (arrow α β) :=
  { default := fun (_x : fin2 n) (_x_1 : α _x) => Inhabited.default }

/-- identity of arrow composition -/
def id {n : ℕ} {α : typevec n} : arrow α α :=
  fun (i : fin2 n) (x : α i) => x

/-- arrow composition in the category of `typevec` -/
def comp {n : ℕ} {α : typevec n} {β : typevec n} {γ : typevec n} (g : arrow β γ) (f : arrow α β) : arrow α γ :=
  fun (i : fin2 n) (x : α i) => g i (f i x)

@[simp] theorem id_comp {n : ℕ} {α : typevec n} {β : typevec n} (f : arrow α β) : comp id f = f :=
  rfl

@[simp] theorem comp_id {n : ℕ} {α : typevec n} {β : typevec n} (f : arrow α β) : comp f id = f :=
  rfl

theorem comp_assoc {n : ℕ} {α : typevec n} {β : typevec n} {γ : typevec n} {δ : typevec n} (h : arrow γ δ) (g : arrow β γ) (f : arrow α β) : comp (comp h g) f = comp h (comp g f) :=
  rfl

/--
Support for extending a typevec by one element.
-/
def append1 {n : ℕ} (α : typevec n) (β : Type u_1) : typevec (n + 1) :=
  sorry

infixl:67 " ::: " => Mathlib.typevec.append1

/-- retain only a `n-length` prefix of the argument -/
def drop {n : ℕ} (α : typevec (n + 1)) : typevec n :=
  fun (i : fin2 n) => α (fin2.fs i)

/-- take the last value of a `(n+1)-length` vector -/
def last {n : ℕ} (α : typevec (n + 1)) :=
  α fin2.fz

protected instance last.inhabited {n : ℕ} (α : typevec (n + 1)) [Inhabited (α fin2.fz)] : Inhabited (last α) :=
  { default := Inhabited.default }

theorem drop_append1 {n : ℕ} {α : typevec n} {β : Type u_1} {i : fin2 n} : drop (α ::: β) i = α i :=
  rfl

@[simp] theorem drop_append1' {n : ℕ} {α : typevec n} {β : Type u_1} : drop (α ::: β) = α :=
  funext fun (x : fin2 n) => drop_append1

theorem last_append1 {n : ℕ} {α : typevec n} {β : Type u_1} : last (α ::: β) = β :=
  rfl

@[simp] theorem append1_drop_last {n : ℕ} (α : typevec (n + 1)) : drop α ::: last α = α := sorry

/-- cases on `(n+1)-length` vectors -/
def append1_cases {n : ℕ} {C : typevec (n + 1) → Sort u} (H : (α : typevec n) → (β : Type u_1) → C (α ::: β)) (γ : typevec (n + 1)) : C γ :=
  eq.mpr sorry (H (drop γ) (last γ))

@[simp] theorem append1_cases_append1 {n : ℕ} {C : typevec (n + 1) → Sort u} (H : (α : typevec n) → (β : Type u_1) → C (α ::: β)) (α : typevec n) (β : Type u_1) : append1_cases H (α ::: β) = H α β :=
  rfl

/-- append an arrow and a function for arbitrary source and target
type vectors -/
def split_fun {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} (f : arrow (drop α) (drop α')) (g : last α → last α') : arrow α α' :=
  sorry

/-- append an arrow and a function as well as their respective source
and target types / typevecs -/
def append_fun {n : ℕ} {α : typevec n} {α' : typevec n} {β : Type u_1} {β' : Type u_2} (f : arrow α α') (g : β → β') : arrow (α ::: β) (α' ::: β') :=
  split_fun f g

infixl:67 " ::: " => Mathlib.typevec.append_fun

/-- split off the prefix of an arrow -/
def drop_fun {n : ℕ} {α : typevec (n + 1)} {β : typevec (n + 1)} (f : arrow α β) : arrow (drop α) (drop β) :=
  fun (i : fin2 n) => f (fin2.fs i)

/-- split off the last function of an arrow -/
def last_fun {n : ℕ} {α : typevec (n + 1)} {β : typevec (n + 1)} (f : arrow α β) : last α → last β :=
  f fin2.fz

/-- arrow in the category of `0-length` vectors -/
def nil_fun {α : typevec 0} {β : typevec 0} : arrow α β :=
  fun (i : fin2 0) => fin2.elim0 i

theorem eq_of_drop_last_eq {n : ℕ} {α : typevec (n + 1)} {β : typevec (n + 1)} {f : arrow α β} {g : arrow α β} (h₀ : drop_fun f = drop_fun g) (h₁ : last_fun f = last_fun g) : f = g := sorry

@[simp] theorem drop_fun_split_fun {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} (f : arrow (drop α) (drop α')) (g : last α → last α') : drop_fun (split_fun f g) = f :=
  rfl

/-- turn an equality into an arrow -/
def arrow.mp {n : ℕ} {α : typevec n} {β : typevec n} (h : α = β) : arrow α β :=
  sorry

/-- turn an equality into an arrow, with reverse direction -/
def arrow.mpr {n : ℕ} {α : typevec n} {β : typevec n} (h : α = β) : arrow β α :=
  sorry

/-- decompose a vector into its prefix appended with its last element -/
def to_append1_drop_last {n : ℕ} {α : typevec (n + 1)} : arrow α (drop α ::: last α) :=
  arrow.mpr (append1_drop_last α)

/-- stitch two bits of a vector back together -/
def from_append1_drop_last {n : ℕ} {α : typevec (n + 1)} : arrow (drop α ::: last α) α :=
  arrow.mp (append1_drop_last α)

@[simp] theorem last_fun_split_fun {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} (f : arrow (drop α) (drop α')) (g : last α → last α') : last_fun (split_fun f g) = g :=
  rfl

@[simp] theorem drop_fun_append_fun {n : ℕ} {α : typevec n} {α' : typevec n} {β : Type u_1} {β' : Type u_2} (f : arrow α α') (g : β → β') : drop_fun (f ::: g) = f :=
  rfl

@[simp] theorem last_fun_append_fun {n : ℕ} {α : typevec n} {α' : typevec n} {β : Type u_1} {β' : Type u_2} (f : arrow α α') (g : β → β') : last_fun (f ::: g) = g :=
  rfl

theorem split_drop_fun_last_fun {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} (f : arrow α α') : split_fun (drop_fun f) (last_fun f) = f :=
  eq_of_drop_last_eq rfl rfl

theorem split_fun_inj {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} {f : arrow (drop α) (drop α')} {f' : arrow (drop α) (drop α')} {g : last α → last α'} {g' : last α → last α'} (H : split_fun f g = split_fun f' g') : f = f' ∧ g = g' := sorry

theorem append_fun_inj {n : ℕ} {α : typevec n} {α' : typevec n} {β : Type u_1} {β' : Type u_2} {f : arrow α α'} {f' : arrow α α'} {g : β → β'} {g' : β → β'} : f ::: g = f' ::: g' → f = f' ∧ g = g' :=
  split_fun_inj

theorem split_fun_comp {n : ℕ} {α₀ : typevec (n + 1)} {α₁ : typevec (n + 1)} {α₂ : typevec (n + 1)} (f₀ : arrow (drop α₀) (drop α₁)) (f₁ : arrow (drop α₁) (drop α₂)) (g₀ : last α₀ → last α₁) (g₁ : last α₁ → last α₂) : split_fun (comp f₁ f₀) (g₁ ∘ g₀) = comp (split_fun f₁ g₁) (split_fun f₀ g₀) :=
  eq_of_drop_last_eq rfl rfl

theorem append_fun_comp_split_fun {n : ℕ} {α : typevec n} {γ : typevec n} {β : Type u_1} {δ : Type u_2} {ε : typevec (n + 1)} (f₀ : arrow (drop ε) α) (f₁ : arrow α γ) (g₀ : last ε → β) (g₁ : β → δ) : comp (f₁ ::: g₁) (split_fun f₀ g₀) = split_fun (comp f₁ f₀) (g₁ ∘ g₀) :=
  Eq.symm (split_fun_comp f₀ f₁ g₀ g₁)

theorem append_fun_comp {n : ℕ} {α₀ : typevec n} {α₁ : typevec n} {α₂ : typevec n} {β₀ : Type u_1} {β₁ : Type u_2} {β₂ : Type u_3} (f₀ : arrow α₀ α₁) (f₁ : arrow α₁ α₂) (g₀ : β₀ → β₁) (g₁ : β₁ → β₂) : comp f₁ f₀ ::: g₁ ∘ g₀ = comp (f₁ ::: g₁) (f₀ ::: g₀) :=
  eq_of_drop_last_eq rfl rfl

theorem append_fun_comp' {n : ℕ} {α₀ : typevec n} {α₁ : typevec n} {α₂ : typevec n} {β₀ : Type u_1} {β₁ : Type u_2} {β₂ : Type u_3} (f₀ : arrow α₀ α₁) (f₁ : arrow α₁ α₂) (g₀ : β₀ → β₁) (g₁ : β₁ → β₂) : comp (f₁ ::: g₁) (f₀ ::: g₀) = comp f₁ f₀ ::: g₁ ∘ g₀ :=
  eq_of_drop_last_eq rfl rfl

theorem nil_fun_comp {α₀ : typevec 0} (f₀ : arrow α₀ fin2.elim0) : comp nil_fun f₀ = f₀ :=
  funext fun (x : fin2 0) => fin2.elim0 x

theorem append_fun_comp_id {n : ℕ} {α : typevec n} {β₀ : Type u_1} {β₁ : Type u_1} {β₂ : Type u_1} (g₀ : β₀ → β₁) (g₁ : β₁ → β₂) : id ::: g₁ ∘ g₀ = comp (id ::: g₁) (id ::: g₀) :=
  eq_of_drop_last_eq rfl rfl

@[simp] theorem drop_fun_comp {n : ℕ} {α₀ : typevec (n + 1)} {α₁ : typevec (n + 1)} {α₂ : typevec (n + 1)} (f₀ : arrow α₀ α₁) (f₁ : arrow α₁ α₂) : drop_fun (comp f₁ f₀) = comp (drop_fun f₁) (drop_fun f₀) :=
  rfl

@[simp] theorem last_fun_comp {n : ℕ} {α₀ : typevec (n + 1)} {α₁ : typevec (n + 1)} {α₂ : typevec (n + 1)} (f₀ : arrow α₀ α₁) (f₁ : arrow α₁ α₂) : last_fun (comp f₁ f₀) = last_fun f₁ ∘ last_fun f₀ :=
  rfl

theorem append_fun_aux {n : ℕ} {α : typevec n} {α' : typevec n} {β : Type u_1} {β' : Type u_2} (f : arrow (α ::: β) (α' ::: β')) : drop_fun f ::: last_fun f = f :=
  eq_of_drop_last_eq rfl rfl

theorem append_fun_id_id {n : ℕ} {α : typevec n} {β : Type u_1} : id ::: id = id :=
  eq_of_drop_last_eq rfl rfl

protected instance subsingleton0 : subsingleton (typevec 0) :=
  subsingleton.intro fun (a b : typevec 0) => funext fun (a_1 : fin2 0) => fin2.elim0 a_1

/-- cases distinction for 0-length type vector -/
protected def cases_nil {β : typevec 0 → Sort u_2} (f : β fin2.elim0) (v : typevec 0) : β v :=
  cast sorry f

/-- cases distinction for (n+1)-length type vector -/
protected def cases_cons (n : ℕ) {β : typevec (n + 1) → Sort u_2} (f : (t : Type u_1) → (v : typevec n) → β (v ::: t)) (v : typevec (n + 1)) : β v :=
  cast sorry (f (last v) (drop v))

protected theorem cases_nil_append1 {β : typevec 0 → Sort u_2} (f : β fin2.elim0) : typevec.cases_nil f fin2.elim0 = f :=
  rfl

protected theorem cases_cons_append1 (n : ℕ) {β : typevec (n + 1) → Sort u_2} (f : (t : Type u_1) → (v : typevec n) → β (v ::: t)) (v : typevec n) (α : Type u_1) : typevec.cases_cons n f (v ::: α) = f α v :=
  rfl

/-- cases distinction for an arrow in the category of 0-length type vectors -/
def typevec_cases_nil₃ {β : (v : typevec 0) → (v' : typevec 0) → arrow v v' → Sort u_3} (f : β fin2.elim0 fin2.elim0 nil_fun) (v : typevec 0) (v' : typevec 0) (fs : arrow v v') : β v v' fs :=
  cast sorry f

/-- cases distinction for an arrow in the category of (n+1)-length type vectors -/
def typevec_cases_cons₃ (n : ℕ) {β : (v : typevec (n + 1)) → (v' : typevec (n + 1)) → arrow v v' → Sort u_3} (F : (t : Type u_1) →
  (t' : Type u_2) →
    (f : t → t') → (v : typevec n) → (v' : typevec n) → (fs : arrow v v') → β (v ::: t) (v' ::: t') (fs ::: f)) (v : typevec (n + 1)) (v' : typevec (n + 1)) (fs : arrow v v') : β v v' fs :=
  eq.mpr sorry
    (eq.mpr sorry
      fun (fs : arrow (drop v ::: last v) (drop v' ::: last v')) =>
        eq.mpr sorry (F (last v) (last v') (last_fun fs) (drop v) (drop v') (drop_fun fs)))

/-- specialized cases distinction for an arrow in the category of 0-length type vectors -/
def typevec_cases_nil₂ {β : arrow fin2.elim0 fin2.elim0 → Sort u_3} (f : β nil_fun) : (f : arrow fin2.elim0 fin2.elim0) → β f :=
  fun (g : arrow fin2.elim0 fin2.elim0) => eq.mpr sorry f

/-- specialized cases distinction for an arrow in the category of (n+1)-length type vectors -/
def typevec_cases_cons₂ (n : ℕ) (t : Type u_1) (t' : Type u_2) (v : typevec n) (v' : typevec n) {β : arrow (v ::: t) (v' ::: t') → Sort u_3} (F : (f : t → t') → (fs : arrow v v') → β (fs ::: f)) (fs : arrow (v ::: t) (v' ::: t')) : β fs :=
  eq.mpr sorry (F (last_fun fs) (drop_fun fs))

theorem typevec_cases_nil₂_append_fun {β : arrow fin2.elim0 fin2.elim0 → Sort u_3} (f : β nil_fun) : typevec_cases_nil₂ f nil_fun = f :=
  rfl

theorem typevec_cases_cons₂_append_fun (n : ℕ) (t : Type u_1) (t' : Type u_2) (v : typevec n) (v' : typevec n) {β : arrow (v ::: t) (v' ::: t') → Sort u_3} (F : (f : t → t') → (fs : arrow v v') → β (fs ::: f)) (f : t → t') (fs : arrow v v') : typevec_cases_cons₂ n t t' v v' F (fs ::: f) = F f fs :=
  rfl

/- for lifting predicates and relations -/

/-- `pred_last α p x` predicates `p` of the last element of `x : α.append1 β`. -/
def pred_last {n : ℕ} (α : typevec n) {β : Type u_1} (p : β → Prop) {i : fin2 (n + 1)} : append1 α β i → Prop :=
  sorry

/-- `rel_last α r x y` says that `p` the last elements of `x y : α.append1 β` are related by `r` and
all the other elements are equal. -/
def rel_last {n : ℕ} (α : typevec n) {β : Type u_1} {γ : Type u_1} (r : β → γ → Prop) {i : fin2 (n + 1)} : append1 α β i → append1 α γ i → Prop :=
  sorry

/-- `repeat n t` is a `n-length` type vector that contains `n` occurences of `t` -/
def repeat (n : ℕ) (t : Type u_1) : typevec n :=
  sorry

/-- `prod α β` is the pointwise product of the components of `α` and `β` -/
def prod {n : ℕ} (α : typevec n) (β : typevec n) : typevec n :=
  sorry

/-- `const x α` is an arrow that ignores its source and constructs a `typevec` that
contains nothing but `x` -/
protected def const {β : Type u_1} (x : β) {n : ℕ} (α : typevec n) : arrow α (repeat n β) :=
  sorry

/-- vector of equality on a product of vectors -/
def repeat_eq {n : ℕ} (α : typevec n) : arrow (prod α α) (repeat n Prop) :=
  sorry

theorem const_append1 {β : Type u_1} {γ : Type u_2} (x : γ) {n : ℕ} (α : typevec n) : typevec.const x (α ::: β) = typevec.const x α ::: fun (_x : β) => x := sorry

theorem eq_nil_fun {α : typevec 0} {β : typevec 0} (f : arrow α β) : f = nil_fun := sorry

theorem id_eq_nil_fun {α : typevec 0} : id = nil_fun := sorry

theorem const_nil {β : Type u_1} (x : β) (α : typevec 0) : typevec.const x α = nil_fun := sorry

theorem repeat_eq_append1 {β : Type u_1} {n : ℕ} (α : typevec n) : repeat_eq (α ::: β) = split_fun (repeat_eq α) (function.uncurry Eq) := sorry

theorem repeat_eq_nil (α : typevec 0) : repeat_eq α = nil_fun := sorry

/-- predicate on a type vector to constrain only the last object -/
def pred_last' {n : ℕ} (α : typevec n) {β : Type u_1} (p : β → Prop) : arrow (α ::: β) (repeat (n + 1) Prop) :=
  split_fun (typevec.const True α) p

/-- predicate on the product of two type vectors to constrain only their last object -/
def rel_last' {n : ℕ} (α : typevec n) {β : Type u_1} (p : β → β → Prop) : arrow (prod (α ::: β) (α ::: β)) (repeat (n + 1) Prop) :=
  split_fun (repeat_eq α) (function.uncurry p)

/-- given `F : typevec.{u} (n+1) → Type u`, `curry F : Type u → typevec.{u} → Type u`,
i.e. its first argument can be fed in separately from the rest of the vector of arguments -/
def curry {n : ℕ} (F : typevec (n + 1) → Type u_1) (α : Type u) (β : typevec n) :=
  F (β ::: α)

protected instance curry.inhabited {n : ℕ} (F : typevec (n + 1) → Type u_1) (α : Type u) (β : typevec n) [I : Inhabited (F (β ::: α))] : Inhabited (curry F α β) :=
  I

/-- arrow to remove one element of a `repeat` vector -/
def drop_repeat (α : Type u_1) {n : ℕ} : arrow (drop (repeat (Nat.succ n) α)) (repeat n α) :=
  sorry

/-- projection for a repeat vector -/
def of_repeat {α : Type u_1} {n : ℕ} {i : fin2 n} : repeat n α i → α :=
  sorry

theorem const_iff_true {n : ℕ} {α : typevec n} {i : fin2 n} {x : α i} {p : Prop} : of_repeat (typevec.const p α i x) ↔ p := sorry

-- variables  {F : typevec.{u} n → Type*} [mvfunctor F]

/-- left projection of a `prod` vector -/
def prod.fst {n : ℕ} {α : typevec n} {β : typevec n} : arrow (prod α β) α :=
  sorry

/-- right projection of a `prod` vector -/
def prod.snd {n : ℕ} {α : typevec n} {β : typevec n} : arrow (prod α β) β :=
  sorry

/-- introduce a product where both components are the same -/
def prod.diag {n : ℕ} {α : typevec n} : arrow α (prod α α) :=
  sorry

/-- constructor for `prod` -/
def prod.mk {n : ℕ} {α : typevec n} {β : typevec n} (i : fin2 n) : α i → β i → prod α β i :=
  sorry

@[simp] theorem prod_fst_mk {n : ℕ} {α : typevec n} {β : typevec n} (i : fin2 n) (a : α i) (b : β i) : prod.fst i (prod.mk i a b) = a := sorry

@[simp] theorem prod_snd_mk {n : ℕ} {α : typevec n} {β : typevec n} (i : fin2 n) (a : α i) (b : β i) : prod.snd i (prod.mk i a b) = b := sorry

/-- `prod` is functorial -/
protected def prod.map {n : ℕ} {α : typevec n} {α' : typevec n} {β : typevec n} {β' : typevec n} : arrow α β → arrow α' β' → arrow (prod α α') (prod β β') :=
  sorry

theorem fst_prod_mk {n : ℕ} {α : typevec n} {α' : typevec n} {β : typevec n} {β' : typevec n} (f : arrow α β) (g : arrow α' β') : comp prod.fst (prod.map f g) = comp f prod.fst := sorry

theorem snd_prod_mk {n : ℕ} {α : typevec n} {α' : typevec n} {β : typevec n} {β' : typevec n} (f : arrow α β) (g : arrow α' β') : comp prod.snd (prod.map f g) = comp g prod.snd := sorry

theorem fst_diag {n : ℕ} {α : typevec n} : comp prod.fst prod.diag = id := sorry

theorem snd_diag {n : ℕ} {α : typevec n} : comp prod.snd prod.diag = id := sorry

theorem repeat_eq_iff_eq {n : ℕ} {α : typevec n} {i : fin2 n} {x : α i} {y : α i} : of_repeat (repeat_eq α i (prod.mk i x y)) ↔ x = y := sorry

/-- given a predicate vector `p` over vector `α`, `subtype_ p` is the type of vectors
that contain an `α` that satisfies `p` -/
def subtype_ {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : typevec n :=
  sorry

/-- projection on `subtype_` -/
def subtype_val {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : arrow (subtype_ p) α :=
  sorry

/-- arrow that rearranges the type of `subtype_` to turn a subtype of vector into
a vector of subtypes -/
def to_subtype {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : arrow (fun (i : fin2 n) => Subtype fun (x : α i) => of_repeat (p i x)) (subtype_ p) :=
  sorry

/-- arrow that rearranges the type of `subtype_` to turn a vector of subtypes
into a subtype of vector -/
def of_subtype {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : arrow (subtype_ p) fun (i : fin2 n) => Subtype fun (x : α i) => of_repeat (p i x) :=
  sorry

/-- similar to `to_subtype` adapted to relations (i.e. predicate on product) -/
def to_subtype' {n : ℕ} {α : typevec n} (p : arrow (prod α α) (repeat n Prop)) : arrow (fun (i : fin2 n) => Subtype fun (x : α i × α i) => of_repeat (p i (prod.mk i (prod.fst x) (prod.snd x))))
  (subtype_ p) :=
  sorry

/-- similar to `of_subtype` adapted to relations (i.e. predicate on product) -/
def of_subtype' {n : ℕ} {α : typevec n} (p : arrow (prod α α) (repeat n Prop)) : arrow (subtype_ p)
  fun (i : fin2 n) => Subtype fun (x : α i × α i) => of_repeat (p i (prod.mk i (prod.fst x) (prod.snd x))) :=
  sorry

/-- similar to `diag` but the target vector is a `subtype_`
guaranteeing the equality of the components -/
def diag_sub {n : ℕ} {α : typevec n} : arrow α (subtype_ (repeat_eq α)) :=
  sorry

theorem subtype_val_nil {α : typevec 0} (ps : arrow α (repeat 0 Prop)) : subtype_val ps = nil_fun := sorry

theorem diag_sub_val {n : ℕ} {α : typevec n} : comp (subtype_val (repeat_eq α)) diag_sub = prod.diag := sorry

theorem prod_id {n : ℕ} {α : typevec n} {β : typevec n} : prod.map id id = id := sorry

theorem append_prod_append_fun {n : ℕ} {α : typevec n} {α' : typevec n} {β : typevec n} {β' : typevec n} {φ : Type u} {φ' : Type u} {ψ : Type u} {ψ' : Type u} {f₀ : arrow α α'} {g₀ : arrow β β'} {f₁ : φ → φ'} {g₁ : ψ → ψ'} : prod.map f₀ g₀ ::: prod.map f₁ g₁ = prod.map (f₀ ::: f₁) (g₀ ::: g₁) := sorry

@[simp] theorem drop_fun_diag {n : ℕ} {α : typevec (n + 1)} : drop_fun prod.diag = prod.diag := sorry

@[simp] theorem drop_fun_subtype_val {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : drop_fun (subtype_val p) = subtype_val (drop_fun p) :=
  rfl

@[simp] theorem last_fun_subtype_val {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : last_fun (subtype_val p) = subtype.val :=
  rfl

@[simp] theorem drop_fun_to_subtype {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : drop_fun (to_subtype p) = to_subtype fun (i : fin2 n) (x : α (fin2.fs i)) => p (fin2.fs i) x := sorry

@[simp] theorem last_fun_to_subtype {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : last_fun (to_subtype p) = id := sorry

@[simp] theorem drop_fun_of_subtype {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : drop_fun (of_subtype p) = of_subtype (drop_fun p) := sorry

@[simp] theorem last_fun_of_subtype {n : ℕ} {α : typevec (n + 1)} (p : arrow α (repeat (n + 1) Prop)) : last_fun (of_subtype p) = id := sorry

@[simp] theorem drop_fun_rel_last {n : ℕ} {α : typevec n} {β : Type u_1} (R : β → β → Prop) : drop_fun (rel_last' α R) = repeat_eq α :=
  rfl

@[simp] theorem drop_fun_prod {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} {β : typevec (n + 1)} {β' : typevec (n + 1)} (f : arrow α β) (f' : arrow α' β') : drop_fun (prod.map f f') = prod.map (drop_fun f) (drop_fun f') := sorry

@[simp] theorem last_fun_prod {n : ℕ} {α : typevec (n + 1)} {α' : typevec (n + 1)} {β : typevec (n + 1)} {β' : typevec (n + 1)} (f : arrow α β) (f' : arrow α' β') : last_fun (prod.map f f') = prod.map (last_fun f) (last_fun f') := sorry

@[simp] theorem drop_fun_from_append1_drop_last {n : ℕ} {α : typevec (n + 1)} : drop_fun from_append1_drop_last = id :=
  rfl

@[simp] theorem last_fun_from_append1_drop_last {n : ℕ} {α : typevec (n + 1)} : last_fun from_append1_drop_last = id :=
  rfl

@[simp] theorem drop_fun_id {n : ℕ} {α : typevec (n + 1)} : drop_fun id = id :=
  rfl

@[simp] theorem prod_map_id {n : ℕ} {α : typevec n} {β : typevec n} : prod.map id id = id := sorry

@[simp] theorem subtype_val_diag_sub {n : ℕ} {α : typevec n} : comp (subtype_val (repeat_eq α)) diag_sub = prod.diag := sorry

@[simp] theorem to_subtype_of_subtype {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : comp (to_subtype p) (of_subtype p) = id := sorry

@[simp] theorem subtype_val_to_subtype {n : ℕ} {α : typevec n} (p : arrow α (repeat n Prop)) : comp (subtype_val p) (to_subtype p) = fun (_x : fin2 n) => subtype.val := sorry

@[simp] theorem to_subtype_of_subtype_assoc {n : ℕ} {α : typevec n} {β : typevec n} (p : arrow α (repeat n Prop)) (f : arrow β (subtype_ p)) : comp (to_subtype p) (comp (of_subtype fun (i : fin2 n) (x : α i) => p i x) f) = f := sorry

@[simp] theorem to_subtype'_of_subtype' {n : ℕ} {α : typevec n} (r : arrow (prod α α) (repeat n Prop)) : comp (to_subtype' r) (of_subtype' r) = id := sorry

theorem subtype_val_to_subtype' {n : ℕ} {α : typevec n} (r : arrow (prod α α) (repeat n Prop)) : comp (subtype_val r) (to_subtype' r) =
  fun (i : fin2 n) (x : Subtype fun (x : α i × α i) => of_repeat (r i (prod.mk i (prod.fst x) (prod.snd x)))) =>
    prod.mk i (prod.fst (subtype.val x)) (prod.snd (subtype.val x)) := sorry


/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.mean_inequalities
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# `L^p` distance on finite products of metric spaces
Given finitely many metric spaces, one can put the max distance on their product, but there is also
a whole family of natural distances, indexed by a real parameter `p ∈ [1, ∞)`, that also induce
the product topology. We define them in this file. The distance on `Π i, α i` is given by
$$
d(x, y) = \left(\sum d(x_i, y_i)^p\right)^{1/p}.
$$

We give instances of this construction for emetric spaces, metric spaces, normed groups and normed
spaces.

To avoid conflicting instances, all these are defined on a copy of the original Pi type, named
`pi_Lp p hp α`, where `hp : 1 ≤ p`. This assumption is included in the definition of the type
to make sure that it is always available to typeclass inference to construct the instances.

We ensure that the topology and uniform structure on `pi_Lp p hp α` are (defeq to) the product
topology and product uniformity, to be able to use freely continuity statements for the coordinate
functions, for instance.

## Implementation notes

We only deal with the `L^p` distance on a product of finitely many metric spaces, which may be
distinct. A closely related construction is the `L^p` norm on the space of
functions from a measure space to a normed space, where the norm is
$$
\left(\int ∥f (x)∥^p dμ\right)^{1/p}.
$$
However, the topology induced by this construction is not the product topology, this only
defines a seminorm (as almost everywhere zero functions have zero `L^p` norm), and some functions
have infinite `L^p` norm. All these subtleties are not present in the case of finitely many
metric spaces (which corresponds to the basis which is a finite space with the counting measure),
hence it is worth devoting a file to this specific case which is particularly well behaved.
The general case is not yet formalized in mathlib.

To prove that the topology (and the uniform structure) on a finite product with the `L^p` distance
are the same as those coming from the `L^∞` distance, we could argue that the `L^p` and `L^∞` norms
are equivalent on `ℝ^n` for abstract (norm equivalence) reasons. Instead, we give a more explicit
(easy) proof which provides a comparison between these two norms with explicit constants.
-/

/-- A copy of a Pi type, on which we will put the `L^p` distance. Since the Pi type itself is
already endowed with the `L^∞` distance, we need the type synonym to avoid confusing typeclass
resolution. Also, we let it depend on `p`, to get a whole family of type on which we can put
different distances, and we provide the assumption `hp` in the definition, to make it available
to typeclass resolution when it looks for a distance on `pi_Lp p hp α`. -/
def pi_Lp {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) := (i : ι) → α i

protected instance pi_Lp.inhabited {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → Inhabited (α i)] : Inhabited (pi_Lp p hp α) :=
  { default := fun (i : ι) => Inhabited.default }

namespace pi_Lp

/-- Canonical bijection between `pi_Lp p hp α` and the original Pi type. We introduce it to be able
to compare the `L^p` and `L^∞` distances through it. -/
protected def equiv {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) :
    pi_Lp p hp α ≃ ((i : ι) → α i) :=
  equiv.refl (pi_Lp p hp α)

/-!
### The uniformity on finite `L^p` products is the product uniformity

In this section, we put the `L^p` edistance on `pi_Lp p hp α`, and we check that the uniformity
coming from this edistance coincides with the product uniformity, by showing that the canonical
map to the Pi type (with the `L^∞` distance) is a uniform embedding, as it is both Lipschitz and
antiLipschitz.

We only register this emetric space structure as a temporary instance, as the true instance (to be
registered later) will have as uniformity exactly the product uniformity, instead of the one coming
from the edistance (which is equal to it, but not defeq). See Note [forgetful inheritance]
explaining why having definitionally the right uniformity is often important.
-/

/-- Endowing the space `pi_Lp p hp α` with the `L^p` edistance. This definition is not satisfactory,
as it does not register the fact that the topology and the uniform structure coincide with the
product one. Therefore, we do not register it as an instance. Using this as a temporary emetric
space instance, we will show that the uniform structure is equal (but not defeq) to the product one,
and then register an instance in which we replace the uniform structure by the product one using
this emetric space and `emetric_space.replace_uniformity`. -/
def emetric_aux {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → emetric_space (α i)] [fintype ι] : emetric_space (pi_Lp p hp α) :=
  (fun (pos : 0 < p) =>
      emetric_space.mk sorry sorry sorry sorry
        (uniform_space_of_edist
          (fun (f g : pi_Lp p hp α) =>
            (finset.sum finset.univ fun (i : ι) => edist (f i) (g i) ^ p) ^ (1 / p))
          sorry sorry sorry))
    sorry

theorem lipschitz_with_equiv {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → emetric_space (α i)] [fintype ι] : lipschitz_with 1 ⇑(pi_Lp.equiv p hp α) :=
  sorry

theorem antilipschitz_with_equiv {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → emetric_space (α i)] [fintype ι] :
    antilipschitz_with (↑(fintype.card ι) ^ (1 / p)) ⇑(pi_Lp.equiv p hp α) :=
  sorry

theorem aux_uniformity_eq {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → emetric_space (α i)] [fintype ι] :
    uniformity (pi_Lp p hp α) = uniformity ((i : ι) → α i) :=
  sorry

/-! ### Instances on finite `L^p` products -/

protected instance uniform_space {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2)
    [(i : ι) → uniform_space (α i)] : uniform_space (pi_Lp p hp α) :=
  Pi.uniform_space fun (i : ι) => α i

/-- emetric space instance on the product of finitely many emetric spaces, using the `L^p`
edistance, and having as uniformity the product uniformity. -/
protected instance emetric_space {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) [fintype ι]
    [(i : ι) → emetric_space (α i)] : emetric_space (pi_Lp p hp α) :=
  emetric_space.replace_uniformity (emetric_aux p hp α) sorry

protected theorem edist {ι : Type u_1} [fintype ι] {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2}
    [(i : ι) → emetric_space (α i)] (x : pi_Lp p hp α) (y : pi_Lp p hp α) :
    edist x y = (finset.sum finset.univ fun (i : ι) => edist (x i) (y i) ^ p) ^ (1 / p) :=
  rfl

/-- metric space instance on the product of finitely many metric spaces, using the `L^p` distance,
and having as uniformity the product uniformity. -/
protected instance metric_space {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) [fintype ι]
    [(i : ι) → metric_space (α i)] : metric_space (pi_Lp p hp α) :=
  emetric_space.to_metric_space_of_dist
    (fun (f g : pi_Lp p hp α) =>
      (finset.sum finset.univ fun (i : ι) => dist (f i) (g i) ^ p) ^ (1 / p))
    sorry sorry

protected theorem dist {ι : Type u_1} [fintype ι] {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2}
    [(i : ι) → metric_space (α i)] (x : pi_Lp p hp α) (y : pi_Lp p hp α) :
    dist x y = (finset.sum finset.univ fun (i : ι) => dist (x i) (y i) ^ p) ^ (1 / p) :=
  rfl

/-- normed group instance on the product of finitely many normed groups, using the `L^p` norm. -/
protected instance normed_group {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) [fintype ι]
    [(i : ι) → normed_group (α i)] : normed_group (pi_Lp p hp α) :=
  normed_group.mk sorry

theorem norm_eq {ι : Type u_1} [fintype ι] {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2}
    [(i : ι) → normed_group (α i)] (f : pi_Lp p hp α) :
    norm f = (finset.sum finset.univ fun (i : ι) => norm (f i) ^ p) ^ (1 / p) :=
  rfl

/-- The product of finitely many normed spaces is a normed space, with the `L^p` norm. -/
protected instance normed_space {ι : Type u_1} (p : ℝ) (hp : 1 ≤ p) (α : ι → Type u_2) [fintype ι]
    (𝕜 : Type u_3) [normed_field 𝕜] [(i : ι) → normed_group (α i)]
    [(i : ι) → normed_space 𝕜 (α i)] : normed_space 𝕜 (pi_Lp p hp α) :=
  normed_space.mk sorry

/- Register simplification lemmas for the applications of `pi_Lp` elements, as the usual lemmas
for Pi types will not trigger. -/

@[simp] theorem add_apply {ι : Type u_1} {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2} [fintype ι]
    [(i : ι) → normed_group (α i)] (x : pi_Lp p hp α) (y : pi_Lp p hp α) (i : ι) :
    Add.add x y i = x i + y i :=
  rfl

@[simp] theorem sub_apply {ι : Type u_1} {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2} [fintype ι]
    [(i : ι) → normed_group (α i)] (x : pi_Lp p hp α) (y : pi_Lp p hp α) (i : ι) :
    Sub.sub x y i = x i - y i :=
  rfl

@[simp] theorem smul_apply {ι : Type u_1} {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2} [fintype ι]
    {𝕜 : Type u_3} [normed_field 𝕜] [(i : ι) → normed_group (α i)] [(i : ι) → normed_space 𝕜 (α i)]
    (c : 𝕜) (x : pi_Lp p hp α) (i : ι) : has_scalar.smul c x i = c • x i :=
  rfl

@[simp] theorem neg_apply {ι : Type u_1} {p : ℝ} {hp : 1 ≤ p} {α : ι → Type u_2} [fintype ι]
    [(i : ι) → normed_group (α i)] (x : pi_Lp p hp α) (i : ι) : Neg.neg x i = -x i :=
  rfl

end Mathlib