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

! This file was ported from Lean 3 source module data.seq.seq
! 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.Data.List.Basic
import Mathbin.Data.LazyList
import Mathbin.Data.Nat.Basic
import Mathbin.Data.Stream.Init
import Mathbin.Data.Seq.Computation

namespace Stream'

universe u v w

/-
coinductive seq (α : Type u) : Type u
| nil : seq α
| cons : α → seq α → seq α
-/
/-- A stream `s : option α` is a sequence if `s.nth n = none` implies `s.nth (n + 1) = none`.
-/
def IsSeq {α : Type u} (s : Stream' (Option α)) : Prop :=
  ∀ {n : ℕ}, s n = none → s (n + 1) = none
#align stream.is_seq Stream'.IsSeq

/-- `seq α` is the type of possibly infinite lists (referred here as sequences).
  It is encoded as an infinite stream of options such that if `f n = none`, then
  `f m = none` for all `m ≥ n`. -/
def Seq (α : Type u) : Type u :=
  { f : Stream' (Option α) // f.IsSeq }
#align stream.seq Stream'.Seq

/-- `seq1 α` is the type of nonempty sequences. -/
def Seq1 (α) :=
  α × Seq α
#align stream.seq1 Stream'.Seq1

namespace Seq

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

/-- The empty sequence -/
def nil : Seq α :=
  ⟨Stream'.const none, fun n h => rfl⟩
#align stream.seq.nil Stream'.Seq.nil

instance : Inhabited (Seq α) :=
  ⟨nil⟩

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- Prepend an element to a sequence -/
def cons (a : α) (s : Seq α) : Seq α :=
  ⟨some a::s.1, by
    rintro (n | _) h
    · contradiction
    · exact s.2 h⟩
#align stream.seq.cons Stream'.Seq.cons

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem val_cons (s : Seq α) (x : α) : (cons x s).val = some x::s.val :=
  rfl
#align stream.seq.val_cons Stream'.Seq.val_cons

/-- Get the nth element of a sequence (if it exists) -/
def nth : Seq α → ℕ → Option α :=
  Subtype.val
#align stream.seq.nth Stream'.Seq.nth

@[simp]
theorem nth_mk (f hf) : @nth α ⟨f, hf⟩ = f :=
  rfl
#align stream.seq.nth_mk Stream'.Seq.nth_mk

@[simp]
theorem nth_nil (n : ℕ) : (@nil α).get? n = none :=
  rfl
#align stream.seq.nth_nil Stream'.Seq.nth_nil

@[simp]
theorem nth_cons_zero (a : α) (s : Seq α) : (cons a s).get? 0 = some a :=
  rfl
#align stream.seq.nth_cons_zero Stream'.Seq.nth_cons_zero

@[simp]
theorem nth_cons_succ (a : α) (s : Seq α) (n : ℕ) : (cons a s).get? (n + 1) = s.get? n :=
  rfl
#align stream.seq.nth_cons_succ Stream'.Seq.nth_cons_succ

@[ext]
protected theorem ext {s t : Seq α} (h : ∀ n : ℕ, s.get? n = t.get? n) : s = t :=
  Subtype.eq <| funext h
#align stream.seq.ext Stream'.Seq.ext

theorem cons_injective2 : Function.Injective2 (cons : α → Seq α → Seq α) := fun x y s t h =>
  ⟨by rw [← Option.some_inj, ← nth_cons_zero, h, nth_cons_zero],
    Seq.ext fun n => by simp_rw [← nth_cons_succ x s n, h, nth_cons_succ]⟩
#align stream.seq.cons_injective2 Stream'.Seq.cons_injective2

theorem cons_left_injective (s : Seq α) : Function.Injective fun x => cons x s :=
  cons_injective2.left _
#align stream.seq.cons_left_injective Stream'.Seq.cons_left_injective

theorem cons_right_injective (x : α) : Function.Injective (cons x) :=
  cons_injective2.right _
#align stream.seq.cons_right_injective Stream'.Seq.cons_right_injective

/-- A sequence has terminated at position `n` if the value at position `n` equals `none`. -/
def TerminatedAt (s : Seq α) (n : ℕ) : Prop :=
  s.get? n = none
#align stream.seq.terminated_at Stream'.Seq.TerminatedAt

/-- It is decidable whether a sequence terminates at a given position. -/
instance terminatedAtDecidable (s : Seq α) (n : ℕ) : Decidable (s.TerminatedAt n) :=
  decidable_of_iff' (s.get? n).isNone <| by unfold terminated_at <;> cases s.nth n <;> simp
#align stream.seq.terminated_at_decidable Stream'.Seq.terminatedAtDecidable

/-- A sequence terminates if there is some position `n` at which it has terminated. -/
def Terminates (s : Seq α) : Prop :=
  ∃ n : ℕ, s.TerminatedAt n
#align stream.seq.terminates Stream'.Seq.Terminates

theorem not_terminates_iff {s : Seq α} : ¬s.Terminates ↔ ∀ n, (s.get? n).isSome := by
  simp [terminates, terminated_at, ← Ne.def, Option.ne_none_iff_isSome]
#align stream.seq.not_terminates_iff Stream'.Seq.not_terminates_iff

/-- Functorial action of the functor `option (α × _)` -/
@[simp]
def omap (f : β → γ) : Option (α × β) → Option (α × γ)
  | none => none
  | some (a, b) => some (a, f b)
#align stream.seq.omap Stream'.Seq.omap

/-- Get the first element of a sequence -/
def head (s : Seq α) : Option α :=
  nth s 0
#align stream.seq.head Stream'.Seq.head

/-- Get the tail of a sequence (or `nil` if the sequence is `nil`) -/
def tail (s : Seq α) : Seq α :=
  ⟨s.1.tail, fun n => by
    cases' s with f al
    exact al⟩
#align stream.seq.tail Stream'.Seq.tail

/-- member definition for `seq`-/
protected def Mem (a : α) (s : Seq α) :=
  some a ∈ s.1
#align stream.seq.mem Stream'.Seq.Mem

instance : Membership α (Seq α) :=
  ⟨Seq.Mem⟩

theorem le_stable (s : Seq α) {m n} (h : m ≤ n) : s.get? m = none → s.get? n = none :=
  by
  cases' s with f al
  induction' h with n h IH
  exacts[id, fun h2 => al (IH h2)]
#align stream.seq.le_stable Stream'.Seq.le_stable

/-- If a sequence terminated at position `n`, it also terminated at `m ≥ n `. -/
theorem terminated_stable : ∀ (s : Seq α) {m n : ℕ}, m ≤ n → s.TerminatedAt m → s.TerminatedAt n :=
  le_stable
#align stream.seq.terminated_stable Stream'.Seq.terminated_stable

/-- If `s.nth n = some aₙ` for some value `aₙ`, then there is also some value `aₘ` such
that `s.nth = some aₘ` for `m ≤ n`.
-/
theorem ge_stable (s : Seq α) {aₙ : α} {n m : ℕ} (m_le_n : m ≤ n)
    (s_nth_eq_some : s.get? n = some aₙ) : ∃ aₘ : α, s.get? m = some aₘ :=
  have : s.get? n ≠ none := by simp [s_nth_eq_some]
  have : s.get? m ≠ none := mt (s.le_stable m_le_n) this
  Option.ne_none_iff_exists'.mp this
#align stream.seq.ge_stable Stream'.Seq.ge_stable

theorem not_mem_nil (a : α) : a ∉ @nil α := fun ⟨n, (h : some a = none)⟩ => by injection h
#align stream.seq.not_mem_nil Stream'.Seq.not_mem_nil

theorem mem_cons (a : α) : ∀ s : Seq α, a ∈ cons a s
  | ⟨f, al⟩ => Stream'.mem_cons (some a) _
#align stream.seq.mem_cons Stream'.Seq.mem_cons

theorem mem_cons_of_mem (y : α) {a : α} : ∀ {s : Seq α}, a ∈ s → a ∈ cons y s
  | ⟨f, al⟩ => Stream'.mem_cons_of_mem (some y)
#align stream.seq.mem_cons_of_mem Stream'.Seq.mem_cons_of_mem

theorem eq_or_mem_of_mem_cons {a b : α} : ∀ {s : Seq α}, a ∈ cons b s → a = b ∨ a ∈ s
  | ⟨f, al⟩, h => (Stream'.eq_or_mem_of_mem_cons h).imp_left fun h => by injection h
#align stream.seq.eq_or_mem_of_mem_cons Stream'.Seq.eq_or_mem_of_mem_cons

@[simp]
theorem mem_cons_iff {a b : α} {s : Seq α} : a ∈ cons b s ↔ a = b ∨ a ∈ s :=
  ⟨eq_or_mem_of_mem_cons, by rintro (rfl | m) <;> [apply mem_cons, exact mem_cons_of_mem _ m]⟩
#align stream.seq.mem_cons_iff Stream'.Seq.mem_cons_iff

/-- Destructor for a sequence, resulting in either `none` (for `nil`) or
  `some (a, s)` (for `cons a s`). -/
def destruct (s : Seq α) : Option (Seq1 α) :=
  (fun a' => (a', s.tail)) <$> nth s 0
#align stream.seq.destruct Stream'.Seq.destruct

theorem destruct_eq_nil {s : Seq α} : destruct s = none → s = nil :=
  by
  dsimp [destruct]
  induction' f0 : nth s 0 with <;> intro h
  · apply Subtype.eq
    funext n
    induction' n with n IH
    exacts[f0, s.2 IH]
  · contradiction
#align stream.seq.destruct_eq_nil Stream'.Seq.destruct_eq_nil

theorem destruct_eq_cons {s : Seq α} {a s'} : destruct s = some (a, s') → s = cons a s' :=
  by
  dsimp [destruct]
  induction' f0 : nth s 0 with a' <;> intro h
  · contradiction
  · cases' s with f al
    injections _ h1 h2
    rw [← h2]
    apply Subtype.eq
    dsimp [tail, cons]
    rw [h1] at f0
    rw [← f0]
    exact (Stream'.eta f).symm
#align stream.seq.destruct_eq_cons Stream'.Seq.destruct_eq_cons

@[simp]
theorem destruct_nil : destruct (nil : Seq α) = none :=
  rfl
#align stream.seq.destruct_nil Stream'.Seq.destruct_nil

@[simp]
theorem destruct_cons (a : α) : ∀ s, destruct (cons a s) = some (a, s)
  | ⟨f, al⟩ => by
    unfold cons destruct Functor.map
    apply congr_arg fun s => some (a, s)
    apply Subtype.eq; dsimp [tail]; rw [Stream'.tail_cons]
#align stream.seq.destruct_cons Stream'.Seq.destruct_cons

theorem head_eq_destruct (s : Seq α) : head s = Prod.fst <$> destruct s := by
  unfold destruct head <;> cases nth s 0 <;> rfl
#align stream.seq.head_eq_destruct Stream'.Seq.head_eq_destruct

@[simp]
theorem head_nil : head (nil : Seq α) = none :=
  rfl
#align stream.seq.head_nil Stream'.Seq.head_nil

@[simp]
theorem head_cons (a : α) (s) : head (cons a s) = some a := by
  rw [head_eq_destruct, destruct_cons] <;> rfl
#align stream.seq.head_cons Stream'.Seq.head_cons

@[simp]
theorem tail_nil : tail (nil : Seq α) = nil :=
  rfl
#align stream.seq.tail_nil Stream'.Seq.tail_nil

@[simp]
theorem tail_cons (a : α) (s) : tail (cons a s) = s := by
  cases' s with f al <;> apply Subtype.eq <;> dsimp [tail, cons] <;> rw [Stream'.tail_cons]
#align stream.seq.tail_cons Stream'.Seq.tail_cons

@[simp]
theorem nth_tail (s : Seq α) (n) : nth (tail s) n = nth s (n + 1) :=
  rfl
#align stream.seq.nth_tail Stream'.Seq.nth_tail

/-- Recursion principle for sequences, compare with `list.rec_on`. -/
def recOn {C : Seq α → Sort v} (s : Seq α) (h1 : C nil) (h2 : ∀ x s, C (cons x s)) : C s :=
  by
  induction' H : destruct s with v v
  · rw [destruct_eq_nil H]
    apply h1
  · cases' v with a s'
    rw [destruct_eq_cons H]
    apply h2
#align stream.seq.rec_on Stream'.Seq.recOn

theorem mem_rec_on {C : Seq α → Prop} {a s} (M : a ∈ s)
    (h1 : ∀ b s', a = b ∨ C s' → C (cons b s')) : C s :=
  by
  cases' M with k e; unfold Stream'.nth at e
  induction' k with k IH generalizing s
  · have TH : s = cons a (tail s) := by
      apply destruct_eq_cons
      unfold destruct nth Functor.map
      rw [← e]
      rfl
    rw [TH]
    apply h1 _ _ (Or.inl rfl)
  revert e; apply s.rec_on _ fun b s' => _ <;> intro e
  · injection e
  · have h_eq : (cons b s').val (Nat.succ k) = s'.val k := by cases s' <;> rfl
    rw [h_eq] at e
    apply h1 _ _ (Or.inr (IH e))
#align stream.seq.mem_rec_on Stream'.Seq.mem_rec_on

/-- Corecursor over pairs of `option` values-/
def Corec.f (f : β → Option (α × β)) : Option β → Option α × Option β
  | none => (none, none)
  | some b =>
    match f b with
    | none => (none, none)
    | some (a, b') => (some a, some b')
#align stream.seq.corec.F Stream'.Seq.Corec.f

/-- Corecursor for `seq α` as a coinductive type. Iterates `f` to produce new elements
  of the sequence until `none` is obtained. -/
def corec (f : β → Option (α × β)) (b : β) : Seq α :=
  by
  refine' ⟨Stream'.corec' (corec.F f) (some b), fun n h => _⟩
  rw [Stream'.corec'_eq]
  change Stream'.corec' (corec.F f) (corec.F f (some b)).2 n = none
  revert h; generalize some b = o; revert o
  induction' n with n IH <;> intro o
  · change (corec.F f o).1 = none → (corec.F f (corec.F f o).2).1 = none
    cases' o with b <;> intro h
    · rfl
    dsimp [corec.F] at h
    dsimp [corec.F]
    cases' f b with s
    · rfl
    · cases' s with a b'
      contradiction
  · rw [Stream'.corec'_eq (corec.F f) (corec.F f o).2, Stream'.corec'_eq (corec.F f) o]
    exact IH (corec.F f o).2
#align stream.seq.corec Stream'.Seq.corec

@[simp]
theorem corec_eq (f : β → Option (α × β)) (b : β) : destruct (corec f b) = omap (corec f) (f b) :=
  by
  dsimp [corec, destruct, nth]
  change Stream'.corec' (corec.F f) (some b) 0 with (corec.F f (some b)).1
  dsimp [corec.F]
  induction' h : f b with s; · rfl
  cases' s with a b'; dsimp [corec.F]
  apply congr_arg fun b' => some (a, b')
  apply Subtype.eq
  dsimp [corec, tail]
  rw [Stream'.corec'_eq, Stream'.tail_cons]
  dsimp [corec.F]; rw [h]; rfl
#align stream.seq.corec_eq Stream'.Seq.corec_eq

section Bisim

variable (R : Seq α → Seq α → Prop)

-- mathport name: R
local infixl:50 " ~ " => R

/-- Bisimilarity relation over `option` of `seq1 α`-/
def BisimO : Option (Seq1 α) → Option (Seq1 α) → Prop
  | none, none => True
  | some (a, s), some (a', s') => a = a' ∧ R s s'
  | _, _ => False
#align stream.seq.bisim_o Stream'.Seq.BisimO

attribute [simp] bisim_o

/-- a relation is bisimiar if it meets the `bisim_o` test-/
def IsBisimulation :=
  ∀ ⦃s₁ s₂⦄, s₁ ~ s₂ → BisimO R (destruct s₁) (destruct s₂)
#align stream.seq.is_bisimulation Stream'.Seq.IsBisimulation

-- If two streams are bisimilar, then they are equal
theorem eq_of_bisim (bisim : IsBisimulation R) {s₁ s₂} (r : s₁ ~ s₂) : s₁ = s₂ :=
  by
  apply Subtype.eq
  apply Stream'.eq_of_bisim fun x y => ∃ s s' : seq α, s.1 = x ∧ s'.1 = y ∧ R s s'
  dsimp [Stream'.IsBisimulation]
  intro t₁ t₂ e
  exact
    match t₁, t₂, e with
    | _, _, ⟨s, s', rfl, rfl, r⟩ =>
      by
      suffices head s = head s' ∧ R (tail s) (tail s') from
        And.imp id (fun r => ⟨tail s, tail s', by cases s <;> rfl, by cases s' <;> rfl, r⟩) this
      have := bisim r; revert r this
      apply rec_on s _ _ <;> intros <;> apply rec_on s' _ _ <;> intros <;> intro r this
      · constructor
        rfl
        assumption
      · rw [destruct_nil, destruct_cons] at this
        exact False.elim this
      · rw [destruct_nil, destruct_cons] at this
        exact False.elim this
      · rw [destruct_cons, destruct_cons] at this
        rw [head_cons, head_cons, tail_cons, tail_cons]
        cases' this with h1 h2
        constructor
        rw [h1]
        exact h2
  exact ⟨s₁, s₂, rfl, rfl, r⟩
#align stream.seq.eq_of_bisim Stream'.Seq.eq_of_bisim

end Bisim

theorem coinduction :
    ∀ {s₁ s₂ : Seq α},
      head s₁ = head s₂ →
        (∀ (β : Type u) (fr : Seq α → β), fr s₁ = fr s₂ → fr (tail s₁) = fr (tail s₂)) → s₁ = s₂
  | ⟨f₁, a₁⟩, ⟨f₂, a₂⟩, hh, ht =>
    Subtype.eq (Stream'.coinduction hh fun β fr => ht β fun s => fr s.1)
#align stream.seq.coinduction Stream'.Seq.coinduction

theorem coinduction2 (s) (f g : Seq α → Seq β)
    (H :
      ∀ s,
        BisimO (fun s1 s2 : Seq β => ∃ s : Seq α, s1 = f s ∧ s2 = g s) (destruct (f s))
          (destruct (g s))) :
    f s = g s :=
  by
  refine' eq_of_bisim (fun s1 s2 => ∃ s, s1 = f s ∧ s2 = g s) _ ⟨s, rfl, rfl⟩
  intro s1 s2 h; rcases h with ⟨s, h1, h2⟩
  rw [h1, h2]; apply H
#align stream.seq.coinduction2 Stream'.Seq.coinduction2

/-- Embed a list as a sequence -/
def ofList (l : List α) : Seq α :=
  ⟨List.get? l, fun n h => by
    rw [List.get?_eq_none] at h⊢
    exact h.trans (Nat.le_succ n)⟩
#align stream.seq.of_list Stream'.Seq.ofList

instance coeList : Coe (List α) (Seq α) :=
  ⟨ofList⟩
#align stream.seq.coe_list Stream'.Seq.coeList

@[simp]
theorem ofList_nil : ofList [] = (nil : Seq α) :=
  rfl
#align stream.seq.of_list_nil Stream'.Seq.ofList_nil

@[simp]
theorem ofList_nth (l : List α) (n : ℕ) : (ofList l).get? n = l.get? n :=
  rfl
#align stream.seq.of_list_nth Stream'.Seq.ofList_nth

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem ofList_cons (a : α) (l : List α) : ofList (a::l) = cons a (ofList l) := by
  ext1 (_ | n) <;> rfl
#align stream.seq.of_list_cons Stream'.Seq.ofList_cons

/-- Embed an infinite stream as a sequence -/
def ofStream (s : Stream' α) : Seq α :=
  ⟨s.map some, fun n h => by contradiction⟩
#align stream.seq.of_stream Stream'.Seq.ofStream

instance coeStream : Coe (Stream' α) (Seq α) :=
  ⟨ofStream⟩
#align stream.seq.coe_stream Stream'.Seq.coeStream

/-- Embed a `lazy_list α` as a sequence. Note that even though this
  is non-meta, it will produce infinite sequences if used with
  cyclic `lazy_list`s created by meta constructions. -/
def ofLazyList : LazyList α → Seq α :=
  corec fun l =>
    match l with
    | LazyList.nil => none
    | LazyList.cons a l' => some (a, l' ())
#align stream.seq.of_lazy_list Stream'.Seq.ofLazyList

instance coeLazyList : Coe (LazyList α) (Seq α) :=
  ⟨ofLazyList⟩
#align stream.seq.coe_lazy_list Stream'.Seq.coeLazyList

/-- Translate a sequence into a `lazy_list`. Since `lazy_list` and `list`
  are isomorphic as non-meta types, this function is necessarily meta. -/
unsafe def to_lazy_list : Seq α → LazyList α
  | s =>
    match destruct s with
    | none => LazyList.nil
    | some (a, s') => LazyList.cons a (to_lazy_list s')
#align stream.seq.to_lazy_list stream.seq.to_lazy_list

/-- Translate a sequence to a list. This function will run forever if
  run on an infinite sequence. -/
unsafe def force_to_list (s : Seq α) : List α :=
  (to_lazy_list s).toList
#align stream.seq.force_to_list stream.seq.force_to_list

/-- The sequence of natural numbers some 0, some 1, ... -/
def nats : Seq ℕ :=
  Stream'.nats
#align stream.seq.nats Stream'.Seq.nats

@[simp]
theorem nats_nth (n : ℕ) : nats.get? n = some n :=
  rfl
#align stream.seq.nats_nth Stream'.Seq.nats_nth

/-- Append two sequences. If `s₁` is infinite, then `s₁ ++ s₂ = s₁`,
  otherwise it puts `s₂` at the location of the `nil` in `s₁`. -/
def append (s₁ s₂ : Seq α) : Seq α :=
  @corec α (Seq α × Seq α)
    (fun ⟨s₁, s₂⟩ =>
      match destruct s₁ with
      | none => omap (fun s₂ => (nil, s₂)) (destruct s₂)
      | some (a, s₁') => some (a, s₁', s₂))
    (s₁, s₂)
#align stream.seq.append Stream'.Seq.append

/-- Map a function over a sequence. -/
def map (f : α → β) : Seq α → Seq β
  | ⟨s, al⟩ =>
    ⟨s.map (Option.map f), fun n =>
      by
      dsimp [Stream'.map, Stream'.nth]
      induction' e : s n with <;> intro
      · rw [al e]
        assumption; · contradiction⟩
#align stream.seq.map Stream'.Seq.map

/-- Flatten a sequence of sequences. (It is required that the
  sequences be nonempty to ensure productivity; in the case
  of an infinite sequence of `nil`, the first element is never
  generated.) -/
def join : Seq (Seq1 α) → Seq α :=
  corec fun S =>
    match destruct S with
    | none => none
    | some ((a, s), S') =>
      some
        (a,
          match destruct s with
          | none => S'
          | some s' => cons s' S')
#align stream.seq.join Stream'.Seq.join

/-- Remove the first `n` elements from the sequence. -/
def drop (s : Seq α) : ℕ → Seq α
  | 0 => s
  | n + 1 => tail (drop n)
#align stream.seq.drop Stream'.Seq.drop

attribute [simp] drop

/-- Take the first `n` elements of the sequence (producing a list) -/
def take : ℕ → Seq α → List α
  | 0, s => []
  | n + 1, s =>
    match destruct s with
    | none => []
    | some (x, r) => List.cons x (take n r)
#align stream.seq.take Stream'.Seq.take

/-- Split a sequence at `n`, producing a finite initial segment
  and an infinite tail. -/
def splitAt : ℕ → Seq α → List α × Seq α
  | 0, s => ([], s)
  | n + 1, s =>
    match destruct s with
    | none => ([], nil)
    | some (x, s') =>
      let (l, r) := split_at n s'
      (List.cons x l, r)
#align stream.seq.split_at Stream'.Seq.splitAt

section ZipWith

/-- Combine two sequences with a function -/
def zipWith (f : α → β → γ) (s₁ : Seq α) (s₂ : Seq β) : Seq γ :=
  ⟨fun n => Option.map₂ f (s₁.get? n) (s₂.get? n), fun n hn =>
    Option.map₂_eq_none_iff.2 <| (Option.map₂_eq_none_iff.1 hn).imp s₁.2 s₂.2⟩
#align stream.seq.zip_with Stream'.Seq.zipWith

variable {s : Seq α} {s' : Seq β} {n : ℕ}

@[simp]
theorem nth_zipWith (f : α → β → γ) (s s' n) :
    (zipWith f s s').get? n = Option.map₂ f (s.get? n) (s'.get? n) :=
  rfl
#align stream.seq.nth_zip_with Stream'.Seq.nth_zipWith

end ZipWith

/-- Pair two sequences into a sequence of pairs -/
def zip : Seq α → Seq β → Seq (α × β) :=
  zipWith Prod.mk
#align stream.seq.zip Stream'.Seq.zip

theorem nth_zip (s : Seq α) (t : Seq β) (n : ℕ) :
    nth (zip s t) n = Option.map₂ Prod.mk (nth s n) (nth t n) :=
  nth_zipWith _ _ _ _
#align stream.seq.nth_zip Stream'.Seq.nth_zip

/-- Separate a sequence of pairs into two sequences -/
def unzip (s : Seq (α × β)) : Seq α × Seq β :=
  (map Prod.fst s, map Prod.snd s)
#align stream.seq.unzip Stream'.Seq.unzip

/-- Enumerate a sequence by tagging each element with its index. -/
def enum (s : Seq α) : Seq (ℕ × α) :=
  Seq.zip nats s
#align stream.seq.enum Stream'.Seq.enum

@[simp]
theorem nth_enum (s : Seq α) (n : ℕ) : nth (enum s) n = Option.map (Prod.mk n) (nth s n) :=
  nth_zip _ _ _
#align stream.seq.nth_enum Stream'.Seq.nth_enum

@[simp]
theorem enum_nil : enum (nil : Seq α) = nil :=
  rfl
#align stream.seq.enum_nil Stream'.Seq.enum_nil

/-- Convert a sequence which is known to terminate into a list -/
def toList (s : Seq α) (h : s.Terminates) : List α :=
  take (Nat.find h) s
#align stream.seq.to_list Stream'.Seq.toList

/-- Convert a sequence which is known not to terminate into a stream -/
def toStream (s : Seq α) (h : ¬s.Terminates) : Stream' α := fun n =>
  Option.get <| not_terminates_iff.1 h n
#align stream.seq.to_stream Stream'.Seq.toStream

/-- Convert a sequence into either a list or a stream depending on whether
  it is finite or infinite. (Without decidability of the infiniteness predicate,
  this is not constructively possible.) -/
def toListOrStream (s : Seq α) [Decidable s.Terminates] : Sum (List α) (Stream' α) :=
  if h : s.Terminates then Sum.inl (toList s h) else Sum.inr (toStream s h)
#align stream.seq.to_list_or_stream Stream'.Seq.toListOrStream

@[simp]
theorem nil_append (s : Seq α) : append nil s = s :=
  by
  apply coinduction2; intro s
  dsimp [append]; rw [corec_eq]
  dsimp [append]; apply rec_on s _ _
  · trivial
  · intro x s
    rw [destruct_cons]
    dsimp
    exact ⟨rfl, s, rfl, rfl⟩
#align stream.seq.nil_append Stream'.Seq.nil_append

@[simp]
theorem cons_append (a : α) (s t) : append (cons a s) t = cons a (append s t) :=
  destruct_eq_cons <| by
    dsimp [append]; rw [corec_eq]
    dsimp [append]; rw [destruct_cons]
    dsimp [append]; rfl
#align stream.seq.cons_append Stream'.Seq.cons_append

@[simp]
theorem append_nil (s : Seq α) : append s nil = s :=
  by
  apply coinduction2 s; intro s
  apply rec_on s _ _
  · trivial
  · intro x s
    rw [cons_append, destruct_cons, destruct_cons]
    dsimp
    exact ⟨rfl, s, rfl, rfl⟩
#align stream.seq.append_nil Stream'.Seq.append_nil

@[simp]
theorem append_assoc (s t u : Seq α) : append (append s t) u = append s (append t u) :=
  by
  apply eq_of_bisim fun s1 s2 => ∃ s t u, s1 = append (append s t) u ∧ s2 = append s (append t u)
  · intro s1 s2 h
    exact
      match s1, s2, h with
      | _, _, ⟨s, t, u, rfl, rfl⟩ => by
        apply rec_on s <;> simp
        · apply rec_on t <;> simp
          · apply rec_on u <;> simp
            · intro x u
              refine' ⟨nil, nil, u, _, _⟩ <;> simp
          · intro x t
            refine' ⟨nil, t, u, _, _⟩ <;> simp
        · intro x s
          exact ⟨s, t, u, rfl, rfl⟩
  · exact ⟨s, t, u, rfl, rfl⟩
#align stream.seq.append_assoc Stream'.Seq.append_assoc

@[simp]
theorem map_nil (f : α → β) : map f nil = nil :=
  rfl
#align stream.seq.map_nil Stream'.Seq.map_nil

@[simp]
theorem map_cons (f : α → β) (a) : ∀ s, map f (cons a s) = cons (f a) (map f s)
  | ⟨s, al⟩ => by apply Subtype.eq <;> dsimp [cons, map] <;> rw [Stream'.map_cons] <;> rfl
#align stream.seq.map_cons Stream'.Seq.map_cons

@[simp]
theorem map_id : ∀ s : Seq α, map id s = s
  | ⟨s, al⟩ => by
    apply Subtype.eq <;> dsimp [map]
    rw [Option.map_id, Stream'.map_id] <;> rfl
#align stream.seq.map_id Stream'.Seq.map_id

@[simp]
theorem map_tail (f : α → β) : ∀ s, map f (tail s) = tail (map f s)
  | ⟨s, al⟩ => by apply Subtype.eq <;> dsimp [tail, map] <;> rw [Stream'.map_tail] <;> rfl
#align stream.seq.map_tail Stream'.Seq.map_tail

theorem map_comp (f : α → β) (g : β → γ) : ∀ s : Seq α, map (g ∘ f) s = map g (map f s)
  | ⟨s, al⟩ => by
    apply Subtype.eq <;> dsimp [map]
    rw [Stream'.map_map]
    apply congr_arg fun f : _ → Option γ => Stream'.map f s
    ext ⟨⟩ <;> rfl
#align stream.seq.map_comp Stream'.Seq.map_comp

@[simp]
theorem map_append (f : α → β) (s t) : map f (append s t) = append (map f s) (map f t) :=
  by
  apply
    eq_of_bisim (fun s1 s2 => ∃ s t, s1 = map f (append s t) ∧ s2 = append (map f s) (map f t)) _
      ⟨s, t, rfl, rfl⟩
  intro s1 s2 h;
  exact
    match s1, s2, h with
    | _, _, ⟨s, t, rfl, rfl⟩ => by
      apply rec_on s <;> simp
      · apply rec_on t <;> simp
        · intro x t
          refine' ⟨nil, t, _, _⟩ <;> simp
      · intro x s
        refine' ⟨s, t, rfl, rfl⟩
#align stream.seq.map_append Stream'.Seq.map_append

@[simp]
theorem map_nth (f : α → β) : ∀ s n, nth (map f s) n = (nth s n).map f
  | ⟨s, al⟩, n => rfl
#align stream.seq.map_nth Stream'.Seq.map_nth

instance : Functor Seq where map := @map

instance : LawfulFunctor Seq where
  id_map := @map_id
  comp_map := @map_comp

@[simp]
theorem join_nil : join nil = (nil : Seq α) :=
  destruct_eq_nil rfl
#align stream.seq.join_nil Stream'.Seq.join_nil

@[simp]
theorem join_cons_nil (a : α) (S) : join (cons (a, nil) S) = cons a (join S) :=
  destruct_eq_cons <| by simp [join]
#align stream.seq.join_cons_nil Stream'.Seq.join_cons_nil

@[simp]
theorem join_cons_cons (a b : α) (s S) :
    join (cons (a, cons b s) S) = cons a (join (cons (b, s) S)) :=
  destruct_eq_cons <| by simp [join]
#align stream.seq.join_cons_cons Stream'.Seq.join_cons_cons

@[simp]
theorem join_cons (a : α) (s S) : join (cons (a, s) S) = cons a (append s (join S)) :=
  by
  apply
    eq_of_bisim
      (fun s1 s2 => s1 = s2 ∨ ∃ a s S, s1 = join (cons (a, s) S) ∧ s2 = cons a (append s (join S)))
      _ (Or.inr ⟨a, s, S, rfl, rfl⟩)
  intro s1 s2 h
  exact
    match s1, s2, h with
    | _, _, Or.inl <| Eq.refl s => by
      apply rec_on s; · trivial
      · intro x s
        rw [destruct_cons]
        exact ⟨rfl, Or.inl rfl⟩
    | _, _, Or.inr ⟨a, s, S, rfl, rfl⟩ => by
      apply rec_on s
      · simp
      · intro x s
        simp
        refine' Or.inr ⟨x, s, S, rfl, rfl⟩
#align stream.seq.join_cons Stream'.Seq.join_cons

@[simp]
theorem join_append (S T : Seq (Seq1 α)) : join (append S T) = append (join S) (join T) :=
  by
  apply
    eq_of_bisim fun s1 s2 =>
      ∃ s S T, s1 = append s (join (append S T)) ∧ s2 = append s (append (join S) (join T))
  · intro s1 s2 h
    exact
      match s1, s2, h with
      | _, _, ⟨s, S, T, rfl, rfl⟩ => by
        apply rec_on s <;> simp
        · apply rec_on S <;> simp
          · apply rec_on T
            · simp
            · intro s T
              cases' s with a s <;> simp
              refine' ⟨s, nil, T, _, _⟩ <;> simp
          · intro s S
            cases' s with a s <;> simp
            exact ⟨s, S, T, rfl, rfl⟩
        · intro x s
          exact ⟨s, S, T, rfl, rfl⟩
  · refine' ⟨nil, S, T, _, _⟩ <;> simp
#align stream.seq.join_append Stream'.Seq.join_append

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
@[simp]
theorem ofStream_cons (a : α) (s) : ofStream (a::s) = cons a (ofStream s) := by
  apply Subtype.eq <;> simp [of_stream, cons] <;> rw [Stream'.map_cons]
#align stream.seq.of_stream_cons Stream'.Seq.ofStream_cons

@[simp]
theorem ofList_append (l l' : List α) : ofList (l ++ l') = append (ofList l) (ofList l') := by
  induction l <;> simp [*]
#align stream.seq.of_list_append Stream'.Seq.ofList_append

@[simp]
theorem ofStream_append (l : List α) (s : Stream' α) :
    ofStream (l ++ₛ s) = append (ofList l) (ofStream s) := by
  induction l <;> simp [*, Stream'.nil_append_stream, Stream'.cons_append_stream]
#align stream.seq.of_stream_append Stream'.Seq.ofStream_append

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/-- Convert a sequence into a list, embedded in a computation to allow for
  the possibility of infinite sequences (in which case the computation
  never returns anything). -/
def toList' {α} (s : Seq α) : Computation (List α) :=
  @Computation.corec (List α) (List α × Seq α)
    (fun ⟨l, s⟩ =>
      match destruct s with
      | none => Sum.inl l.reverse
      | some (a, s') => Sum.inr (a::l, s'))
    ([], s)
#align stream.seq.to_list' Stream'.Seq.toList'

theorem dropn_add (s : Seq α) (m) : ∀ n, drop s (m + n) = drop (drop s m) n
  | 0 => rfl
  | n + 1 => congr_arg tail (dropn_add n)
#align stream.seq.dropn_add Stream'.Seq.dropn_add

theorem dropn_tail (s : Seq α) (n) : drop (tail s) n = drop s (n + 1) := by
  rw [add_comm] <;> symm <;> apply dropn_add
#align stream.seq.dropn_tail Stream'.Seq.dropn_tail

@[simp]
theorem head_dropn (s : Seq α) (n) : head (drop s n) = nth s n :=
  by
  induction' n with n IH generalizing s; · rfl
  rw [Nat.succ_eq_add_one, ← nth_tail, ← dropn_tail]; apply IH
#align stream.seq.head_dropn Stream'.Seq.head_dropn

theorem mem_map (f : α → β) {a : α} : ∀ {s : Seq α}, a ∈ s → f a ∈ map f s
  | ⟨g, al⟩ => Stream'.mem_map (Option.map f)
#align stream.seq.mem_map Stream'.Seq.mem_map

theorem exists_of_mem_map {f} {b : β} : ∀ {s : Seq α}, b ∈ map f s → ∃ a, a ∈ s ∧ f a = b
  | ⟨g, al⟩, h => by
    let ⟨o, om, oe⟩ := Stream'.exists_of_mem_map h
    cases' o with a <;> injection oe with h' <;> exact ⟨a, om, h'⟩
#align stream.seq.exists_of_mem_map Stream'.Seq.exists_of_mem_map

theorem of_mem_append {s₁ s₂ : Seq α} {a : α} (h : a ∈ append s₁ s₂) : a ∈ s₁ ∨ a ∈ s₂ :=
  by
  have := h; revert this
  generalize e : append s₁ s₂ = ss; intro h; revert s₁
  apply mem_rec_on h _
  intro b s' o s₁
  apply s₁.rec_on _ fun c t₁ => _ <;> intro m e <;> have := congr_arg destruct e
  · apply Or.inr
    simpa using m
  · cases' show a = c ∨ a ∈ append t₁ s₂ by simpa using m with e' m
    · rw [e']
      exact Or.inl (mem_cons _ _)
    · cases' show c = b ∧ append t₁ s₂ = s' by simpa with i1 i2
      cases' o with e' IH
      · simp [i1, e']
      · exact Or.imp_left (mem_cons_of_mem _) (IH m i2)
#align stream.seq.of_mem_append Stream'.Seq.of_mem_append

theorem mem_append_left {s₁ s₂ : Seq α} {a : α} (h : a ∈ s₁) : a ∈ append s₁ s₂ := by
  apply mem_rec_on h <;> intros <;> simp [*]
#align stream.seq.mem_append_left Stream'.Seq.mem_append_left

@[simp]
theorem enum_cons (s : Seq α) (x : α) :
    enum (cons x s) = cons (0, x) (map (Prod.map Nat.succ id) (enum s)) :=
  by
  ext ⟨n⟩ : 1
  · simp
  · simp only [nth_enum, nth_cons_succ, map_nth, Option.map_map]
    congr
#align stream.seq.enum_cons Stream'.Seq.enum_cons

end Seq

namespace Seq1

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

open Stream'.Seq

/-- Convert a `seq1` to a sequence. -/
def toSeq : Seq1 α → Seq α
  | (a, s) => Seq.cons a s
#align stream.seq1.to_seq Stream'.Seq1.toSeq

instance coeSeq : Coe (Seq1 α) (Seq α) :=
  ⟨toSeq⟩
#align stream.seq1.coe_seq Stream'.Seq1.coeSeq

/-- Map a function on a `seq1` -/
def map (f : α → β) : Seq1 α → Seq1 β
  | (a, s) => (f a, Seq.map f s)
#align stream.seq1.map Stream'.Seq1.map

theorem map_id : ∀ s : Seq1 α, map id s = s
  | ⟨a, s⟩ => by simp [map]
#align stream.seq1.map_id Stream'.Seq1.map_id

/-- Flatten a nonempty sequence of nonempty sequences -/
def join : Seq1 (Seq1 α) → Seq1 α
  | ((a, s), S) =>
    match destruct s with
    | none => (a, Seq.join S)
    | some s' => (a, Seq.join (Seq.cons s' S))
#align stream.seq1.join Stream'.Seq1.join

@[simp]
theorem join_nil (a : α) (S) : join ((a, nil), S) = (a, Seq.join S) :=
  rfl
#align stream.seq1.join_nil Stream'.Seq1.join_nil

@[simp]
theorem join_cons (a b : α) (s S) :
    join ((a, Seq.cons b s), S) = (a, Seq.join (Seq.cons (b, s) S)) := by
  dsimp [join] <;> rw [destruct_cons] <;> rfl
#align stream.seq1.join_cons Stream'.Seq1.join_cons

/-- The `return` operator for the `seq1` monad,
  which produces a singleton sequence. -/
def ret (a : α) : Seq1 α :=
  (a, nil)
#align stream.seq1.ret Stream'.Seq1.ret

instance [Inhabited α] : Inhabited (Seq1 α) :=
  ⟨ret default⟩

/-- The `bind` operator for the `seq1` monad,
  which maps `f` on each element of `s` and appends the results together.
  (Not all of `s` may be evaluated, because the first few elements of `s`
  may already produce an infinite result.) -/
def bind (s : Seq1 α) (f : α → Seq1 β) : Seq1 β :=
  join (map f s)
#align stream.seq1.bind Stream'.Seq1.bind

@[simp]
theorem join_map_ret (s : Seq α) : Seq.join (Seq.map ret s) = s := by
  apply coinduction2 s <;> intro s <;> apply rec_on s <;> simp [ret]
#align stream.seq1.join_map_ret Stream'.Seq1.join_map_ret

@[simp]
theorem bind_ret (f : α → β) : ∀ s, bind s (ret ∘ f) = map f s
  | ⟨a, s⟩ => by
    dsimp [bind, map]; change fun x => ret (f x) with ret ∘ f
    rw [map_comp]; simp [Function.comp, ret]
#align stream.seq1.bind_ret Stream'.Seq1.bind_ret

@[simp]
theorem ret_bind (a : α) (f : α → Seq1 β) : bind (ret a) f = f a :=
  by
  simp [ret, bind, map]
  cases' f a with a s
  apply rec_on s <;> intros <;> simp
#align stream.seq1.ret_bind Stream'.Seq1.ret_bind

@[simp]
theorem map_join' (f : α → β) (S) : Seq.map f (Seq.join S) = Seq.join (Seq.map (map f) S) :=
  by
  apply
    seq.eq_of_bisim fun s1 s2 =>
      ∃ s S,
        s1 = seq.append s (seq.map f (seq.join S)) ∧ s2 = append s (seq.join (seq.map (map f) S))
  · intro s1 s2 h
    exact
      match s1, s2, h with
      | _, _, ⟨s, S, rfl, rfl⟩ => by
        apply rec_on s <;> simp
        · apply rec_on S <;> simp
          · intro x S
            cases' x with a s <;> simp [map]
            exact ⟨_, _, rfl, rfl⟩
        · intro x s
          refine' ⟨s, S, rfl, rfl⟩
  · refine' ⟨nil, S, _, _⟩ <;> simp
#align stream.seq1.map_join' Stream'.Seq1.map_join'

@[simp]
theorem map_join (f : α → β) : ∀ S, map f (join S) = join (map (map f) S)
  | ((a, s), S) => by apply rec_on s <;> intros <;> simp [map]
#align stream.seq1.map_join Stream'.Seq1.map_join

@[simp]
theorem join_join (SS : Seq (Seq1 (Seq1 α))) :
    Seq.join (Seq.join SS) = Seq.join (Seq.map join SS) :=
  by
  apply
    seq.eq_of_bisim fun s1 s2 =>
      ∃ s SS,
        s1 = seq.append s (seq.join (seq.join SS)) ∧ s2 = seq.append s (seq.join (seq.map join SS))
  · intro s1 s2 h
    exact
      match s1, s2, h with
      | _, _, ⟨s, SS, rfl, rfl⟩ => by
        apply rec_on s <;> simp
        · apply rec_on SS <;> simp
          · intro S SS
            cases' S with s S <;> cases' s with x s <;> simp [map]
            apply rec_on s <;> simp
            · exact ⟨_, _, rfl, rfl⟩
            · intro x s
              refine' ⟨seq.cons x (append s (seq.join S)), SS, _, _⟩ <;> simp
        · intro x s
          exact ⟨s, SS, rfl, rfl⟩
  · refine' ⟨nil, SS, _, _⟩ <;> simp
#align stream.seq1.join_join Stream'.Seq1.join_join

@[simp]
theorem bind_assoc (s : Seq1 α) (f : α → Seq1 β) (g : β → Seq1 γ) :
    bind (bind s f) g = bind s fun x : α => bind (f x) g :=
  by
  cases' s with a s
  simp [bind, map]
  rw [← map_comp]
  change fun x => join (map g (f x)) with join ∘ map g ∘ f
  rw [map_comp _ join]
  generalize seq.map (map g ∘ f) s = SS
  rcases map g (f a) with ⟨⟨a, s⟩, S⟩
  apply rec_on s <;> intros <;> apply rec_on S <;> intros <;> simp
  · cases' x with x t
    apply rec_on t <;> intros <;> simp
  · cases' x_1 with y t <;> simp
#align stream.seq1.bind_assoc Stream'.Seq1.bind_assoc

instance : Monad Seq1 where
  map := @map
  pure := @ret
  bind := @bind

instance : LawfulMonad Seq1 where
  id_map := @map_id
  bind_pure_comp_eq_map := @bind_ret
  pure_bind := @ret_bind
  bind_assoc := @bind_assoc

end Seq1

end Stream'


/-
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

! This file was ported from Lean 3 source module data.sigma.basic
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Meta.Univs
import Mathbin.Tactic.Lint.Default
import Mathbin.Tactic.Ext
import Mathbin.Logic.Function.Basic

/-!
# Sigma types

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

This file proves basic results about sigma types.

A sigma type is a dependent pair type. Like `α × β` but where the type of the second component
depends on the first component. This can be seen as a generalization of the sum type `α ⊕ β`:
* `α ⊕ β` is made of stuff which is either of type `α` or `β`.
* Given `α : ι → Type*`, `sigma α` is made of stuff which is of type `α i` for some `i : ι`. One
  effectively recovers a type isomorphic to `α ⊕ β` by taking a `ι` with exactly two elements. See
  `equiv.sum_equiv_sigma_bool`.

`Σ x, A x` is notation for `sigma A` (note the difference with the big operator `∑`).
`Σ x y z ..., A x y z ...` is notation for `Σ x, Σ y, Σ z, ..., A x y z ...`. Here we have
`α : Type*`, `β : α → Type*`, `γ : Π a : α, β a → Type*`, ...,
`A : Π (a : α) (b : β a) (c : γ a b) ..., Type*`  with `x : α` `y : β x`, `z : γ x y`, ...

## Notes

The definition of `sigma` takes values in `Type*`. This effectively forbids `Prop`- valued sigma
types. To that effect, we have `psigma`, which takes value in `Sort*` and carries a more complicated
universe signature in consequence.
-/

section Sigma

variable {α α₁ α₂ : Type _} {β : α → Type _} {β₁ : α₁ → Type _} {β₂ : α₂ → Type _}

namespace Sigma

instance [Inhabited α] [Inhabited (β default)] : Inhabited (Sigma β) :=
  ⟨⟨default, default⟩⟩

instance [h₁ : DecidableEq α] [h₂ : ∀ a, DecidableEq (β a)] : DecidableEq (Sigma β)
  | ⟨a₁, b₁⟩, ⟨a₂, b₂⟩ =>
    match a₁, b₁, a₂, b₂, h₁ a₁ a₂ with
    | _, b₁, _, b₂, is_true (Eq.refl a) =>
      match b₁, b₂, h₂ a b₁ b₂ with
      | _, _, is_true (Eq.refl b) => isTrue rfl
      | b₁, b₂, is_false n => isFalse fun h => Sigma.noConfusion h fun e₁ e₂ => n <| eq_of_hEq e₂
    | a₁, _, a₂, _, is_false n => isFalse fun h => Sigma.noConfusion h fun e₁ e₂ => n e₁

/- warning: sigma.mk.inj_iff -> Sigma.mk.inj_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {a₁ : α} {a₂ : α} {b₁ : β a₁} {b₂ : β a₂}, Iff (Eq.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α (fun {a₁ : α} => β a₁)) (Sigma.mk.{u1, u2} α (fun {a₁ : α} => β a₁) a₁ b₁) (Sigma.mk.{u1, u2} α (fun {a₁ : α} => β a₁) a₂ b₂)) (And (Eq.{succ u1} α a₁ a₂) (HEq.{succ u2} (β a₁) b₁ (β a₂) b₂))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {a₁ : α} {a₂ : α} {b₁ : β a₁} {b₂ : β a₂}, Iff (Eq.{max (succ u2) (succ u1)} (Sigma.{u2, u1} α β) (Sigma.mk.{u2, u1} α β a₁ b₁) (Sigma.mk.{u2, u1} α β a₂ b₂)) (And (Eq.{succ u2} α a₁ a₂) (HEq.{succ u1} (β a₁) b₁ (β a₂) b₂))
Case conversion may be inaccurate. Consider using '#align sigma.mk.inj_iff Sigma.mk.inj_iffₓ'. -/
-- sometimes the built-in injectivity support does not work
@[simp, nolint simp_nf]
theorem mk.inj_iff {a₁ a₂ : α} {b₁ : β a₁} {b₂ : β a₂} :
    Sigma.mk a₁ b₁ = ⟨a₂, b₂⟩ ↔ a₁ = a₂ ∧ HEq b₁ b₂ := by simp
#align sigma.mk.inj_iff Sigma.mk.inj_iff

/- warning: sigma.eta -> Sigma.eta is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} (x : Sigma.{u1, u2} α (fun (a : α) => β a)), Eq.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α β) (Sigma.mk.{u1, u2} α β (Sigma.fst.{u1, u2} α (fun (a : α) => β a) x) (Sigma.snd.{u1, u2} α (fun (a : α) => β a) x)) x
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} (x : Sigma.{u2, u1} α (fun (a : α) => β a)), Eq.{max (succ u2) (succ u1)} (Sigma.{u2, u1} α β) (Sigma.mk.{u2, u1} α β (Sigma.fst.{u2, u1} α (fun (a : α) => β a) x) (Sigma.snd.{u2, u1} α (fun (a : α) => β a) x)) x
Case conversion may be inaccurate. Consider using '#align sigma.eta Sigma.etaₓ'. -/
@[simp]
theorem eta : ∀ x : Σa, β a, Sigma.mk x.1 x.2 = x
  | ⟨i, x⟩ => rfl
#align sigma.eta Sigma.eta

/- warning: sigma.ext -> Sigma.ext is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {x₀ : Sigma.{u1, u2} α β} {x₁ : Sigma.{u1, u2} α β}, (Eq.{succ u1} α (Sigma.fst.{u1, u2} α β x₀) (Sigma.fst.{u1, u2} α β x₁)) -> (HEq.{succ u2} (β (Sigma.fst.{u1, u2} α β x₀)) (Sigma.snd.{u1, u2} α β x₀) (β (Sigma.fst.{u1, u2} α β x₁)) (Sigma.snd.{u1, u2} α β x₁)) -> (Eq.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α β) x₀ x₁)
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {x₀ : Sigma.{u2, u1} α β} {x₁ : Sigma.{u2, u1} α β}, (Eq.{succ u2} α (Sigma.fst.{u2, u1} α β x₀) (Sigma.fst.{u2, u1} α β x₁)) -> (HEq.{succ u1} (β (Sigma.fst.{u2, u1} α β x₀)) (Sigma.snd.{u2, u1} α β x₀) (β (Sigma.fst.{u2, u1} α β x₁)) (Sigma.snd.{u2, u1} α β x₁)) -> (Eq.{max (succ u2) (succ u1)} (Sigma.{u2, u1} α β) x₀ x₁)
Case conversion may be inaccurate. Consider using '#align sigma.ext Sigma.extₓ'. -/
@[ext]
theorem ext {x₀ x₁ : Sigma β} (h₀ : x₀.1 = x₁.1) (h₁ : HEq x₀.2 x₁.2) : x₀ = x₁ :=
  by
  cases x₀
  cases x₁
  cases h₀
  cases h₁
  rfl
#align sigma.ext Sigma.ext

/- warning: sigma.ext_iff -> Sigma.ext_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {x₀ : Sigma.{u1, u2} α β} {x₁ : Sigma.{u1, u2} α β}, Iff (Eq.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α β) x₀ x₁) (And (Eq.{succ u1} α (Sigma.fst.{u1, u2} α β x₀) (Sigma.fst.{u1, u2} α β x₁)) (HEq.{succ u2} (β (Sigma.fst.{u1, u2} α β x₀)) (Sigma.snd.{u1, u2} α β x₀) (β (Sigma.fst.{u1, u2} α β x₁)) (Sigma.snd.{u1, u2} α β x₁)))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {x₀ : Sigma.{u2, u1} α β} {x₁ : Sigma.{u2, u1} α β}, Iff (Eq.{max (succ u2) (succ u1)} (Sigma.{u2, u1} α β) x₀ x₁) (And (Eq.{succ u2} α (Sigma.fst.{u2, u1} α β x₀) (Sigma.fst.{u2, u1} α β x₁)) (HEq.{succ u1} (β (Sigma.fst.{u2, u1} α β x₀)) (Sigma.snd.{u2, u1} α β x₀) (β (Sigma.fst.{u2, u1} α β x₁)) (Sigma.snd.{u2, u1} α β x₁)))
Case conversion may be inaccurate. Consider using '#align sigma.ext_iff Sigma.ext_iffₓ'. -/
theorem ext_iff {x₀ x₁ : Sigma β} : x₀ = x₁ ↔ x₀.1 = x₁.1 ∧ HEq x₀.2 x₁.2 :=
  by
  cases x₀
  cases x₁
  exact Sigma.mk.inj_iff
#align sigma.ext_iff Sigma.ext_iff

#print Sigma.subtype_ext /-
/-- A specialized ext lemma for equality of sigma types over an indexed subtype. -/
@[ext]
theorem subtype_ext {β : Type _} {p : α → β → Prop} :
    ∀ {x₀ x₁ : Σa, Subtype (p a)}, x₀.fst = x₁.fst → (x₀.snd : β) = x₁.snd → x₀ = x₁
  | ⟨a₀, b₀, hb₀⟩, ⟨a₁, b₁, hb₁⟩, rfl, rfl => rfl
#align sigma.subtype_ext Sigma.subtype_ext
-/

#print Sigma.subtype_ext_iff /-
theorem subtype_ext_iff {β : Type _} {p : α → β → Prop} {x₀ x₁ : Σa, Subtype (p a)} :
    x₀ = x₁ ↔ x₀.fst = x₁.fst ∧ (x₀.snd : β) = x₁.snd :=
  ⟨fun h => h ▸ ⟨rfl, rfl⟩, fun ⟨h₁, h₂⟩ => subtype_ext h₁ h₂⟩
#align sigma.subtype_ext_iff Sigma.subtype_ext_iff
-/

/- warning: sigma.forall -> Sigma.forall is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {p : (Sigma.{u1, u2} α (fun (a : α) => β a)) -> Prop}, Iff (forall (x : Sigma.{u1, u2} α (fun (a : α) => β a)), p x) (forall (a : α) (b : β a), p (Sigma.mk.{u1, u2} α (fun (a : α) => β a) a b))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {p : (Sigma.{u2, u1} α (fun (a : α) => β a)) -> Prop}, Iff (forall (x : Sigma.{u2, u1} α (fun (a : α) => β a)), p x) (forall (a : α) (b : β a), p (Sigma.mk.{u2, u1} α (fun (a : α) => β a) a b))
Case conversion may be inaccurate. Consider using '#align sigma.forall Sigma.forallₓ'. -/
@[simp]
theorem forall {p : (Σa, β a) → Prop} : (∀ x, p x) ↔ ∀ a b, p ⟨a, b⟩ :=
  ⟨fun h a b => h ⟨a, b⟩, fun h ⟨a, b⟩ => h a b⟩
#align sigma.forall Sigma.forall

/- warning: sigma.exists -> Sigma.exists is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {p : (Sigma.{u1, u2} α (fun (a : α) => β a)) -> Prop}, Iff (Exists.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α (fun (a : α) => β a)) (fun (x : Sigma.{u1, u2} α (fun (a : α) => β a)) => p x)) (Exists.{succ u1} α (fun (a : α) => Exists.{succ u2} (β a) (fun (b : β a) => p (Sigma.mk.{u1, u2} α (fun (a : α) => β a) a b))))
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {p : (Sigma.{u2, u1} α (fun (a : α) => β a)) -> Prop}, Iff (Exists.{max (succ u2) (succ u1)} (Sigma.{u2, u1} α (fun (a : α) => β a)) (fun (x : Sigma.{u2, u1} α (fun (a : α) => β a)) => p x)) (Exists.{succ u2} α (fun (a : α) => Exists.{succ u1} (β a) (fun (b : β a) => p (Sigma.mk.{u2, u1} α (fun (a : α) => β a) a b))))
Case conversion may be inaccurate. Consider using '#align sigma.exists Sigma.existsₓ'. -/
@[simp]
theorem exists {p : (Σa, β a) → Prop} : (∃ x, p x) ↔ ∃ a b, p ⟨a, b⟩ :=
  ⟨fun ⟨⟨a, b⟩, h⟩ => ⟨a, b, h⟩, fun ⟨a, b, h⟩ => ⟨⟨a, b⟩, h⟩⟩
#align sigma.exists Sigma.exists

#print Sigma.map /-
/-- Map the left and right components of a sigma -/
def map (f₁ : α₁ → α₂) (f₂ : ∀ a, β₁ a → β₂ (f₁ a)) (x : Sigma β₁) : Sigma β₂ :=
  ⟨f₁ x.1, f₂ x.1 x.2⟩
#align sigma.map Sigma.map
-/

end Sigma

#print sigma_mk_injective /-
theorem sigma_mk_injective {i : α} : Function.Injective (@Sigma.mk α β i)
  | _, _, rfl => rfl
#align sigma_mk_injective sigma_mk_injective
-/

/- warning: function.injective.sigma_map -> Function.Injective.sigma_map is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {β₁ : α₁ -> Type.{u3}} {β₂ : α₂ -> Type.{u4}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{succ u1, succ u2} α₁ α₂ f₁) -> (forall (a : α₁), Function.Injective.{succ u3, succ u4} (β₁ a) (β₂ (f₁ a)) (f₂ a)) -> (Function.Injective.{max (succ u1) (succ u3), max (succ u2) (succ u4)} (Sigma.{u1, u3} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u2, u4} α₂ β₂) (Sigma.map.{u1, u2, u3, u4} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂))
but is expected to have type
  forall {α₁ : Type.{u4}} {α₂ : Type.{u3}} {β₁ : α₁ -> Type.{u2}} {β₂ : α₂ -> Type.{u1}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{succ u4, succ u3} α₁ α₂ f₁) -> (forall (a : α₁), Function.Injective.{succ u2, succ u1} (β₁ a) (β₂ (f₁ a)) (f₂ a)) -> (Function.Injective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Sigma.{u4, u2} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u3, u1} α₂ β₂) (Sigma.map.{u4, u3, u2, u1} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂))
Case conversion may be inaccurate. Consider using '#align function.injective.sigma_map Function.Injective.sigma_mapₓ'. -/
theorem Function.Injective.sigma_map {f₁ : α₁ → α₂} {f₂ : ∀ a, β₁ a → β₂ (f₁ a)}
    (h₁ : Function.Injective f₁) (h₂ : ∀ a, Function.Injective (f₂ a)) :
    Function.Injective (Sigma.map f₁ f₂)
  | ⟨i, x⟩, ⟨j, y⟩, h => by
    obtain rfl : i = j; exact h₁ (sigma.mk.inj_iff.mp h).1
    obtain rfl : x = y; exact h₂ i (sigma_mk_injective h)
    rfl
#align function.injective.sigma_map Function.Injective.sigma_map

/- warning: function.injective.of_sigma_map -> Function.Injective.of_sigma_map is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {β₁ : α₁ -> Type.{u3}} {β₂ : α₂ -> Type.{u4}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{max (succ u1) (succ u3), max (succ u2) (succ u4)} (Sigma.{u1, u3} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u2, u4} α₂ β₂) (Sigma.map.{u1, u2, u3, u4} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂)) -> (forall (a : α₁), Function.Injective.{succ u3, succ u4} (β₁ a) (β₂ (f₁ a)) (f₂ a))
but is expected to have type
  forall {α₁ : Type.{u3}} {α₂ : Type.{u1}} {β₁ : α₁ -> Type.{u4}} {β₂ : α₂ -> Type.{u2}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{max (succ u4) (succ u3), max (succ u2) (succ u1)} (Sigma.{u3, u4} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u1, u2} α₂ β₂) (Sigma.map.{u3, u1, u4, u2} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂)) -> (forall (a : α₁), Function.Injective.{succ u4, succ u2} (β₁ a) (β₂ (f₁ a)) (f₂ a))
Case conversion may be inaccurate. Consider using '#align function.injective.of_sigma_map Function.Injective.of_sigma_mapₓ'. -/
theorem Function.Injective.of_sigma_map {f₁ : α₁ → α₂} {f₂ : ∀ a, β₁ a → β₂ (f₁ a)}
    (h : Function.Injective (Sigma.map f₁ f₂)) (a : α₁) : Function.Injective (f₂ a) :=
  fun x y hxy => sigma_mk_injective <| @h ⟨a, x⟩ ⟨a, y⟩ (Sigma.ext rfl (heq_iff_eq.2 hxy))
#align function.injective.of_sigma_map Function.Injective.of_sigma_map

/- warning: function.injective.sigma_map_iff -> Function.Injective.sigma_map_iff is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {β₁ : α₁ -> Type.{u3}} {β₂ : α₂ -> Type.{u4}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{succ u1, succ u2} α₁ α₂ f₁) -> (Iff (Function.Injective.{max (succ u1) (succ u3), max (succ u2) (succ u4)} (Sigma.{u1, u3} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u2, u4} α₂ β₂) (Sigma.map.{u1, u2, u3, u4} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂)) (forall (a : α₁), Function.Injective.{succ u3, succ u4} (β₁ a) (β₂ (f₁ a)) (f₂ a)))
but is expected to have type
  forall {α₁ : Type.{u4}} {α₂ : Type.{u3}} {β₁ : α₁ -> Type.{u2}} {β₂ : α₂ -> Type.{u1}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Injective.{succ u4, succ u3} α₁ α₂ f₁) -> (Iff (Function.Injective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Sigma.{u4, u2} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u3, u1} α₂ β₂) (Sigma.map.{u4, u3, u2, u1} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂)) (forall (a : α₁), Function.Injective.{succ u2, succ u1} (β₁ a) (β₂ (f₁ a)) (f₂ a)))
Case conversion may be inaccurate. Consider using '#align function.injective.sigma_map_iff Function.Injective.sigma_map_iffₓ'. -/
theorem Function.Injective.sigma_map_iff {f₁ : α₁ → α₂} {f₂ : ∀ a, β₁ a → β₂ (f₁ a)}
    (h₁ : Function.Injective f₁) :
    Function.Injective (Sigma.map f₁ f₂) ↔ ∀ a, Function.Injective (f₂ a) :=
  ⟨fun h => h.of_sigma_map, h₁.sigma_map⟩
#align function.injective.sigma_map_iff Function.Injective.sigma_map_iff

/- warning: function.surjective.sigma_map -> Function.Surjective.sigma_map is a dubious translation:
lean 3 declaration is
  forall {α₁ : Type.{u1}} {α₂ : Type.{u2}} {β₁ : α₁ -> Type.{u3}} {β₂ : α₂ -> Type.{u4}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Surjective.{succ u1, succ u2} α₁ α₂ f₁) -> (forall (a : α₁), Function.Surjective.{succ u3, succ u4} (β₁ a) (β₂ (f₁ a)) (f₂ a)) -> (Function.Surjective.{max (succ u1) (succ u3), max (succ u2) (succ u4)} (Sigma.{u1, u3} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u2, u4} α₂ β₂) (Sigma.map.{u1, u2, u3, u4} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂))
but is expected to have type
  forall {α₁ : Type.{u4}} {α₂ : Type.{u3}} {β₁ : α₁ -> Type.{u2}} {β₂ : α₂ -> Type.{u1}} {f₁ : α₁ -> α₂} {f₂ : forall (a : α₁), (β₁ a) -> (β₂ (f₁ a))}, (Function.Surjective.{succ u4, succ u3} α₁ α₂ f₁) -> (forall (a : α₁), Function.Surjective.{succ u2, succ u1} (β₁ a) (β₂ (f₁ a)) (f₂ a)) -> (Function.Surjective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Sigma.{u4, u2} α₁ (fun (a : α₁) => β₁ a)) (Sigma.{u3, u1} α₂ β₂) (Sigma.map.{u4, u3, u2, u1} α₁ α₂ (fun (a : α₁) => β₁ a) β₂ f₁ f₂))
Case conversion may be inaccurate. Consider using '#align function.surjective.sigma_map Function.Surjective.sigma_mapₓ'. -/
theorem Function.Surjective.sigma_map {f₁ : α₁ → α₂} {f₂ : ∀ a, β₁ a → β₂ (f₁ a)}
    (h₁ : Function.Surjective f₁) (h₂ : ∀ a, Function.Surjective (f₂ a)) :
    Function.Surjective (Sigma.map f₁ f₂) :=
  by
  simp only [Function.Surjective, Sigma.forall, h₁.forall]
  exact fun i => (h₂ _).forall.2 fun x => ⟨⟨i, x⟩, rfl⟩
#align function.surjective.sigma_map Function.Surjective.sigma_map

#print Sigma.curry /-
/-- Interpret a function on `Σ x : α, β x` as a dependent function with two arguments.

This also exists as an `equiv` as `equiv.Pi_curry γ`. -/
def Sigma.curry {γ : ∀ a, β a → Type _} (f : ∀ x : Sigma β, γ x.1 x.2) (x : α) (y : β x) : γ x y :=
  f ⟨x, y⟩
#align sigma.curry Sigma.curry
-/

#print Sigma.uncurry /-
/-- Interpret a dependent function with two arguments as a function on `Σ x : α, β x`.

This also exists as an `equiv` as `(equiv.Pi_curry γ).symm`. -/
def Sigma.uncurry {γ : ∀ a, β a → Type _} (f : ∀ (x) (y : β x), γ x y) (x : Sigma β) : γ x.1 x.2 :=
  f x.1 x.2
#align sigma.uncurry Sigma.uncurry
-/

/- warning: sigma.uncurry_curry -> Sigma.uncurry_curry is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {γ : forall (a : α), (β a) -> Type.{u3}} (f : forall (x : Sigma.{u1, u2} α β), γ (Sigma.fst.{u1, u2} α β x) (Sigma.snd.{u1, u2} α β x)), Eq.{max (max (succ u1) (succ u2)) (succ u3)} (forall (x : Sigma.{u1, u2} α (fun (x : α) => β x)), γ (Sigma.fst.{u1, u2} α (fun (x : α) => β x) x) (Sigma.snd.{u1, u2} α (fun (x : α) => β x) x)) (Sigma.uncurry.{u1, u2, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) (Sigma.curry.{u1, u2, u3} α β γ f)) f
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {γ : forall (a : α), (β a) -> Type.{u3}} (f : forall (x : Sigma.{u2, u1} α β), γ (Sigma.fst.{u2, u1} α β x) (Sigma.snd.{u2, u1} α β x)), Eq.{max (max (succ u2) (succ u1)) (succ u3)} (forall (x : Sigma.{u2, u1} α (fun (x : α) => β x)), γ (Sigma.fst.{u2, u1} α (fun (x : α) => β x) x) (Sigma.snd.{u2, u1} α (fun (x : α) => β x) x)) (Sigma.uncurry.{u2, u1, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) (Sigma.curry.{u2, u1, u3} α β γ f)) f
Case conversion may be inaccurate. Consider using '#align sigma.uncurry_curry Sigma.uncurry_curryₓ'. -/
@[simp]
theorem Sigma.uncurry_curry {γ : ∀ a, β a → Type _} (f : ∀ x : Sigma β, γ x.1 x.2) :
    Sigma.uncurry (Sigma.curry f) = f :=
  funext fun ⟨i, j⟩ => rfl
#align sigma.uncurry_curry Sigma.uncurry_curry

/- warning: sigma.curry_uncurry -> Sigma.curry_uncurry is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : α -> Type.{u2}} {γ : forall (a : α), (β a) -> Type.{u3}} (f : forall (x : α) (y : β x), γ x y), Eq.{max (succ u1) (succ u2) (succ u3)} (forall (x : α) (y : β x), γ x y) (Sigma.curry.{u1, u2, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) (Sigma.uncurry.{u1, u2, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) f)) f
but is expected to have type
  forall {α : Type.{u2}} {β : α -> Type.{u1}} {γ : forall (a : α), (β a) -> Type.{u3}} (f : forall (x : α) (y : β x), γ x y), Eq.{max (max (succ u2) (succ u1)) (succ u3)} (forall (x : α) (y : β x), γ x y) (Sigma.curry.{u2, u1, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) (Sigma.uncurry.{u2, u1, u3} α (fun (x : α) => β x) (fun (x : α) (y : β x) => γ x y) f)) f
Case conversion may be inaccurate. Consider using '#align sigma.curry_uncurry Sigma.curry_uncurryₓ'. -/
@[simp]
theorem Sigma.curry_uncurry {γ : ∀ a, β a → Type _} (f : ∀ (x) (y : β x), γ x y) :
    Sigma.curry (Sigma.uncurry f) = f :=
  rfl
#align sigma.curry_uncurry Sigma.curry_uncurry

#print Prod.toSigma /-
/-- Convert a product type to a Σ-type. -/
def Prod.toSigma {α β} (p : α × β) : Σ_ : α, β :=
  ⟨p.1, p.2⟩
#align prod.to_sigma Prod.toSigma
-/

/- warning: prod.fst_comp_to_sigma -> Prod.fst_comp_toSigma is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ u1) (succ u2)} ((Prod.{u1, u2} α β) -> α) (Function.comp.{max (succ u1) (succ u2), max (succ u1) (succ u2), succ u1} (Prod.{u1, u2} α β) (Sigma.{u1, u2} α (fun (_x : α) => β)) α (Sigma.fst.{u1, u2} α (fun (_x : α) => β)) (Prod.toSigma.{u1, u2} α β)) (Prod.fst.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u1) (succ u2)} ((Prod.{u2, u1} α β) -> α) (Function.comp.{max (succ u1) (succ u2), max (succ u1) (succ u2), succ u2} (Prod.{u2, u1} α β) (Sigma.{u2, u1} α (fun (_x : α) => β)) α (Sigma.fst.{u2, u1} α (fun (_x : α) => β)) (Prod.toSigma.{u2, u1} α β)) (Prod.fst.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.fst_comp_to_sigma Prod.fst_comp_toSigmaₓ'. -/
@[simp]
theorem Prod.fst_comp_toSigma {α β} : Sigma.fst ∘ @Prod.toSigma α β = Prod.fst :=
  rfl
#align prod.fst_comp_to_sigma Prod.fst_comp_toSigma

/- warning: prod.fst_to_sigma -> Prod.fst_toSigma is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (x : Prod.{u1, u2} α β), Eq.{succ u1} α (Sigma.fst.{u1, u2} α (fun (_x : α) => β) (Prod.toSigma.{u1, u2} α β x)) (Prod.fst.{u1, u2} α β x)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (x : Prod.{u2, u1} α β), Eq.{succ u2} α (Sigma.fst.{u2, u1} α (fun (_x : α) => β) (Prod.toSigma.{u2, u1} α β x)) (Prod.fst.{u2, u1} α β x)
Case conversion may be inaccurate. Consider using '#align prod.fst_to_sigma Prod.fst_toSigmaₓ'. -/
@[simp]
theorem Prod.fst_toSigma {α β} (x : α × β) : (Prod.toSigma x).fst = x.fst :=
  rfl
#align prod.fst_to_sigma Prod.fst_toSigma

/- warning: prod.snd_to_sigma -> Prod.snd_toSigma is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (x : Prod.{u1, u2} α β), Eq.{succ u2} β (Sigma.snd.{u1, u2} α (fun (_x : α) => β) (Prod.toSigma.{u1, u2} α β x)) (Prod.snd.{u1, u2} α β x)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (x : Prod.{u2, u1} α β), Eq.{succ u1} β (Sigma.snd.{u2, u1} α (fun (_x : α) => β) (Prod.toSigma.{u2, u1} α β x)) (Prod.snd.{u2, u1} α β x)
Case conversion may be inaccurate. Consider using '#align prod.snd_to_sigma Prod.snd_toSigmaₓ'. -/
@[simp]
theorem Prod.snd_toSigma {α β} (x : α × β) : (Prod.toSigma x).snd = x.snd :=
  rfl
#align prod.snd_to_sigma Prod.snd_toSigma

/- warning: prod.to_sigma_mk -> Prod.toSigma_mk is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (x : α) (y : β), Eq.{max (succ u1) (succ u2)} (Sigma.{u1, u2} α (fun (_x : α) => β)) (Prod.toSigma.{u1, u2} α β (Prod.mk.{u1, u2} α β x y)) (Sigma.mk.{u1, u2} α (fun (_x : α) => β) x y)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (x : α) (y : β), Eq.{max (succ u1) (succ u2)} (Sigma.{u2, u1} α (fun (_x : α) => β)) (Prod.toSigma.{u2, u1} α β (Prod.mk.{u2, u1} α β x y)) (Sigma.mk.{u2, u1} α (fun (_x : α) => β) x y)
Case conversion may be inaccurate. Consider using '#align prod.to_sigma_mk Prod.toSigma_mkₓ'. -/
@[simp]
theorem Prod.toSigma_mk {α β} (x : α) (y : β) : (x, y).toSigma = ⟨x, y⟩ :=
  rfl
#align prod.to_sigma_mk Prod.toSigma_mk

/- ./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `reflect_name #[] -/
-- we generate this manually as `@[derive has_reflect]` fails
@[instance]
protected unsafe def sigma.reflect.{u, v} [reflected_univ.{u}] [reflected_univ.{v}] {α : Type u}
    (β : α → Type v) [reflected _ α] [reflected _ β] [hα : has_reflect α]
    [hβ : ∀ i, has_reflect (β i)] : has_reflect (Σa, β a) := fun ⟨a, b⟩ =>
  (by
        trace
          "./././Mathport/Syntax/Translate/Tactic/Builtin.lean:73:14: unsupported tactic `reflect_name #[]" :
        reflected _ @Sigma.mk.{u, v}).subst₄
    q(α) q(β) q(a) q(b)
#align sigma.reflect sigma.reflect

end Sigma

section PSigma

variable {α : Sort _} {β : α → Sort _}

namespace PSigma

#print PSigma.elim /-
/-- Nondependent eliminator for `psigma`. -/
def elim {γ} (f : ∀ a, β a → γ) (a : PSigma β) : γ :=
  PSigma.casesOn a f
#align psigma.elim PSigma.elim
-/

/- warning: psigma.elim_val -> PSigma.elim_val is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {γ : Sort.{u3}} (f : forall (a : α), (β a) -> γ) (a : α) (b : β a), Eq.{u3} γ (PSigma.elim.{u1, u2, u3} α (fun (a : α) => β a) γ f (PSigma.mk.{u1, u2} α (fun (a : α) => β a) a b)) (f a b)
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {γ : Sort.{u3}} (f : forall (a : α), (β a) -> γ) (a : α) (b : β a), Eq.{u3} γ (PSigma.elim.{u2, u1, u3} α (fun (a : α) => β a) γ f (PSigma.mk.{u2, u1} α (fun (a : α) => β a) a b)) (f a b)
Case conversion may be inaccurate. Consider using '#align psigma.elim_val PSigma.elim_valₓ'. -/
@[simp]
theorem elim_val {γ} (f : ∀ a, β a → γ) (a b) : PSigma.elim f ⟨a, b⟩ = f a b :=
  rfl
#align psigma.elim_val PSigma.elim_val

instance [Inhabited α] [Inhabited (β default)] : Inhabited (PSigma β) :=
  ⟨⟨default, default⟩⟩

instance [h₁ : DecidableEq α] [h₂ : ∀ a, DecidableEq (β a)] : DecidableEq (PSigma β)
  | ⟨a₁, b₁⟩, ⟨a₂, b₂⟩ =>
    match a₁, b₁, a₂, b₂, h₁ a₁ a₂ with
    | _, b₁, _, b₂, is_true (Eq.refl a) =>
      match b₁, b₂, h₂ a b₁ b₂ with
      | _, _, is_true (Eq.refl b) => isTrue rfl
      | b₁, b₂, is_false n => isFalse fun h => PSigma.noConfusion h fun e₁ e₂ => n <| eq_of_hEq e₂
    | a₁, _, a₂, _, is_false n => isFalse fun h => PSigma.noConfusion h fun e₁ e₂ => n e₁

/- warning: psigma.mk.inj_iff -> PSigma.mk.inj_iff is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {a₁ : α} {a₂ : α} {b₁ : β a₁} {b₂ : β a₂}, Iff (Eq.{max 1 u1 u2} (PSigma.{u1, u2} α β) (PSigma.mk.{u1, u2} α β a₁ b₁) (PSigma.mk.{u1, u2} α β a₂ b₂)) (And (Eq.{u1} α a₁ a₂) (HEq.{u2} (β a₁) b₁ (β a₂) b₂))
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {a₁ : α} {a₂ : α} {b₁ : β a₁} {b₂ : β a₂}, Iff (Eq.{max (max 1 u2) u1} (PSigma.{u2, u1} α β) (PSigma.mk.{u2, u1} α β a₁ b₁) (PSigma.mk.{u2, u1} α β a₂ b₂)) (And (Eq.{u2} α a₁ a₂) (HEq.{u1} (β a₁) b₁ (β a₂) b₂))
Case conversion may be inaccurate. Consider using '#align psigma.mk.inj_iff PSigma.mk.inj_iffₓ'. -/
theorem mk.inj_iff {a₁ a₂ : α} {b₁ : β a₁} {b₂ : β a₂} :
    @PSigma.mk α β a₁ b₁ = @PSigma.mk α β a₂ b₂ ↔ a₁ = a₂ ∧ HEq b₁ b₂ :=
  Iff.intro PSigma.mk.inj fun ⟨h₁, h₂⟩ =>
    match a₁, a₂, b₁, b₂, h₁, h₂ with
    | _, _, _, _, Eq.refl a, HEq.refl b => rfl
#align psigma.mk.inj_iff PSigma.mk.inj_iff

/- warning: psigma.ext -> PSigma.ext is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {x₀ : PSigma.{u1, u2} α β} {x₁ : PSigma.{u1, u2} α β}, (Eq.{u1} α (PSigma.fst.{u1, u2} α β x₀) (PSigma.fst.{u1, u2} α β x₁)) -> (HEq.{u2} (β (PSigma.fst.{u1, u2} α β x₀)) (PSigma.snd.{u1, u2} α β x₀) (β (PSigma.fst.{u1, u2} α β x₁)) (PSigma.snd.{u1, u2} α β x₁)) -> (Eq.{max 1 u1 u2} (PSigma.{u1, u2} α β) x₀ x₁)
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {x₀ : PSigma.{u2, u1} α β} {x₁ : PSigma.{u2, u1} α β}, (Eq.{u2} α (PSigma.fst.{u2, u1} α β x₀) (PSigma.fst.{u2, u1} α β x₁)) -> (HEq.{u1} (β (PSigma.fst.{u2, u1} α β x₀)) (PSigma.snd.{u2, u1} α β x₀) (β (PSigma.fst.{u2, u1} α β x₁)) (PSigma.snd.{u2, u1} α β x₁)) -> (Eq.{max (max 1 u2) u1} (PSigma.{u2, u1} α β) x₀ x₁)
Case conversion may be inaccurate. Consider using '#align psigma.ext PSigma.extₓ'. -/
@[ext]
theorem ext {x₀ x₁ : PSigma β} (h₀ : x₀.1 = x₁.1) (h₁ : HEq x₀.2 x₁.2) : x₀ = x₁ :=
  by
  cases x₀
  cases x₁
  cases h₀
  cases h₁
  rfl
#align psigma.ext PSigma.ext

/- warning: psigma.ext_iff -> PSigma.ext_iff is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {x₀ : PSigma.{u1, u2} α β} {x₁ : PSigma.{u1, u2} α β}, Iff (Eq.{max 1 u1 u2} (PSigma.{u1, u2} α β) x₀ x₁) (And (Eq.{u1} α (PSigma.fst.{u1, u2} α β x₀) (PSigma.fst.{u1, u2} α β x₁)) (HEq.{u2} (β (PSigma.fst.{u1, u2} α β x₀)) (PSigma.snd.{u1, u2} α β x₀) (β (PSigma.fst.{u1, u2} α β x₁)) (PSigma.snd.{u1, u2} α β x₁)))
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {x₀ : PSigma.{u2, u1} α β} {x₁ : PSigma.{u2, u1} α β}, Iff (Eq.{max (max 1 u2) u1} (PSigma.{u2, u1} α β) x₀ x₁) (And (Eq.{u2} α (PSigma.fst.{u2, u1} α β x₀) (PSigma.fst.{u2, u1} α β x₁)) (HEq.{u1} (β (PSigma.fst.{u2, u1} α β x₀)) (PSigma.snd.{u2, u1} α β x₀) (β (PSigma.fst.{u2, u1} α β x₁)) (PSigma.snd.{u2, u1} α β x₁)))
Case conversion may be inaccurate. Consider using '#align psigma.ext_iff PSigma.ext_iffₓ'. -/
theorem ext_iff {x₀ x₁ : PSigma β} : x₀ = x₁ ↔ x₀.1 = x₁.1 ∧ HEq x₀.2 x₁.2 :=
  by
  cases x₀
  cases x₁
  exact PSigma.mk.inj_iff
#align psigma.ext_iff PSigma.ext_iff

/- warning: psigma.forall -> PSigma.forall is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {p : (PSigma.{u1, u2} α (fun (a : α) => β a)) -> Prop}, Iff (forall (x : PSigma.{u1, u2} α (fun (a : α) => β a)), p x) (forall (a : α) (b : β a), p (PSigma.mk.{u1, u2} α (fun (a : α) => β a) a b))
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {p : (PSigma.{u2, u1} α (fun (a : α) => β a)) -> Prop}, Iff (forall (x : PSigma.{u2, u1} α (fun (a : α) => β a)), p x) (forall (a : α) (b : β a), p (PSigma.mk.{u2, u1} α (fun (a : α) => β a) a b))
Case conversion may be inaccurate. Consider using '#align psigma.forall PSigma.forallₓ'. -/
@[simp]
theorem forall {p : (Σ'a, β a) → Prop} : (∀ x, p x) ↔ ∀ a b, p ⟨a, b⟩ :=
  ⟨fun h a b => h ⟨a, b⟩, fun h ⟨a, b⟩ => h a b⟩
#align psigma.forall PSigma.forall

/- warning: psigma.exists -> PSigma.exists is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : α -> Sort.{u2}} {p : (PSigma.{u1, u2} α (fun (a : α) => β a)) -> Prop}, Iff (Exists.{max 1 u1 u2} (PSigma.{u1, u2} α (fun (a : α) => β a)) (fun (x : PSigma.{u1, u2} α (fun (a : α) => β a)) => p x)) (Exists.{u1} α (fun (a : α) => Exists.{u2} (β a) (fun (b : β a) => p (PSigma.mk.{u1, u2} α (fun (a : α) => β a) a b))))
but is expected to have type
  forall {α : Sort.{u2}} {β : α -> Sort.{u1}} {p : (PSigma.{u2, u1} α (fun (a : α) => β a)) -> Prop}, Iff (Exists.{max (max 1 u2) u1} (PSigma.{u2, u1} α (fun (a : α) => β a)) (fun (x : PSigma.{u2, u1} α (fun (a : α) => β a)) => p x)) (Exists.{u2} α (fun (a : α) => Exists.{u1} (β a) (fun (b : β a) => p (PSigma.mk.{u2, u1} α (fun (a : α) => β a) a b))))
Case conversion may be inaccurate. Consider using '#align psigma.exists PSigma.existsₓ'. -/
@[simp]
theorem exists {p : (Σ'a, β a) → Prop} : (∃ x, p x) ↔ ∃ a b, p ⟨a, b⟩ :=
  ⟨fun ⟨⟨a, b⟩, h⟩ => ⟨a, b, h⟩, fun ⟨a, b, h⟩ => ⟨⟨a, b⟩, h⟩⟩
#align psigma.exists PSigma.exists

#print PSigma.subtype_ext /-
/-- A specialized ext lemma for equality of psigma types over an indexed subtype. -/
@[ext]
theorem subtype_ext {β : Sort _} {p : α → β → Prop} :
    ∀ {x₀ x₁ : Σ'a, Subtype (p a)}, x₀.fst = x₁.fst → (x₀.snd : β) = x₁.snd → x₀ = x₁
  | ⟨a₀, b₀, hb₀⟩, ⟨a₁, b₁, hb₁⟩, rfl, rfl => rfl
#align psigma.subtype_ext PSigma.subtype_ext
-/

#print PSigma.subtype_ext_iff /-
theorem subtype_ext_iff {β : Sort _} {p : α → β → Prop} {x₀ x₁ : Σ'a, Subtype (p a)} :
    x₀ = x₁ ↔ x₀.fst = x₁.fst ∧ (x₀.snd : β) = x₁.snd :=
  ⟨fun h => h ▸ ⟨rfl, rfl⟩, fun ⟨h₁, h₂⟩ => subtype_ext h₁ h₂⟩
#align psigma.subtype_ext_iff PSigma.subtype_ext_iff
-/

variable {α₁ : Sort _} {α₂ : Sort _} {β₁ : α₁ → Sort _} {β₂ : α₂ → Sort _}

#print PSigma.map /-
/-- Map the left and right components of a sigma -/
def map (f₁ : α₁ → α₂) (f₂ : ∀ a, β₁ a → β₂ (f₁ a)) : PSigma β₁ → PSigma β₂
  | ⟨a, b⟩ => ⟨f₁ a, f₂ a b⟩
#align psigma.map PSigma.map
-/

end PSigma

end PSigma


/-
Copyright (c) 2021 Ashvni Narayanan. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ashvni Narayanan
-/
import norm_properties
import nat_properties
import misc
import number_theory.bernoulli_polynomials
/-!
# Theorems regarding sums of even characters
This file describes theorems of convergence properties of certain sums twisted
by even Dirichlet characters. These are proved separately over `zmod (d * p^n)`
and `(zmod (d * p^n))ˣ`, for `d` coprime to the prime `p`.

## Main theorems
 * `sum_even_character_tendsto_zero`
 * `sum_even_character_tendsto_zero_of_units`

## References
Introduction to Cyclotomic Fields, Washington (Chapter 7, Lemma 7.11)

## Tags
p-adic, L-function, Dirichlet character
-/
-- `mul_eval_coprime` replaced with `mul_eval_of_coprime`
-- `lev_mul_dvd` replaced with `lev_mul_dvd_lcm`
-- `mul_eval_coprime'` replaced with `mul_eval_neg_one`
-- `teichmuller_character_mod_p_change_level_pow` replaced with `dirichlet_character.pow_apply`
-- `teichmuller_character_mod_p_eval_neg_one` replaced with `teichmuller_character.eval_neg_one`
-- removed `asso_dc`

open dirichlet_character zmod
variables {p d m : nat} [fact (nat.prime p)] [fact (0 < d)] {R : Type*} [normed_comm_ring R]
  {χ : dirichlet_character R (d * p^m)}
open_locale big_operators
--set_option pp.proofs true
open dirichlet_character teichmuller_character

-- choosing `teichmuller_character_mod_p_change_level'` as easiest to work with?
lemma sum_eq_neg_sum_add_dvd (hχ : χ.is_even) [algebra ℚ_[p] R] [nontrivial R]
  [no_zero_divisors R] [fact (0 < m)] (hp : 2 < p) {k : ℕ} (hk : 1 ≤ k) {x : ℕ} (hx : m ≤ x) :
  ∑ (i : ℕ) in finset.range (d * p ^ x).succ, (asso_dirichlet_character (χ.mul
  (teichmuller_character_mod_p' p R ^ k))) ↑i * ↑i ^ (k - 1) = -1 *
  ∑ (y : ℕ) in finset.range (d * p ^ x + 1),
  (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y * ↑y ^ (k - 1) +
  ↑(d * p ^ x) * ∑ (y : ℕ) in finset.range (d * p ^ x + 1),
  (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) (-1) *
  ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y *
  ∑ (x_1 : ℕ) in finset.range (k - 1), ↑(d * p ^ x) ^ x_1 * ((-1) * ↑y) ^ (k - 1 - (x_1 + 1)) *
  ↑((k - 1).choose (x_1 + 1))) :=
begin
  have lev_mul_dvd : lev (χ.mul (teichmuller_character_mod_p' p R ^ k)) ∣ d * p^m,
  { apply dvd_trans (lev_mul_dvd_lcm _ _) _,
    rw helper_4, },
  rw ←finset.sum_flip,
  conv_lhs { apply_congr, skip, rw [nat.cast_sub (finset.mem_range_succ_iff.1 H),
    dirichlet_character.asso_dirichlet_character.eval_mul_sub' _ (dvd_trans lev_mul_dvd
    (mul_dvd_mul dvd_rfl (pow_dvd_pow _ hx)))],
    conv { congr, skip, rw [nat.cast_sub (finset.mem_range_succ_iff.1 H), sub_eq_add_neg,
      add_pow, finset.sum_range_succ', add_comm, pow_zero, one_mul, nat.sub_zero,
      nat.choose_zero_right, nat.cast_one, mul_one, neg_eq_neg_one_mul, mul_pow],
    congr, skip, apply_congr, skip, rw [pow_succ, mul_assoc ↑(d * p^x) _, mul_assoc ↑(d * p^x) _], },
    rw [←finset.mul_sum, mul_add, mul_mul_mul_comm, mul_mul_mul_comm _ _ ↑(d * p^x) _,
      mul_comm _ ↑(d * p^x), mul_assoc ↑(d * p^x) _ _], },
  rw [finset.sum_add_distrib, ←finset.mul_sum, ←finset.mul_sum],
  refine congr_arg2 _ (congr_arg2 _ _ _) rfl,
  { rw [←int.cast_one, ←int.cast_neg, mul_eval_neg_one, asso_even_dirichlet_character_eval_neg_one
      _ hχ, one_mul, asso_dirichlet_character_eq_char' _ (is_unit.neg (is_unit_one)),
      change_level_pow_eval_neg_one' k hp, units.coe_pow, units.coe_neg_one, ←pow_add,
      nat.add_sub_pred, odd.neg_one_pow _],
    { rw [nat.odd_iff, nat.two_mul_sub_one_mod_two_eq_one hk], },
    any_goals { apply_instance, }, },
  { rw ←finset.sum_flip, },
end

-- `sum_odd_char` replaced with `helper_11`
lemma helper_11 [nontrivial R] [no_zero_divisors R] [normed_algebra ℚ_[p] R]  [norm_one_class R]
 (na : ∀ (n : ℕ) (f : ℕ → R), ∥ ∑ (i : ℕ) in finset.range n, f i∥ ≤ ⨆ (i : zmod n), ∥f i.val∥)
 [fact (0 < m)] {k : ℕ} (hk : 1 < k) (hχ : χ.is_even) (hp : 2 < p) {x : ℕ} (hx : m ≤ x) :
 ∃ y, (2 : R) * ∑ i in finset.range (d * p^x), ((asso_dirichlet_character (χ.mul
  (teichmuller_character_mod_p' p R ^ k))) i * i^(k - 1)) = ↑(d * p^x) * y ∧ ∥y∥ ≤ ((χ.mul
  (teichmuller_character_mod_p' p R ^ k)).bound * (↑k - 1)) + ∥(((d * p ^ x : ℕ) : R) ^ (k - 2)) *
  (1 + 1)∥ * (χ.mul (teichmuller_character_mod_p' p R ^ k)).bound :=
begin
  have f1 : ∑ (i : ℕ) in finset.range (d * p ^ x), (asso_dirichlet_character (χ.mul
    (teichmuller_character_mod_p' p R ^ k))) ↑i * ↑i ^ (k - 1) =
    ∑ (i : ℕ) in finset.range (d * p ^ x).succ, (asso_dirichlet_character
    (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑i * ↑i ^ (k - 1)
   - ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑(d * p^x) *
   ↑(d * p^x) ^ (k - 1)),
  { rw [finset.sum_range_succ, add_sub_cancel], },
  rw [f1, mul_sub, mul_comm _ (↑(d * p ^ x) ^ (k - 1)), ←mul_assoc _ (↑(d * p ^ x) ^ (k - 1)) _,
    mul_comm _ (↑(d * p ^ x) ^ (k - 1)), mul_assoc _ (2 : R) _, ←nat.cast_pow],
  clear f1,
  conv { congr, funext, rw [sub_eq_iff_eq_add, @helper_5 p _ d _ k hk,
    nat.cast_mul (d * p^x) _, mul_assoc ↑(d * p^x) _ _],
    conv { congr, rw ←mul_add ↑(d * p^x) _ _, }, },
  have two_eq_one_add_one : (2 : R) = (1 : R) + (1 : R) := rfl,
  rw [two_eq_one_add_one, add_mul, one_mul],
  conv { congr, funext, conv { congr, to_lhs, congr, skip,
    rw sum_eq_neg_sum_add_dvd hχ hp (le_of_lt hk) hx, }, },
  rw [←neg_eq_neg_one_mul, ←add_assoc, ←sub_eq_add_neg],
  conv { congr, funext, rw [sub_self _, zero_add], },
  refine ⟨∑ (y : ℕ) in finset.range (d * p ^ x + 1),
    (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) (-1) *
    ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y *
    ∑ (x_1 : ℕ) in finset.range (k - 1),
    ↑(d * p ^ x) ^ x_1 * ((-1) * ↑y) ^ (k - 1 - (x_1 + 1)) * ↑((k - 1).choose (x_1 + 1))) -
    ↑((d * p ^ x) ^ (k - 2)) * ((1 + 1) * (asso_dirichlet_character (χ.mul
    (teichmuller_character_mod_p' p R ^ k))) ↑(d * p ^ x)), _, _⟩,
  { rw sub_add_cancel, },
  { apply le_trans (norm_sub_le _ _) _,
    conv { congr, congr, congr, apply_congr, skip, rw [←mul_assoc, ←monoid_hom.map_mul], },
    apply le_trans (add_le_add (norm_sum_le_smul hk na) (le_refl _)) _,
    rw ← mul_assoc,
    refine le_trans (add_le_add (le_refl _) (norm_mul_le _ _)) (le_trans (add_le_add (le_refl _)
      ((mul_le_mul_left _).2 (le_of_lt (dirichlet_character.lt_bound _ _)))) _),
    { haveI : algebra ℚ_[p] R, apply_instance, -- needed for char_zero
      haveI : char_zero R := char_zero_of_nontrivial_of_normed_algebra p R,
      refine lt_iff_le_and_ne.2 ⟨norm_nonneg _, λ h, _⟩,
      rw [eq_comm, norm_eq_zero, mul_eq_zero] at h,
      cases h,
      { refine pow_ne_zero _ (ne_zero_of_lt (nat.mul_prime_pow_pos _)) (nat.cast_eq_zero.1 h), },
      { apply zero_ne_one ((self_eq_neg R R).1 (eq_neg_iff_add_eq_zero.2 h)).symm, }, },
    { rw nat.cast_pow, }, },
end

-- `sum_even_character` replaced with `sum_even_character_tendsto_zero`
lemma sum_even_character_tendsto_zero [nontrivial R] [no_zero_divisors R] [normed_algebra ℚ_[p] R]
  [norm_one_class R] [fact (0 < m)] {k : ℕ} (hk : 1 < k) (hχ : χ.is_even) (hp : 2 < p)
  (na : ∀ (n : ℕ) (f : ℕ → R), ∥ ∑ (i : ℕ) in finset.range n, f i∥ ≤ ⨆ (i : zmod n), ∥f i.val∥) :
  filter.tendsto (λ n : nat, ∑ i in finset.range (d * p^n), ((asso_dirichlet_character
  (χ.mul (teichmuller_character_mod_p' p R ^ k))) i * i^(k - 1)) ) filter.at_top (nhds 0) :=
begin
  -- better way to do this with filters
  refine metric.tendsto_at_top.2 (λ ε hε, _),
  obtain ⟨z, hz⟩ := helper_10 χ hk hp hε,
  refine ⟨max z m, λ x hx, _⟩,
  cases helper_11 na hk hχ hp (max_le_iff.1 hx).2,
  rw [dist_eq_norm, sub_zero, helper_6 p h.1, norm_smul],
  apply lt_of_le_of_lt (mul_le_mul le_rfl (norm_mul_le _ _)
    (norm_nonneg (↑(d * p ^ x) * w)) (norm_nonneg _)) _,
  rw ← mul_assoc,
  apply lt_of_le_of_lt (mul_le_mul le_rfl h.2 (norm_nonneg _) (mul_nonneg (norm_nonneg _)
    (norm_nonneg _))) _,
  rw [mul_comm _ (k - 1 : ℝ), ←add_mul, mul_mul_mul_comm],
  apply lt_of_le_of_lt (mul_le_mul (mul_le_mul le_rfl (add_le_add le_rfl _) (helper_8 hk _)
    (norm_nonneg _)) (mul_le_mul (norm_mul_prime_pow_le_of_ge p R (le_trans (le_max_left _ _) hx))
    le_rfl (le_of_lt (dirichlet_character.bound_pos _)) (norm_nonneg _)) (mul_nonneg (norm_nonneg _)
    (le_of_lt (dirichlet_character.bound_pos _))) (mul_nonneg (norm_nonneg _) (helper_8 hk _))) hz,
-- refine is so much more powerful than apply, it captures instances of explicit vars very well, but not implicit
  have : ((2 : ℕ) : R) = 1 + 1,
  { simp only [nat.cast_bit0, nat.cast_one], refl, },
  simp_rw [←this, ←nat.cast_pow, norm_mul_nat_eq_mul_norm p R], -- better than repeat
  apply mul_le_mul _ le_rfl (norm_nonneg _) (norm_nonneg _),
  simp_rw [nat.cast_pow, norm_pow_nat_eq_pow_norm p R],
  refine pow_le_pow_of_le_left (norm_nonneg _) (norm_mul_prime_pow_le_of_ge p R (le_trans (le_max_left _ _) hx)) _,
end
-- btw, this still works without the na condition, since in the end, we divide by d*p^x

open filter dirichlet_character ring_hom

variables (p d R)
lemma helper_12 [algebra ℚ R] {k : ℕ} (hk : 1 < k) (x y : ℕ) :
  (algebra_map ℚ R) (((d * p ^ x : ℕ) : ℚ) ^ k) * (algebra_map ℚ R)
  (polynomial.eval (↑(y.succ) / ↑(d * p ^ x : ℕ)) (polynomial.bernoulli k)) =
  ((y + 1 : ℕ) : R)^k + ((algebra_map ℚ R) (bernoulli 1 * (k : ℚ))) * ((d * p^x : ℕ) : R) *
  ((y + 1 : ℕ) : R)^k.pred + (d * p^x : ℕ) * (∑ (x_1 : ℕ) in finset.range k.pred,
  (algebra_map ℚ R) (bernoulli (k.pred.succ - x_1) * ↑(k.pred.succ.choose x_1) *
  (((y + 1 : ℕ) : ℚ) ^ x_1 / ↑(d * p ^ x) ^ x_1) * ↑(d * p ^ x) ^ k.pred)) :=
begin
  rw [←(algebra_map ℚ R).map_mul, polynomial.bernoulli_def, polynomial.eval_finset_sum,
    finset.mul_sum],
  simp only [polynomial.eval_monomial, div_pow, nat.cast_succ],
  simp_rw [mul_comm (((d * p ^ x : ℕ) : ℚ) ^ k) _, mul_assoc],
  rw [finset.sum_range_succ_comm, div_mul_cancel _],
  { rw (algebra_map ℚ R).map_add,
    conv_lhs { congr, skip, rw ← nat.succ_pred_eq_of_pos (pos_of_gt hk),
      rw finset.sum_range_succ_comm, },
    rw [div_mul_comm, (algebra_map ℚ R).map_add, add_assoc],
    congr,
    { simp only [nat.choose_self, map_nat_cast, one_mul, map_add, nat.sub_self, bernoulli_zero,
        map_pow, map_one, nat.cast_one], },
    { rw [nat.choose_succ_self_right, ←nat.succ_eq_add_one, nat.succ_pred_eq_of_pos (pos_of_gt hk),
        nat.pred_eq_sub_one, div_eq_mul_inv,
        ←pow_sub₀ ((d * p^x : ℕ) : ℚ) (nat.cast_ne_zero.2 (nat.ne_zero_of_lt' 0)) (nat.sub_le k 1)],
      rw [nat.sub_sub_self (le_of_lt hk), pow_one, ←mul_assoc, (algebra_map ℚ R).map_mul],
      simp only [map_nat_cast, map_add, map_pow, map_one, map_mul], },
    { rw [map_sum, pow_succ'],
      conv_lhs { apply_congr, skip, rw [←mul_assoc, ←mul_assoc, ←mul_assoc,
        (algebra_map ℚ R).map_mul], },
      rw [←finset.sum_mul, mul_comm, map_nat_cast],
      conv_rhs { congr, skip, apply_congr, skip, rw [←mul_assoc, ←mul_assoc], }, }, },
  { norm_cast,
    refine pow_ne_zero _ (nat.ne_zero_of_lt' 0), },
end

variables {p d R}
variables {p d R}
lemma helper_15 [nontrivial R] [algebra ℚ R] [normed_algebra ℚ_[p] R] [norm_one_class R]
  (na : ∀ (n : ℕ) (f : ℕ → R), ∥ ∑ (i : ℕ) in finset.range n, f i∥ ≤ ⨆ (i : zmod n), ∥f i.val∥)
  {k : ℕ} (hk : 1 < k) (x y : ℕ) : ∥(∑ (x_1 : ℕ) in finset.range k.pred,
  (algebra_map ℚ R) (bernoulli (k.pred.succ - x_1) * ↑(k.pred.succ.choose x_1) *
  (((y + 1 : ℕ) : ℚ) ^ x_1 / ↑(d * p ^ x) ^ x_1) * ↑(d * p ^ x) ^ k.pred))∥ ≤
  ∥((d * p^x : ℕ) : R)∥ * (⨆ (x_1 : zmod k.pred), (∥(algebra_map ℚ R) (bernoulli (k.pred.succ - x_1.val) *
  ↑(k.pred.succ.choose x_1.val) )∥)) :=
begin
  have le : k.pred = k.pred - 1 + 1,
  { rw [nat.sub_add_cancel _, nat.pred_eq_sub_one], apply nat.le_pred_of_lt hk, },
  haveI : fact (0 < k.pred) := fact_iff.2 (nat.lt_pred_iff.2 hk),
  refine le_trans (na _ _) (csupr_le (λ z, _)),
  conv { congr, congr, find (↑(d * p ^ x) ^ k.pred) { rw [le, pow_add, pow_one], },
    rw [←mul_assoc, (algebra_map ℚ R).map_mul, mul_assoc _ _ (↑(d * p ^ x) ^ (k.pred - 1)),
      div_mul_comm], },
  rw mul_comm,
  apply le_trans (norm_mul_le _ _) _,
  rw [←mul_one_div, ←inv_eq_one_div, ←pow_sub₀ ((d * p^x : ℕ) : ℚ)
    (nat.cast_ne_zero.2 (nat.ne_zero_of_lt' 0)) (nat.le_pred_of_lt (zmod.val_lt z)),
    ring_hom.map_mul, ←nat.cast_pow, ←nat.cast_pow, ←nat.cast_mul, map_nat_cast,
    mul_le_mul_left (norm_mul_pow_pos p d R x), map_nat_cast],
  refine le_trans (norm_mul_le _ _) (le_trans (mul_le_mul le_rfl (norm_le_one p _)
    (norm_nonneg _) (norm_nonneg _)) _),
  rw mul_one,
  refine le_cSup (set.finite.bdd_above (set.finite_range _)) ⟨z, _⟩,
  simp only,
end

--`helper_W_3` replaced with `helper_16`
lemma helper_16 [normed_algebra ℚ_[p] R] [fact (0 < m)] (k : ℕ) {x : ℕ} (hx : m ≤ x) :
  ∑ (i : (zmod (d * p ^ x))ˣ), (asso_dirichlet_character (χ.mul
    (teichmuller_character_mod_p' p R ^ k))) ↑i * ↑i ^ (k - 1) =
  ∑ (i : (zmod (d * p ^ x))ˣ), (asso_dirichlet_character (χ.mul
    (teichmuller_character_mod_p' p R ^ k))) ↑(d * p ^ x - (i : zmod (d * p^x)).val) *
    ↑(d * p ^ x - (i : zmod (d * p^x)).val) ^ (k - 1) :=
begin
  symmetry,
  apply finset.sum_bij,
  swap 5, { intros a ha, apply zmod.unit_of_coprime (d * p^x - (a : zmod (d * p^x)).val),
    apply nat.coprime_sub (coprime_of_is_unit _).symm (le_of_lt (zmod.val_lt _)),
    { rw zmod.nat_cast_val, rw zmod.cast_id,
      apply units.is_unit _, },
    { apply_instance, }, },
  { intros a ha, apply finset.mem_univ _, },
  { intros a ha,
    simp only,
    have lev_mul_dvd : lev (χ.mul (teichmuller_character_mod_p' p R ^ k)) ∣ d * p^m,
    { --apply dvd_trans _ (mul_dvd_mul_left d (pow_dvd_pow p hk)),
      apply dvd_trans (conductor.dvd_lev _) _, --(dvd_trans (conductor_dvd _) _),
      rw helper_4, },
    have lev_mul_dvd' : lev (χ.mul (teichmuller_character_mod_p' p R ^ k)) ∣ d * p^x,
    { apply dvd_trans lev_mul_dvd _,
      --convert dvd_trans (dirichlet_character.lev_mul_dvd _ _) _, rw [lcm_eq_nat_lcm, nat.lcm_self],
      apply mul_dvd_mul_left d, apply pow_dvd_pow p hx, },
    symmetry,
    rw _root_.coe_coe, rw _root_.coe_coe, rw zmod.coe_unit_of_coprime,
    rw zmod.cast_nat_cast lev_mul_dvd' (d * p ^ x - (a : zmod (d * p^x)).val),
    swap 2, { delta lev, refine zmod.char_p _, }, congr' 2,
    rw ← zmod.nat_cast_val (d * p ^ x - (a : zmod (d * p^x)).val : ℕ), congr,
    apply zmod.val_cast_of_lt, apply nat.sub_lt _ _,
    { refine fact_iff.1 _, apply_instance, },
    { apply lt_of_le_of_ne,
      { apply nat.zero_le _, },
      { apply ne.symm, simp only [ne.def, zmod.val_eq_zero],
        apply is_unit.ne_zero (units.is_unit _),
        apply zmod.nontrivial _,
        apply fact_iff.2 _, apply nat.one_lt_mul_pow_of_ne_one, intro h,
        rw nat.mul_eq_one_iff at h,
        rw pow_eq_one_iff (ne_zero_of_lt (lt_of_le_of_lt' hx (fact.out _))) at h,
        apply nat.prime.ne_one (fact.out _) h.2,
        swap 3, { exact 0, },
        any_goals { apply_instance, }, }, },
    { apply_instance, }, },
  { intros a b ha hb, simp only, intro h,
    rw units.ext_iff at h, rw zmod.coe_unit_of_coprime at h, rw zmod.coe_unit_of_coprime at h,
    rw nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)) at h,
    rw nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)) at h,
    rw zmod.nat_cast_self at h, rw zero_sub at h, rw zero_sub at h, rw eq_neg_iff_eq_neg at h,
    rw neg_neg at h, rw zmod.nat_cast_val at h, rw zmod.cast_id at h,
    rw zmod.nat_cast_val at h, rw zmod.cast_id at h, rw ← units.ext_iff at h,
    rw h, },
  { intros b hb,
    refine ⟨_, finset.mem_univ _, _⟩,
    { apply zmod.unit_of_coprime (d * p^x - (b : zmod (d * p^x)).val),
      apply nat.coprime_sub (coprime_of_is_unit _).symm (le_of_lt (zmod.val_lt _)),
      { rw zmod.nat_cast_val, rw zmod.cast_id,
        apply units.is_unit _, },
      { apply_instance, }, },
    simp only, rw units.ext_iff, rw zmod.coe_unit_of_coprime, rw zmod.coe_unit_of_coprime,
    rw nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)),
    rw nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)),
    rw zmod.nat_cast_val, rw zmod.cast_id, rw zmod.nat_cast_val, rw zmod.cast_id,
    rw sub_sub_cancel, },
end

--`sum_eq_neg_sum_add_dvd'` replaced with `sum_eq_neg_sum_add_dvd_of_units`
lemma sum_eq_neg_sum_add_dvd_of_units (hχ : χ.is_even) [normed_algebra ℚ_[p] R] [nontrivial R]
  [no_zero_divisors R] [fact (0 < m)] (hp : 2 < p) (k : ℕ) (hk : 1 ≤ k) {x : ℕ} (hx : m ≤ x) :
  ∑ (i : (zmod (d * p ^ x))ˣ), (asso_dirichlet_character (χ.mul
  (teichmuller_character_mod_p' p R ^ k))) ↑i * ↑i ^ (k - 1) =
  -1 * ∑ (y : (zmod (d * p ^ x))ˣ),
  (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y *
  ↑y ^ (k - 1) + ↑(d * p ^ x) * ∑ (y : (zmod (d * p ^ x))ˣ),
  (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) (-1) *
  ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y *
  ∑ (x_1 : ℕ) in finset.range (k - 1), ↑(d * p ^ x) ^ x_1 * ((-1) * ↑y) ^ (k - 1 - (x_1 + 1)) *
  ↑((k - 1).choose (x_1 + 1))) :=
begin
  have lev_mul_dvd : lev (χ.mul (teichmuller_character_mod_p' p R ^ k)) ∣ d * p^m,
  { --apply dvd_trans _ (mul_dvd_mul_left d (pow_dvd_pow p hk)),
    apply dvd_trans (conductor.dvd_lev _) _, --(dvd_trans (conductor_dvd _) _),
    rw helper_4, },
  refine eq.trans (helper_16 k hx) _,
  conv_lhs { apply_congr, skip, rw nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)),
    rw asso_dirichlet_character.eval_mul_sub' _ (dvd_trans lev_mul_dvd
      (mul_dvd_mul dvd_rfl (pow_dvd_pow _ hx))),
    conv { congr, skip, rw [nat.cast_sub (le_of_lt (@zmod.val_lt (d * p^x) _ _)), sub_eq_add_neg,
    add_pow, finset.sum_range_succ', add_comm, pow_zero, one_mul, nat.sub_zero,
    nat.choose_zero_right, nat.cast_one, mul_one, neg_eq_neg_one_mul, mul_pow],
    congr, skip, apply_congr, skip, rw pow_succ, rw mul_assoc ↑(d * p^x) _,
    rw mul_assoc ↑(d * p^x) _, },
    rw [←finset.mul_sum, mul_add, mul_mul_mul_comm, mul_mul_mul_comm _ _ ↑(d * p^x) _,
      mul_comm _ ↑(d * p^x), mul_assoc ↑(d * p^x) _ _], },
  rw finset.sum_add_distrib, rw ←finset.mul_sum, rw ←finset.mul_sum,
  simp_rw [← zmod.cast_nat_cast lev_mul_dvd, zmod.nat_cast_val, ← _root_.coe_coe],
  apply congr_arg2 _ (congr_arg2 _ _ rfl) rfl,
--  apply congr_arg2 _ (congr_arg2 _ _ rfl) rfl,
  rw ←int.cast_one, rw ←int.cast_neg,
  --rw ←zmod.neg_one_eq_sub _,
  rw mul_eval_neg_one _ _,
--  rw zmod.neg_one_eq_sub _,
  --rw int.cast_neg, rw int.cast_one,
  rw asso_even_dirichlet_character_eval_neg_one _ hχ, rw one_mul,
  rw asso_dirichlet_character_eq_char' _ (is_unit.neg (is_unit_one)),
  convert_to (-1 : R)^k * (-1)^(k -1) = -1,
  { apply congr_arg2 _ _ rfl,
    rw change_level_pow_eval_neg_one' k hp,
    simp only [units.coe_neg_one, units.coe_pow],
    any_goals { apply_instance, }, },
  { rw ←pow_add, rw nat.add_sub_pred, rw odd.neg_one_pow _, rw nat.odd_iff,
    rw nat.two_mul_sub_one_mod_two_eq_one hk, },
  { apply_instance, },
  { apply fact_iff.2 (nat.prime.pos _), refine fact_iff.1 _, apply_instance, },
end

--`sum_even_character'` replaced with `sum_even_character_tendsto_zero_of_units`
lemma sum_even_character_tendsto_zero_of_units [nontrivial R] [no_zero_divisors R] [normed_algebra ℚ_[p] R]  [norm_one_class R]
 --(n : ℕ) --[fact (0 < n)]
  (na' : ∀ (n : ℕ) (f : (zmod n)ˣ → R), ∥∑ i : (zmod n)ˣ, f i∥ ≤ ⨆ (i : (zmod n)ˣ), ∥f i∥)
  [fact (0 < m)] {k : ℕ} (hk : 1 < k) (hχ : χ.is_even) (hp : 2 < p) :
  filter.tendsto (λ n, ∑ (i : (zmod (d * p^n))ˣ), ((asso_dirichlet_character
  (dirichlet_character.mul χ (teichmuller_character_mod_p' p R^k)))
  i * i^(k - 1)) ) (@filter.at_top ℕ _) (nhds 0) :=
begin
  suffices : filter.tendsto (λ n, (2 : R) * ∑ (i : (zmod (d * p^n))ˣ), ((asso_dirichlet_character
    (dirichlet_character.mul χ (teichmuller_character_mod_p' p R^k)))
    i * i^(k - 1)) ) (@filter.at_top ℕ _) (nhds 0),
  { have h1 : (2 : ℚ_[p]) ≠ 0, { norm_num, },
    apply tendsto_zero_of_tendsto_const_smul_zero h1,
    have h2 : (2 : R) = ((2 : ℕ) : R), { rw nat.cast_two, },
    conv at this { congr, funext, rw [h2, ←map_nat_cast (algebra_map ℚ_[p] R) 2, ←smul_eq_mul,
      algebra_map_smul, nat.cast_two], },
    apply this, },
  { apply (tendsto_congr' _).2,
    swap 2, { refine λ x : ℕ, ↑(d * p ^ x) * ∑ (y : (zmod (d * p ^ x))ˣ),
      (asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) (-1) *
      ((asso_dirichlet_character (χ.mul (teichmuller_character_mod_p' p R ^ k))) ↑y *
      ∑ (x_1 : ℕ) in finset.range (k - 1), ↑(d * p ^ x) ^ x_1 * ((-1) * ↑y) ^ (k - 1 - (x_1 + 1)) *
      ↑((k - 1).choose (x_1 + 1))) },
    { conv { congr, funext, rw finset.mul_sum, },
      rw metric.tendsto_at_top,
      intros ε hε,
      have h4 : 0 < ε / 2 / (χ.mul (teichmuller_character_mod_p' p R ^ k)).bound,
      { apply div_pos (half_pos hε) (bound_pos _), },
      obtain ⟨z, hz⟩ := padic_int.exists_pow_neg_lt p h4,
      refine ⟨max z 1, λ x hx, _⟩,
      rw dist_zero_right,
      apply lt_of_le_of_lt (na' _ _),
      have h2 : ε / 2 < ε, linarith,
      apply lt_of_le_of_lt _ h2,
      apply cSup_le _ _,
      { exact set.range_nonempty (λ (i : (zmod (d * p ^ x))ˣ), ∥↑(d * p ^ x) *
          ((asso_dirichlet_character (mul χ (teichmuller_character_mod_p' p R ^ k)))
          (-1) * ((asso_dirichlet_character (mul χ
          (teichmuller_character_mod_p' p R ^ k))) ↑i * ∑ (x_1 : ℕ) in
          finset.range (k - 1), ↑(d * p ^ x) ^ x_1 * ((-1) * ↑i) ^ (k - 1 - (x_1 + 1)) *
          ↑((k - 1).choose (x_1 + 1))))∥), },
      { intros b hb,
        cases hb with y hy,
        rw ← hy, simp only, clear hy,
        conv { congr, congr, congr, skip, rw ← mul_assoc, rw ←monoid_hom.map_mul, rw mul_comm, },
        rw ← mul_assoc,
        apply le_trans (norm_mul_le _ _) _,
        apply le_trans (mul_le_mul le_rfl (le_of_lt (lt_bound _ _)) _ (norm_nonneg _)) _,
        { apply norm_nonneg _, },
        rw _root_.coe_coe, rw ← zmod.nat_cast_val,
        --rw nat.mul_comm d (p^x),
        rw nat.cast_mul, rw mul_comm ↑d _, rw mul_assoc,
        apply le_trans (mul_le_mul (norm_mul_le _ _) le_rfl (le_of_lt (bound_pos _)) _) _,
        { apply mul_nonneg (norm_nonneg _) (norm_nonneg _), },
        refine le_trans (mul_le_mul (mul_le_mul le_rfl (helper_17 y) (norm_nonneg _)
          (norm_nonneg _)) le_rfl (le_of_lt (bound_pos _)) _) _,
        { rw mul_one, apply norm_nonneg _, },
        rw mul_one,
        rw ← map_nat_cast (algebra_map ℚ_[p] R), rw norm_algebra_map',
        rw nat.cast_pow, rw norm_pow,
        rw padic_norm_e.norm_p,
        apply le_trans (mul_le_mul (le_trans _ (le_of_lt hz)) le_rfl (le_of_lt (bound_pos _))
          (le_of_lt h4)) _,
        { rw inv_pow,
          rw ← zpow_neg_coe_of_pos _,
          apply zpow_le_of_le _ _,
          { norm_cast, apply le_of_lt (nat.prime.one_lt _), apply fact_iff.1, apply_instance, },
          { apply neg_le_neg, norm_cast, apply (max_le_iff.1 hx).1, },
          { apply nat.succ_le_iff.1 (max_le_iff.1 hx).2, }, },
        { rw div_mul_cancel _ _,
          intro h,
          have := bound_pos (mul χ (teichmuller_character_mod_p' p R ^ k)),
          rw h at this, simp only [lt_self_iff_false] at this, apply this, }, }, },
    { simp_rw two_mul,
      rw eventually_eq,
      rw eventually_at_top,
      refine ⟨m, λ x hx, _⟩,
      conv { congr, --skip, funext,
        conv { congr, skip, rw sum_eq_neg_sum_add_dvd_of_units hχ hp _ (le_of_lt hk) hx, }, },
      rw neg_one_mul, rw ← add_assoc, ring, }, },
end


import Logic.Predicate.FirstOrder.Semantics

namespace FirstOrder

variable {L : Language.{u}} [L.HasEq]

def fvarSeq {k n : ℕ} (f : Fin k → Fin n) : Fin k → SubTerm L μ n := fun i => #(f i)

namespace SubFormula

def vecEq {k} (v w : Fin k → SubTerm L μ n) : SubFormula L μ n := Matrix.conj (fun i => “!(v i) = !(w i)”)

end SubFormula

namespace Theory

class Sub (T U : CTheory L) where
  sub : T ⊆ U

section Eq

abbrev Eq.varSeq₀ {k} : Fin k → SubTerm L μ (k + k) := fun i => #(Fin.castLe (by simp) i)

abbrev Eq.varSeq₁ {k} : Fin k → SubTerm L μ (k + k) := fun i => #(Fin.natAdd k i)

variable (L)

inductive Eq : CTheory L
  | refl : Eq “∀ #0 = #0”
  | symm : Eq “∀ ∀ (#1 = #0 → #0 = #1)”
  | trans : Eq “∀ ∀ ∀ (#2 = #1 → #1 = #0 → #2 = #0)”
  | funcExt {k} (f : L.func k) :
    Eq “∀* (!(SubFormula.vecEq Eq.varSeq₀ Eq.varSeq₁) → !(SubTerm.func f Eq.varSeq₀) = !(SubTerm.func f Eq.varSeq₁))”
  | relExt {k} (r : L.rel k) :
    Eq “∀* (!(SubFormula.vecEq Eq.varSeq₀ Eq.varSeq₁) → (!(SubFormula.rel r Eq.varSeq₀) ↔ !(SubFormula.rel r Eq.varSeq₁)))”

end Eq

end Theory

abbrev EqTheory (T : CTheory L) := SubTheory (Theory.Eq L) T

namespace EqTheory

variable (T : CTheory L) [EqTheory T]

lemma subset : Theory.Eq L ⊆ T := SubTheory.sub

end EqTheory

namespace Structure

namespace Eq

variable (L)

variable {M : Type u} [Structure L M]

def eqv (a b : M) : Prop := rel (L := L) Language.HasEq.eq ![a, b]

variable {L}

variable (H : M ⊧₁* Theory.Eq L)

open Theory SubFormula

lemma eqv_refl (a : M) : eqv L a a := by
  have : M ⊧₁ “∀ #0 = #0” := H (Theory.Eq.refl (L := L))
  simp[models_def] at this
  exact this a

lemma eqv_symm {a b : M} : eqv L a b → eqv L b a := by
  have : M ⊧₁ “∀ ∀ (#1 = #0 → #0 = #1)” := H (Theory.Eq.symm (L := L))
  simp[models_def] at this
  exact this a b

lemma eqv_trans {a b c : M} : eqv L a b → eqv L b c → eqv L a c := by
  have : M ⊧₁ “∀ ∀ ∀ (#2 = #1 → #1 = #0 → #2 = #0)” := H (Theory.Eq.trans (L := L))
  simp[models_def] at this
  exact this a b c

lemma eqv_funcExt {k} (f : L.func k) {v w : Fin k → M} (h : ∀ i, eqv L (v i) (w i)) :
    eqv L (func f v) (func f w) := by
  have : M ⊧₁ “∀* (!(vecEq Eq.varSeq₀ Eq.varSeq₁) → !(SubTerm.func f Eq.varSeq₀) = !(SubTerm.func f Eq.varSeq₁))” :=
    H (Eq.funcExt f (L := L))
  simp[models_def, vecEq, SubTerm.val_func] at this
  simpa[Matrix.vecAppend_eq_ite] using this (Matrix.vecAppend rfl v w) (fun i => by simpa[Matrix.vecAppend_eq_ite] using h i)

lemma eqv_relExt {k} (r : L.rel k) {v w : Fin k → M} (h : ∀ i, eqv L (v i) (w i)) :
    rel r v = rel r w := by
  have : M ⊧₁ “∀* (!(vecEq Eq.varSeq₀ Eq.varSeq₁) → (!(SubFormula.rel r Eq.varSeq₀) ↔ !(SubFormula.rel r Eq.varSeq₁)))” :=
    H (Eq.relExt r (L := L))
  simp[models_def, vecEq, SubTerm.val_func, eval_rel (r := r)] at this
  simpa[eval_rel, Matrix.vecAppend_eq_ite] using this (Matrix.vecAppend rfl v w) (fun i => by simpa[Matrix.vecAppend_eq_ite] using h i)

lemma eqv_equivalence : Equivalence (eqv L (M := M)) where
  refl := eqv_refl H
  symm := eqv_symm H
  trans := eqv_trans H

instance eqvSetoid : Setoid M := Setoid.mk (eqv L) (eqv_equivalence H)

def QuotEq := Quotient (eqvSetoid H)

instance [Inhabited M] : Inhabited (QuotEq H) := ⟨⟦default⟧⟩

lemma of_eq_of {a b : M} : (⟦a⟧ : QuotEq H) = ⟦b⟧ ↔ eqv L a b := Quotient.eq (r := eqvSetoid H)

namespace QuotEq

def func {k} (f : L.func k) (v : Fin k → QuotEq H) : QuotEq H :=
  Quotient.liftVec (s := eqvSetoid H) (⟦Structure.func f ·⟧) (fun _ _ hvw => (of_eq_of H).mpr (eqv_funcExt H f hvw)) v

def rel {k} (r : L.rel k) (v : Fin k → QuotEq H) : Prop :=
  Quotient.liftVec (s := eqvSetoid H) (Structure.rel r) (fun _ _ hvw => eqv_relExt H r hvw) v

variable {H}

instance QuotEqStruc : Structure L (QuotEq H) where
  func := QuotEq.func H
  rel := QuotEq.rel H

lemma funk_mk {k} (f : L.func k) (v : Fin k → M) : Structure.func (M := QuotEq H) f (fun i => ⟦v i⟧) = ⟦Structure.func f v⟧ :=
  Quotient.liftVec_mk (s := eqvSetoid H) _ _ _

lemma rel_mk {k} (r : L.rel k) (v : Fin k → M) : Structure.rel (M := QuotEq H) r (fun i => ⟦v i⟧) ↔ Structure.rel r v :=
  of_eq $ Quotient.liftVec_mk (s := eqvSetoid H) _ _ _

lemma val_mk {e} {ε} (t : SubTerm L μ n) : SubTerm.val! (QuotEq H) (fun i => ⟦e i⟧) (fun i => ⟦ε i⟧) t = ⟦SubTerm.val! M e ε t⟧ :=
  by induction t <;> simp[*, funk_mk, SubTerm.val_func]

lemma eval_mk {e} {ε} {p : SubFormula L μ n} :
    SubFormula.Eval! (QuotEq H) (fun i => ⟦e i⟧) (fun i => ⟦ε i⟧) p ↔ SubFormula.Eval! M e ε p := by
  induction p using SubFormula.rec' <;> simp[*, SubFormula.eval_rel, SubFormula.eval_nrel, val_mk, rel_mk]
  case hall n p ih =>
    constructor
    · intro h a; exact (ih (e := a :> e)).mp (by simpa[Matrix.comp_vecCons] using h ⟦a⟧)
    · intro h a;
      induction' a using Quotient.ind with a
      simpa[Matrix.comp_vecCons] using ih.mpr (h a)
  case hex n p ih =>
    constructor
    · intro ⟨a, h⟩
      induction' a using Quotient.ind with a
      exact ⟨a, (ih (e := a :> e)).mp (by simpa[Matrix.comp_vecCons] using h)⟩
    · intro ⟨a, h⟩; exact ⟨⟦a⟧, by simpa[Matrix.comp_vecCons] using ih.mpr h⟩

lemma models_iff {σ : Sentence L} : (QuotEq H) ⊧₁ σ ↔ M ⊧₁ σ := by
  simpa[models_def, SubFormula.Val, eq_finZeroElim, Empty.eq_elim] using
    eval_mk (H := H) (e := finZeroElim) (ε := Empty.elim) (p := σ)

variable (H)

lemma elementaryEquiv : QuotEq H ≃ₑ[L] M := fun _ => models_iff

variable {H}

lemma rel_eq (a b : QuotEq H) : Structure.rel (L := L) (M := QuotEq H) Language.HasEq.eq ![a, b] ↔ (a = b) := by
  induction' a using Quotient.ind with a
  induction' b using Quotient.ind with b
  simp[rel_mk]; rw[of_eq_of]; rfl

instance : Structure.Eq L (QuotEq H) := ⟨rel_eq⟩

end QuotEq

end Eq

end Structure

lemma consequence_iff_eq {T : CTheory L} [EqTheory T] {σ : Sentence L} :
    T ⊨ σ ↔ (∀ (M : Type u) [Inhabited M] [Structure L M] [Structure.Eq L M], M ⊧₁* T → M ⊧₁ σ) := by
  simp[consequence_iff]; constructor
  · intro h M i s _ hM; exact h M hM
  · intro h M i s hM
    have H : M ⊧₁* Theory.Eq L := Semantics.modelsTheoryₛ_of_subset hM (EqTheory.subset T)
    have e : Structure.Eq.QuotEq H ≃ₑ[L] M := Structure.Eq.QuotEq.elementaryEquiv H
    exact e.models.mp $ h (Structure.Eq.QuotEq H) (e.modelsₛ.mpr hM)

lemma satisfiableₛ_iff_eq {T : CTheory L} [EqTheory T] :
    Semantics.Satisfiableₛ T ↔ (∃ (M : Type u) (_ : Inhabited M) (_ : Structure L M) (_ : Structure.Eq L M), M ⊧₁* T) := by
  simp[satisfiableₛ_iff]; constructor
  · intro ⟨M, i, s, hM⟩;
    have H : M ⊧₁* Theory.Eq L := Semantics.modelsTheoryₛ_of_subset hM (EqTheory.subset T)
    have e : Structure.Eq.QuotEq H ≃ₑ[L] M := Structure.Eq.QuotEq.elementaryEquiv H
    exact ⟨Structure.Eq.QuotEq H, inferInstance, inferInstance, inferInstance, e.modelsₛ.mpr hM⟩
  · intro ⟨M, i, s, _, hM⟩; exact ⟨M, i, s, hM⟩

end FirstOrder