open Classical

----------------------------------LOGIC-----------------------------------------
theorem Or.distrib_and : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
  Iff.intro
  (fun hpqr : p ∨ (q ∧ r) =>
    Or.elim hpqr
    (fun hp:p => show (p ∨ q) ∧ (p ∨ r) from ⟨(Or.intro_left q hp), (Or.intro_left r hp)⟩)
    (fun hqr: (q ∧ r) => show (p ∨ q) ∧ (p ∨ r) from ⟨(Or.intro_right p hqr.left), (Or.intro_right p hqr.right)⟩))
  (fun hpqpr: (p ∨ q) ∧ (p ∨ r) =>
    have hpq := hpqpr.left
    have hpr := hpqpr.right
    Or.elim hpq
    (fun hp:p => show p ∨ (q ∧ r) from Or.intro_left (q ∧ r) hp)
    (fun hq:q =>
    Or.elim hpr
    (fun hp:p => show p ∨ (q ∧ r) from Or.intro_left (q ∧ r) hp)
    (fun hr:r => show p ∨ (q ∧ r) from Or.intro_right p ⟨hq, hr⟩))
 )

theorem And.assoc : (a ∧ b) ∧ c ↔ a ∧ (b ∧ c) := by
  constructor
  case mp => intro ⟨⟨ha, hb⟩, hc⟩; exact ⟨ha, ⟨hb, hc⟩⟩
  case mpr => intro ⟨ha, ⟨hb, hc⟩⟩; exact ⟨⟨ha, hb⟩, hc⟩

theorem And.distrib_or : a ∧ (b ∨ c) ↔ (a ∧ b) ∨ (a ∧ c) := by
  constructor

  case mp =>
    intro ⟨ha, hor⟩
    cases hor with
    | inl hl => exact Or.inl ⟨ha, hl⟩
    | inr hr => exact Or.inr ⟨ha, hr⟩

  case mpr =>
    intro h
    match h with
    | Or.inl ⟨ha, hb⟩ => exact ⟨ha, Or.inl hb⟩
    | Or.inr ⟨ha, hc⟩ => exact ⟨ha, Or.inr hc⟩

theorem Or.morgan : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
  Iff.intro
  (fun hnpq: ¬(p ∨ q) =>
    ⟨fun hp:p => hnpq (Or.intro_left q hp), fun hq:q => hnpq (Or.intro_right p hq)⟩
 )
  (fun hnpq : ¬p ∧ ¬q =>
    fun hpq : (p ∨ q) =>
      Or.elim hpq
      (fun hp:p => hnpq.left hp)
      (fun hq:q => hnpq.right hq)
 )

theorem And.morgan : ¬(p ∧ q) ↔ ¬p ∨ ¬q :=
  Iff.intro
  (fun hpq : ¬(p ∧ q) =>
    Or.elim (em p)
    (fun hp:p =>
      Or.elim (em q)
      (fun hq : q => show ¬p ∨ ¬q from False.elim (hpq ⟨hp, hq⟩))
      (fun nq : ¬q => (Or.intro_right (¬p) nq)))
    (fun np:¬p => (Or.intro_left (¬q) np)))
  (fun hpoq : ¬p ∨ ¬q =>
    Or.elim hpoq
    (fun hp:¬p =>
      fun hpnq : p ∧ q => hp hpnq.left)
    (fun hp:¬q =>
      fun hpnq : p ∧ q => hp hpnq.right))

theorem not_or_eq_implication: (¬p ∨ q) ↔ (p → q) :=
  Iff.intro
  ( fun hpq : ¬p ∨ q =>
    Or.elim hpq
    (fun hnp: ¬p =>
      fun hp:p => show q from False.elim (hnp hp))
    (fun hq: q =>
      fun _:p => hq))
  ( fun hpq : (p → q) =>
    Or.elim (em q)
    (fun hq:q => Or.intro_right (¬p) hq)
    (fun nq:¬q =>
      Or.elim (em p)
      (fun hp:p => show (¬p ∨ q) from False.elim (nq (hpq hp)))
      (fun np:¬p => show (¬p ∨ q) from Or.intro_left q np)
   ))

theorem And.elimination : p ∧ (¬p ∨ q) ↔ p ∧ q := by
  apply Iff.intro
  intro pnpq
  rw [not_or_eq_implication] at pnpq
  exact ⟨pnpq.left, pnpq.right pnpq.left⟩
  intro pq
  exact ⟨pq.left, Or.intro_right (¬p) pq.right⟩

theorem np_and_p_imp_false {p : Prop}: ¬p ∧ p → False := by
  intro npp
  exact npp.left npp.right

theorem not_not_p {p : Prop}: (¬¬p) ↔ p := by
  apply Iff.intro
  intro nnp
  cases (Classical.em p) with
  | inl hp =>
    exact hp
  | inr hr =>
    apply False.elim (nnp hr)
  intro hp
  cases (Classical.em ¬ p) with
  | inl hl =>
    apply False.elim (hl hp)
  | inr hr =>
    exact hr

theorem Or.comm (a b:Prop) : a ∨ b ↔ b ∨ a := by
  constructor
  intro ab
  apply Or.elim ab
  intro ha
  exact Or.intro_right b ha
  intro hb
  exact Or.intro_left a hb
  intro ba
  apply Or.elim ba
  intro hb
  exact Or.intro_right a hb
  intro ha
  exact Or.intro_left b ha

theorem notAllEqExists (p : α → Prop) (h: ¬ ∀ x, ¬ p x) : ∃ x, p x :=
  byContradiction
    (fun h1 : ¬ ∃ x, p x =>
      have h2 : ∀ x, ¬ p x :=
        fun x =>
        fun h3 : p x =>
        have h4 : ∃ x, p x := ⟨x, h3⟩
        show False from h1 h4
      show False from h h2)

theorem notExistsEqAll (p : α → Prop) : (¬∃ x, p x) → (∀ x, ¬p x) := by
  intro h2
  intro x
  intro px
  have existsY : ∃y, p y := by exists x
  exact h2 existsY

----------------------------------LISTS-----------------------------------------
theorem list_concat_empty {α :Type u} (as : List α) : as ++ [] = as := by
simp [List.cons]

theorem EmptyNotFull : (∀ x xs, list ≠ (x::xs)) → list = [] := by
      intro h
      cases (list) with
      | nil => rfl
      | cons x1 xs1 =>
        exact absurd rfl (h x1 xs1)

----------------------------------SETS------------------------------------------
def Set (α : Type u) := α -> Prop

namespace Set
def element {α : Type u} (e : α) (X : Set α) : Prop := X e
infixr:75 " ∈ " => element

def union {α : Type u} (X Y : Set α) : Set α := fun a => a ∈ X ∨ a ∈ Y
infixr:65 " ∪ " => union

def subset {α : Type u} (X Y : Set α) : Prop := ∀ e : α, e ∈ X → e ∈ Y
infixr:50 " ⊆ " => subset

def intersection {α : Type u} (X Y : Set α) : Set α := fun (e: α) => e ∈ X ∧ e ∈ Y
infixr:80 " ∩ " => intersection

def diff {α : Type u} (X Y : Set α) : Set α := fun (e: α) => e ∈ X ∧ ¬(e ∈ Y)
--infixr:80 " \\ " => intersection

def empty {α :Type u}: Set α := fun _ => False

notation (priority := high) "∅" => empty

theorem setext {α :Type u} {a b : Set α} (h : ∀ x, x ∈ a ↔ x ∈ b) : a = b :=
 funext (fun x => propext (h x))

theorem inter_self {α :Type u} (a : Set α) : a ∩ a = a := by
  apply setext
  intro x
  constructor
  intro n
  rw [element, intersection] at n
  exact n.left
  intro n
  exact ⟨n,n⟩

theorem inter_empty {α :Type u} (X : Set α) : X ∩ ∅ = ∅ := by
  apply setext
  intro n
  constructor
  intro x
  rw [element, intersection] at x
  exact x.right
  intro x
  rw [element] at x
  rw [empty] at x
  exact False.elim x

theorem inter_comm {α : Type u} (X Y:Set α) : X ∩ Y = Y ∩ X := by
  apply setext
  intro x
  constructor
  intro n
  rw [element, intersection] at n
  rw [element, intersection]
  exact ⟨n.right, n.left⟩
  intro n
  rw [element, intersection] at n
  rw [element, intersection]
  exact ⟨n.right, n.left⟩

theorem union_or {α : Type u} (X Y : Set α) (e : α) : (e ∈ (X ∪ Y)) = (e ∈ X ∨ e ∈ Y) := by rfl

theorem union_comm {α :Type u} (X Y : Set α) : X ∪ Y = Y ∪ X :=
  by
  apply funext
  intro f
  rw [union]
  rw [union]
  rw [Or.comm]

theorem empty_inter {α :Type u} (a : Set α) : ∅ ∩ a = ∅ := by
  rw [inter_comm]
  apply inter_empty

theorem intersection_and {α : Type u} (X Y : Set α) (e:α) : (e ∈ (X ∩ Y)) = (e ∈ X ∧ e ∈ Y) := by rfl

theorem union_dist_intersection {α : Type u} (X Y Z : Set α) : X ∪ (Y ∩ Z) = (X ∪ Y) ∩ (X ∪ Z) := by
  apply funext
  intro x
  rw [union, intersection]
  repeat rw [element]
  rw [union, union, intersection]
  rw [Or.distrib_and]
  rfl

theorem intersection_dist_union {α : Type u} (X Y Z : Set α) : X ∩ (Y ∪ Z) = (X ∩ Y) ∪ (X ∩ Z) := by
  apply funext
  intro x
  rw [intersection, union]
  repeat rw [element]
  rw [union, intersection, intersection,And.distrib_or]
  rfl

end Set

----------------------------------WORDS------------------------------------------
structure Word (α : Type u) where
  data : List α
  deriving Repr

def Word.concat {α : Type u} (x y : Word α) : Word α := {data := x.data ++ y.data}
infixr:70 " ∘ " => Word.concat

def Word.epsilon {α : Type u} : Word α := Word.mk List.nil
notation (priority := high) "ε" => Word.epsilon

def Word.len {α : Type u} (w:Word α) : Nat :=
  match w with
  | Word.mk List.nil => 0
  | Word.mk (x::xs) => 1 + Word.len (Word.mk (xs))

theorem Word.objects_equal {α : Type u} (w :Word α): Word.mk w.data = w := by rfl

@[simp] theorem Word.epsilon_eq_epsilon {α : Type u} : (@ε α)= (Word.mk List.nil) := by rfl

def Word.AllElementsOfWordInSet {α : Type u} (w: Word α) (S: Set α) :=
  match w with
  | Word.mk (a::as)=> a ∈ S ∧ Word.AllElementsOfWordInSet (Word.mk as) S
  | _ => True

----------------------------------LANGUAGES---------------------------------------
def Language (α : Type u) := Set (Word α)

def Language.concat {α : Type u} (X Y : Language α) : Language α := fun w : Word α => ∃ u v : Word α, u ∈ X ∧ v ∈ Y ∧ w = u ∘ v
infixr:70 " ∘ₗ " => Language.concat

def Language.epsilon {α:Type u} : Language α :=
  fun w =>
  match w with
  | Word.mk List.nil => True
  | Word.mk (_::_) => False

def Language.power {α : Type u} (n:Nat) (X: Language α) : Language α :=
  match n with
  | 0 =>
    Language.epsilon
  | (Nat.succ m) =>
    fun (w:Word α)=>
      ∃ w1 w2 : Word α, w2 ∈ (Language.power m X) ∧ w1 ∈ X ∧ w = w1 ∘ w2

def Language.kleene {α :Type u} (X: Language α) : Language α :=
  fun w: Word α =>
    ∃ n : Nat, w ∈ Language.power n X

def Language.plus {α :Type u} (X :Language α) : Language α :=
  fun w: Word α => ∃ n:Nat, ¬ (n = 0) ∧ w ∈ Language.power n X

def Sigma.language {α : Type u}: Language α :=
  fun w: Word α =>
    match w with
    | (Word.mk (_::[])) => True
    | _ => False

def Sigma.kleene {α :Type u}: Language α :=
  fun w: Word α => (@Language.kleene _ Sigma.language) w

def Language.complement {α :Type u} (X: Language α) : Language α :=
  fun w: Word α =>
    ¬(w ∈ X)

theorem Lanuage.eps_element_only_element_in_eps_lang_il {α :Type u} (w : Word α) : Language.epsilon w -> w = {data := []} := by
intro n
rw [← Word.objects_equal w] at n
cases h:w.data with
| nil =>
  rw [h] at n
  rw [← Word.objects_equal w]
  rw [h]
| cons a as =>
  rw [h] at n
  simp [Language.epsilon] at n

theorem Language.eps_element_only_element_in_eps_lang {α :Type u} {w: Word α} : w ∈ Language.epsilon ↔ (w = Word.mk []) := by
constructor
exact Lanuage.eps_element_only_element_in_eps_lang_il w
intro n
simp [Set.element,n, Language.epsilon]

theorem Language.kleene_eq_plus_eps {α :Type u} {X: Language α} : Language.plus X ∪ Language.epsilon = Language.kleene X := by
  apply funext
  intro w
  apply propext
  constructor
  intro x
  simp [Set.union] at x
  rw[Set.element] at x
  rw [Language.kleene]
  cases x with
  |inl p =>
    rw [Language.plus] at p
    cases p with
    | intro n r =>
      exists n
      exact r.right
  |inr e =>
    exists 0
  intro n
  rw [Set.union]
  simp [Language.kleene] at n
  cases n with
  | intro nn r =>
    cases nn with
    | succ m =>
      apply Or.inl
      simp [Set.element, Language.plus]
      exists (Nat.succ m)
      rw [Set.element] at r
      exact ⟨Nat.succ_ne_zero m, r⟩
    | zero =>
      apply Or.inr
      simp [Set.element] at r
      simp [Language.power] at r
      simp [Set.element]
      exact r

theorem Language.lan_eps_eq_lan {α : Type u} (L : Language α): L ∘ₗ Language.epsilon = L := by
  apply funext
  intro w
  apply propext
  constructor
  rw [Language.concat]
  intro ⟨u,v, h1, h2, h3⟩
  rw [Word.concat] at h3
  rw [Set.element] at h1
  rw [Language.eps_element_only_element_in_eps_lang] at h2
  simp [h2] at h3
  rw [(Word.objects_equal u)] at h3
  simp [*]
  intro n
  rw [Language.concat]
  exists w
  exists Word.mk []
  apply And.intro
  exact n
  apply And.intro
  rw [Set.element]
  simp [Word.epsilon, Language.epsilon]
  simp [Word.concat]

theorem Language.eps_lan_eq_lan {α : Type u} (L : Language α): Language.epsilon ∘ₗ L = L := by
  apply funext
  intro w
  apply propext
  constructor
  rw [Language.concat]
  intro ⟨u,v, h1, h2, h3⟩
  rw [Word.concat] at h3
  rw [Language.eps_element_only_element_in_eps_lang] at h1
  simp [h2] at h3
  rw [Set.element] at h2
  simp [h1,Word.objects_equal] at h3
  simp [*]
  intro n
  rw [Language.concat]
  exists (Word.mk [])
  exists w

theorem Language.empty_lan_eq_empty {α :Type u} (L : Language α) : L ∘ₗ ∅ = ∅ := by
  apply Set.setext
  intro w
  constructor
  intro n
  rw [Set.element, Language.concat] at n
  match n with
  | ⟨u,v,_,h2,h3⟩ =>
    rw [Set.element, Set.empty] at h2
    apply False.elim h2
  intro n
  rw [Set.element, Set.empty] at n
  apply False.elim n

theorem Language.lan_empty_eq_empty {α :Type u} (L : Language α) : ∅ ∘ₗ L = ∅ := by
  apply Set.setext
  intro w
  constructor
  intro n
  rw [Set.element, Language.concat] at n
  match n with
  | ⟨u,v, h1,h2,h3⟩ =>
    rw [Set.element, Set.empty] at h1
    apply False.elim h1
  intro n
  rw [Set.element, Set.empty] at n
  apply False.elim n

theorem Language.concat_dist_union_r {α : Type u} (L1 L2 L3 : Language α) : (L1 ∪ L2) ∘ₗ L3 = (L1 ∘ₗ L3) ∪ (L2 ∘ₗ L3) := by
  apply Set.setext
  intro w
  constructor
  intro n
  rw [Set.element, Language.concat] at n
  match n with
  | ⟨u,v,h1,h2,h3⟩ =>
    rw [Set.element,Set.union, Set.element]
    rw [Set.element, Set.union] at h1
    cases h1 with
    | inl hl =>
      apply Or.inl
      rw [Language.concat]
      exists u
      exists v
    | inr hr =>
      apply Or.inr
      rw [Set.element, Language.concat]
      exists u
      exists v
  intro n
  rw [Set.element, Set.union] at n
  cases n with
  | inl hl =>
    rw [Set.element, Language.concat]
    rw [Set.element, Language.concat] at hl
    match hl with
    | ⟨u,v,h1,h2,h3⟩ =>
      exists u
      exists v
      rw [Set.element,Set.union]
      exact ⟨Or.inl h1, h2,h3⟩
  |inr hr =>
    rw [Set.element, Language.concat]
    rw [Set.element, Language.concat] at hr
    match hr with
    | ⟨u,v,h1,h2,h3⟩ =>
      exists u
      exists v
      exact ⟨Or.inr h1, h2, h3⟩

theorem Language.morgan_union {α : Type u} (L1 L2 : Language α) : (L1 ∪ L2) = Language.complement (Language.complement L1 ∩ Language.complement L2):= by
  apply Set.setext
  intro w
  repeat (first | rw [Set.element] | rw [Set.union] | rw [Set.intersection] | rw [Set.union] |rw [Language.complement] )
  rw [← Or.morgan, not_not_p]constructor
  repeat (first | intro n | exact n)

theorem Language.morgan_inter {α : Type u} (L1 L2: Language α) : (L1 ∩ L2) = Language.complement (Language.complement L1 ∪ Language.complement L2) := by
  apply Set.setext
  intro w
  repeat (first | rw [Set.element] | rw [Set.union] | rw [Set.intersection] | rw [Set.union] |rw [Language.complement] )
  rw [← And.morgan, not_not_p]
  constructor
  repeat (first | intro n | exact n)

theorem Language.concat_dist_union_l {α : Type u} (L1 L2 L3 : Language α) :
L1 ∘ₗ (L2 ∪ L3) = (L1 ∘ₗ L2) ∪ (L1 ∘ₗ L3) :=
  by
    apply funext
    intro w
    apply propext
    constructor

    case mp =>
      intro h
      cases h with | intro u pu => cases pu with | intro v pv =>
        rw [@And.comm (v ∈ (L2 ∪ L3)), ← And.assoc, Set.union_or, And.distrib_or] at pv

        cases pv with
        | inl _ =>
          apply Or.inl
          exists u, v
          rw [@And.comm (v ∈ L2), ← And.assoc]
          assumption
        | inr _ =>
          apply Or.inr
          exists u, v
          rw [@And.comm (v ∈ L3), ← And.assoc]
          assumption

    case mpr =>
      intro h
      cases h with
        | inl hl => cases hl with | intro u pu => cases pu with | intro v pv =>
          match pv with
          | ⟨h1, h2, h3⟩ =>
            exists u, v
            exact ⟨h1, Or.inl h2, h3⟩
        | inr hr => cases hr with | intro u pu => cases pu with | intro v pv =>
          match pv with
          | ⟨h1, h2, h3⟩ =>
            exists u, v
            exact ⟨h1, Or.inr h2, h3⟩

@[simp] theorem eq_rfl {a : Type α} : (a = a) ↔ True := by simp[]

----------------------------------GRAMMARS---------------------------------------
structure Grammar {α : Type u} where
  V : Set α
  E : Set α
  S: α
  P : Set ((Word α) × (Word α))
  bed_VEdisj : V ∩ E = ∅
  bed_SinV: S ∈ V
  bed_VarInLeft:
    ∀ pair : (Word α) × (Word α),
    P pair -> (
      ∃ v1 v2 v3 : Word α,
        ((pair.fst) = (v1 ∘ v2 ∘ v3)) ∧
        (∃ t: α, (Word.mk ([t]) = v2 ∧ t ∈ V))
   )

structure RegularGrammar {α : Type u} extends (@Grammar α) where
  bed_reg: ∀ pair : ((Word α) × (Word α)),
    (pair ∈ P) ->
    (
      (∃ t: α, (Word.mk [t] = pair.fst) ∧ t ∈ V) ∧ (
        (∃ t1 t2 : α, (Word.mk [t1, t2] = pair.snd) ∧ t1 ∈ E ∧ t2 ∈ V) ∨
        (∃ t: α, Word.mk [t] = pair.snd ∧ t ∈ E) ∨
        pair.snd = Word.mk []
     )
   )

structure EpsilonFreeRegularGrammar {α : Type u} extends (@RegularGrammar α) where
  epsilonFree : ∀ pair : (Word α) × (Word α),
  ¬ (pair ∈ P) ∨ (
    pair.fst = Word.mk ([S]) ∨ ¬ (pair.snd = Word.epsilon)
 )

def OneStepDerivation {α : Type u} (G : @Grammar α) (w : Word α) (v : Word α) : Prop :=
    ∃ w1 w2 w3: Word α,
      ∃ v1 v2 v3: Word α,
      have p1 := w = w1 ∘ w2 ∘ w3
      have p2 := v = v1 ∘ v2 ∘ v3
      (v1 = w1) ∧ (v3 = w3) ∧ p1 ∧ p2 ∧ G.P ⟨w2, v2⟩

def NstepDerivation {α : Type u} (G : @Grammar α) (w : Word α) (v : Word α) (n:Nat) : Prop :=
  match n with
  | 0 =>
    w = v
  | (Nat.succ m) =>
      ∃ w1 : Word α, (OneStepDerivation G w w1) ∧ (NstepDerivation G w1 v m) ∧ (Word.AllElementsOfWordInSet v G.E)

def StarDerivation {α : Type u} (G : @Grammar α) (w : Word α) (v : Word α) : Prop :=
  ∃ n : Nat, NstepDerivation G w v n

def GeneratedLanguageGrammar {α : Type u} (G : @Grammar α): Language α :=
  fun w: Word α =>
    StarDerivation G (Word.mk [G.S]) w

def RunRegularGrammarSub {α : Type u} (ql qg : α) (G : @RegularGrammar α) (run: List (Word α × Word α))(w : Word α) : Prop :=
    match w with
    | (Word.mk (word::ws1)) =>
      match run with
      | (p1::xs) =>
          p1 ∈ G.P ∧
          p1.fst = (Word.mk [ql]) ∧
          (∃ t1 : α, (Word.mk [word, t1] = p1.snd)
                  ∧ RunRegularGrammarSub t1 qg G xs (Word.mk ws1)
           )
      | _ => False
    | _ =>
      ql = qg

def LanguageRegularGrammar {α : Type u} (G : @RegularGrammar α) : Language α :=
  fun w => ∃ qn run, (RunRegularGrammarSub G.S qn G run w ∧ ⟨Word.mk [qn], Word.mk []⟩ ∈ G.P
    ∨ ∃ w1, ∃ z : α, (w = w1 ∘ Word.mk [z]) ∧ RunRegularGrammarSub G.S qn G run w1 ∧ ⟨Word.mk [qn], Word.mk [z]⟩ ∈ G.P)

----------------------------------AUTOMATA------------------------------------------
structure NFA {α : Type u} where
  Q : Set α
  E : Set α
  δ : Set ((α × α) × α)
  Q0 : Set α
  F: Set α
  QEdisj: Q ∩ E = ∅
  Q0subset: Q0 ⊆ Q
  Fsubset: F ⊆ Q
  Tfunction:
    ∀ t : ((α × α) × α),
       ¬ (t ∈ δ) ∨ (
          t.fst.fst ∈ Q ∧
          t.fst.snd ∈ E ∧
          t.snd ∈ Q
      )

structure DFA {α : Type u} extends (@ NFA α) where
  q0 : α
  bed_Q0:
  (Q0 =
    (fun a : α =>
       (q0 = a)
     )
 )
  uniqueness:
      ∀ t1 t2 : ((α × α) × α),
       ¬ ((t1 ∈ δ) ∧ (t2 ∈ δ)) ∨
        (¬ (t1.fst = t2.fst) ∨ t1.snd = t2.snd)

def nfaDerivation {α : Type u} (nfa: @ NFA α) (q1 qf: α) (w: Word α) : Prop :=
  match w with
  | Word.mk (x::xs) => ∃ qn, nfa.δ ⟨⟨q1, x⟩,qn⟩ ∧ nfaDerivation nfa qn qf (Word.mk xs)
  | Word.mk [] => q1 = qf

def nfaLanguage {α : Type u} (nfa: @ NFA α) : Language α :=
  fun w => ∃ qs qf, qs ∈ nfa.Q0 ∧ qf ∈ nfa.F ∧ nfaDerivation nfa qs qf w

structure TotalDFA {α : Type u} extends (@ DFA α) where
  tot: ∀ t : ((α × α) × α),
  (¬ (t.fst.snd ∈ E ∧ t.fst.fst ∈ Q) ∨
    ∃ q2 : α, ⟨⟨t.fst.fst, t.fst.snd⟩, q2⟩ ∈ δ
 )

def TotalDFAConstruct {α : Type u} (dfa: @ DFA α) (fang: α) (p1: ¬fang ∈ dfa.Q ∧ ¬fang ∈ dfa.E) : @TotalDFA α :=
  let Q2: Set α := fun w => (w ∈ dfa.Q) ∨ (w=fang)
  let δ2: Set ((α × α) × α) := fun ⟨⟨w1, w2⟩, w3⟩ => ⟨⟨w1, w2⟩, w3⟩ ∈ dfa.δ ∨ (¬ (∃ a : α,⟨⟨w1, w2⟩, a⟩ ∈ dfa.δ)∧ Q2 w1 ∧ dfa.E w2 ∧ w3 = fang)

  have delta_def_rfl : (fun ⟨⟨w1, w2⟩, w3⟩ => ⟨⟨w1, w2⟩, w3⟩ ∈ dfa.δ ∨ (¬ (∃ a : α,⟨⟨w1, w2⟩, a⟩ ∈ dfa.δ)∧ Q2 w1 ∧ dfa.E w2 ∧ w3 = fang)) = δ2 :=
    by rfl

  have Q2_def_rfl : ((fun w => (w ∈ dfa.Q) ∨ (w=fang)): (Set α)) = Q2 :=
    by rfl

  have setEmpty_rfl : Set.empty = (fun _ => False) := by rfl

  have QSubsetQ2: (dfa.Q ⊆ Q2) := by
    intro n
    intro w
    simp [Set.element]
    apply Or.inl
    exact w

  have Q2Edisj : Q2 ∩ dfa.E = Set.empty:= by
    rw [setEmpty_rfl]
    simp [Set.intersection]
    apply funext
    intro x
    simp [And.distrib_or, Set.element]
    have hq := dfa.QEdisj
    simp [Set.intersection, setEmpty_rfl] at hq
    have hp : ((fun e => e ∈ dfa.Q ∧ e ∈ dfa.E) = fun _ => False) → (∀e : α, (e ∈ dfa.Q ∧ e ∈ dfa.E) = False):= by
      intro _
      intro n
      rw [←Set.intersection, dfa.QEdisj, Set.empty]
    have hl := hp hq
    have hll := hl x
    simp [Set.element] at hll
    rw [And.comm] at hll
    simp [Set.intersection, Set.element]
    cases (Classical.em (x = fang)) with
    | inl xfang =>
      have p2 := p1.right
      simp [xfang]
      have hv : dfa.E fang ↔ False := by
        constructor
        intro x
        apply p2
        rw [Set.element]
        exact x
        intro x
        apply False.elim x
      simp [hv]
    | inr xNotFang =>
      simp [xNotFang]
      apply propext
      constructor
      intro x1
      rw [And.comm, hll] at x1
      exact x1
      intro x
      apply False.elim x

  have Q0SubsetQ2: (dfa.Q0 ⊆ Q2) := by
    have dfa_subset := dfa.Q0subset
    simp [Set.subset]
    intro n
    have hl := QSubsetQ2
    rw [Set.subset] at hl
    have hll := hl n
    rw [Set.subset] at dfa_subset
    have hrr := dfa_subset n
    intro x
    exact hll (hrr x)

  have FSubsetQ2: (dfa.F ⊆ Q2) := by
    have dfa_subset := dfa.Fsubset
    simp [Set.subset]
    intro n
    have hl := QSubsetQ2
    rw [Set.subset] at hl
    have hll := hl n
    rw [Set.subset] at dfa_subset
    have hrr := dfa_subset n
    intro x
    exact hll (hrr x)

  have Tfunction2: (∀ t : ((α × α) × α),
       ¬ (t ∈ δ2) ∨ (
          t.fst.fst ∈ Q2 ∧
          t.fst.snd ∈ dfa.E ∧
          t.snd ∈ Q2
      )) := by
       intro triple
       rw [not_or_eq_implication]
       intro triple_in_delta2
       have triple_in_delta2_old := triple_in_delta2
       rw [Set.element,←delta_def_rfl] at triple_in_delta2
       match triple with
       | ⟨⟨qs,b⟩, qz⟩ =>
          have hsorry2 : ⟨⟨qs, b⟩, qz⟩ ∈ dfa.toNFA.δ ∨ ((¬ (∃ a : α,⟨⟨qs, b⟩, a⟩ ∈ dfa.δ))∧ Q2 qs ∧ NFA.E dfa.toNFA b ∧  (qz = fang)) := by
            simp [Set.element, ← delta_def_rfl] at triple_in_delta2
            exact triple_in_delta2
          have hsorry : ⟨⟨qs, b⟩, qz⟩ ∈ dfa.toNFA.δ ∨ (Q2 qs ∧ NFA.E dfa.toNFA b ∧ (qz = fang)):= by
            apply Or.elim hsorry2
            intro x
            apply Or.inl
            exact x
            intro x
            apply Or.inr
            simp [x]
          simp[Set.element]
          have dfa_tfun := dfa.Tfunction
          have dfa_tfun_w := dfa_tfun ⟨⟨qs, b⟩, qz⟩
          simp [Set.element] at dfa_tfun_w
          repeat rw [←Set.element] at dfa_tfun_w
          simp [not_or_eq_implication] at dfa_tfun_w
          have k1 : Q2 qs := by
            cases (Classical.em (dfa.δ ⟨⟨qs,b⟩, qz⟩)) with
            | inl hl =>
                have dfa_tfun_conjunctions := dfa_tfun_w hl
                have kk1 := dfa_tfun_conjunctions.left
                apply QSubsetQ2
                rw [Set.element]
                exact kk1
            | inr hr =>
              apply Or.elim (hsorry)
              intro f
              apply False.elim (hr f)
              intro rdef
              have g := rdef.left
              exact g
          have k2 : dfa.E b := by
            cases (Classical.em (dfa.δ ⟨⟨qs,b⟩, qz⟩)) with
            | inl hl =>
              have dfa_tfun_conjunctions := dfa_tfun_w hl
              have kk1 := dfa_tfun_conjunctions.right.left
              exact kk1
            | inr hr =>
              apply Or.elim (hsorry)
              intro f
              apply False.elim (hr f)
              intro rdef
              have g := rdef.right.left
              exact g
          have k3: Q2 qz := by
            cases (Classical.em (dfa.δ ⟨⟨qs,b⟩, qz⟩)) with
            | inl hl =>
              have dfa_tfun_conjunctions := dfa_tfun_w hl
              have kk1 := dfa_tfun_conjunctions.right.right
              apply QSubsetQ2
              exact kk1
            | inr hr =>
              apply Or.elim (hsorry)
              intro f
              apply False.elim (hr f)
              intro rdef
              have gh := rdef.right.right
              rw [gh]
              have q2_fang : Q2 fang := by
                rw [← Q2_def_rfl]
                have hqq : (fun w => w ∈ dfa.toNFA.Q ∨ w = fang) fang = (fang ∈ dfa.toNFA.Q ∨ fang = fang) := by rfl
                rw [hqq]
                apply Or.inr
                have aea : fang = fang := by rfl
                exact aea
              exact q2_fang
          exact ⟨k1, k2, k3⟩

  have tot2: ∀ t : ((α × α) × α),
  (¬ (t.fst.snd ∈ dfa.E ∧ t.fst.fst ∈ Q2) ∨
    ∃ q2 : α, ⟨⟨t.fst.fst, t.fst.snd⟩, q2⟩ ∈ δ2
 ):= by
        intro triple
        match triple with
       | ⟨⟨qs,b⟩, qz⟩ =>
          simp [not_or_eq_implication]
          intro x
          simp [Set.element, ← delta_def_rfl]
          cases (Classical.em (∃y, dfa.δ ⟨⟨qs,b⟩, y⟩)) with
          | inl hl =>
            match hl with
            | ⟨y, hy⟩ =>
              exists y
              apply Or.inl
              exact hy
          | inr hr =>
            exists fang
            have hfang : fang = fang := rfl
            apply Or.inr
            exact ⟨hr, x.right, x.left, hfang⟩

  have uniqueness2 :
      ∀ t1 t2 : ((α × α) × α),
       ¬ ((t1 ∈ δ2) ∧ (t2 ∈ δ2)) ∨
        (¬ (t1.fst = t2.fst) ∨ t1.snd = t2.snd) := by
        intro triple1
        intro triple2
        rw [not_or_eq_implication]
        intro bed1
        rw [not_or_eq_implication]
        intro bed2
        simp[] at bed2
        have bed11 := bed1.left
        have bed12 := bed1.right
        have dfa_uniqueness := dfa.uniqueness triple1 triple2
        repeat rw [not_or_eq_implication] at dfa_uniqueness
        match triple1 with
        | ⟨first1, qz1⟩ =>
          match triple2 with
          |⟨first2, qz2⟩ =>
            simp [] at bed2
            simp [Set.element] at dfa_uniqueness
            simp [Set.element, ← delta_def_rfl] at bed11
            simp [bed2, Set.element, ← delta_def_rfl] at bed12
            simp []
            cases bed11 with
            | inl hl1 =>
              cases bed12 with
              | inl hl2 =>
                exact dfa_uniqueness ⟨hl1, hl2⟩ bed2
              | inr hr2 =>
                rw [bed2] at hl1
                have hNEtransition := hr2.left
                have exa :∃ a, dfa.δ ⟨first2,a⟩ := by
                  exists qz1
                have hfalse := hNEtransition exa
                apply False.elim hfalse
            | inr hr1 =>
              cases bed12 with
              | inl hl1 =>
                have hNEtransition := hr1.left
                rw [← bed2] at hl1
                have exa :∃ a, dfa.δ ⟨first1,a⟩ := by
                  exists qz2
                have hfalse := hNEtransition exa
                apply False.elim hfalse

              | inr hr2 =>
                have h_right := hr1.right.right.right
                have h_left := hr2.right.right.right
                simp [h_left, h_right]

    {tot := tot2, uniqueness := uniqueness2, Tfunction := Tfunction2, Q0 := dfa.Q0, Q:= Q2, E := dfa.E, δ := δ2, QEdisj := Q2Edisj, F := dfa.F, Q0subset := Q0SubsetQ2, Fsubset := FSubsetQ2, q0 := dfa.q0, bed_Q0 := dfa.bed_Q0 : TotalDFA}

----------------------------------GRAMMARS & AUTOMATA------------------------------
def ConstructRegularGrammarFromDFA {α : Type u} (dfa: @ DFA α) : @RegularGrammar α:=
  let E : Set α := dfa.E
  have E_def_refl : E = dfa.E := by rfl

  let V : Set α := dfa.Q
  have V_def_refl : V = dfa.Q := by rfl

  let S : α := dfa.q0
  have S_def_refl : S = dfa.q0 := by rfl

  let P : Set ((Word α) × (Word α)) :=
    fun rule : (Word α) × (Word α) =>
      (∃ql a qr : α, rule.fst = Word.mk [ql] ∧ rule.snd = Word.mk [a] ∘ Word.mk [qr] ∧ ⟨⟨ql,a⟩,qr⟩ ∈ dfa.δ)
      ∨ (∃ q, rule.fst = Word.mk [q] ∧ rule.snd = Word.epsilon ∧ (q ∈ dfa.F))
  have P_def_refl : P = fun rule : (Word α) × (Word α) =>
      (∃ql a qr : α, rule.fst = Word.mk [ql] ∧ rule.snd = Word.mk [a] ∘ Word.mk [qr] ∧ ⟨⟨ql,a⟩,qr⟩ ∈ dfa.δ)
      ∨ (∃ q, rule.fst = Word.mk [q] ∧ rule.snd = Word.epsilon ∧ (q ∈ dfa.F)) := by rfl

  have bed_VEdisj : V ∩ E = ∅ := by
    have QEdisj := dfa.QEdisj
    simp [E_def_refl, V_def_refl]
    exact QEdisj

  have bed_SinV: S ∈ V := by
    simp [S_def_refl, V_def_refl, Set.element]
    have q0inQ0 : dfa.q0 ∈ dfa.Q0 := by
      simp [Set.element]
      have bed_Q0 := dfa.bed_Q0
      rw [bed_Q0]
    have q0inQ := dfa.Q0subset dfa.q0 q0inQ0
    rw [Set.element] at q0inQ
    exact q0inQ

  have bed_VarInLeft:
    ∀ pair : (Word α) × (Word α),
    P pair -> (
      ∃ v1 v2 v3 : Word α,
        ((pair.fst) = (v1 ∘ v2 ∘ v3)) ∧
        (∃ t: α, (Word.mk ([t]) = v2 ∧ t ∈ V))
   ) := by
    intro pair
    intro pairInP
    simp [P_def_refl] at pairInP
    cases pairInP with
    | inl disj1 =>
      match disj1 with
      | ⟨ql, a, qr, disj1woE⟩ =>
        exists Word.mk []
        exists Word.mk [ql]
        exists Word.mk []
        simp [Word.concat]
        have k1 := disj1woE.left
        have k2 : ∃ t, t = ql ∧ t ∈ V := by
          exists ql
          simp []
          have disj3 := disj1woE.right.right
          rw [Set.element] at disj3
          have Tfunction := dfa.Tfunction
          have Tfunction2 := Tfunction ⟨⟨ql, a⟩,qr⟩
          rw [not_or_eq_implication] at Tfunction2
          have Tfunction3 := Tfunction2 disj3
          simp [] at Tfunction3
          have Tfunction31 := Tfunction3.left
          exact Tfunction31
        have k1Andk2 := And.intro k1 k2
        exact k1Andk2
    | inr disjB =>
        match disjB with
        | ⟨q, disjB2⟩ =>

          exists Word.mk []
          exists Word.mk [q]
          exists Word.mk []
          simp [Word.concat]
          have k2 : ∃ t, t = q ∧ t ∈ dfa.toNFA.Q := by
            exists q
            have qInQ : q ∈ dfa.Q := by
              have FsbQ := dfa.Fsubset
              have qInQ2 := FsbQ q disjB2.right.right
              exact qInQ2
            simp [qInQ]
          simp [disjB2.left, k2]
  have bed_reg: ∀ pair : ((Word α) × (Word α)),
    (pair ∈ P) ->
    (
      (∃ t: α, (Word.mk [t] = pair.fst) ∧ t ∈ V) ∧ (
        (∃ t1 t2 : α, (Word.mk [t1, t2] = pair.snd) ∧ t1 ∈ E ∧ t2 ∈ V) ∨
        (∃ t: α, Word.mk [t] = pair.snd ∧ t ∈ E) ∨
        pair.snd = Word.mk []
     )
   ) := by
      intro pair
      intro pairInP
      simp [P_def_refl] at pairInP
      cases pairInP with
      | inl disj1 =>
        match disj1 with
        | ⟨ql, a, qr, disj1woE⟩ =>
          simp [Word.concat]
          have k1 : ∃ t, t = ql ∧ t ∈ V := by
            exists ql
            simp []
            have disj3 := disj1woE.right.right
            rw [Set.element] at disj3
            have Tfunction := dfa.Tfunction
            have Tfunction2 := Tfunction ⟨⟨ql, a⟩,qr⟩
            rw [not_or_eq_implication] at Tfunction2
            have Tfunction3 := Tfunction2 disj3
            simp [] at Tfunction3
            have Tfunction31 := Tfunction3.left
            exact Tfunction31
          have k2 : ∃ t1 t2 : α, (Word.mk [t1, t2] = pair.snd) ∧ t1 ∈ E ∧ t2 ∈ V := by
            exists a
            exists qr
            simp [Word.concat] at disj1woE
            have k21 := disj1woE.right.left
            have Tfunction := dfa.Tfunction
            have k22 : a ∈ E ∧ qr ∈ V := by
              have pInQ := disj1woE.right.right
              have Tfunction2 := Tfunction ⟨⟨ql, a⟩,qr⟩
              rw [not_or_eq_implication] at Tfunction2
              have Tfunction3 := Tfunction2 pInQ
              simp [] at Tfunction3
              have Tfunction31 := Tfunction3.right
              rw [V_def_refl, E_def_refl]
              simp [Tfunction31]
            simp [k21, k22]
          simp [k2]
          exists ql
          simp [disj1woE.left]
          match k1 with
          | ⟨qrr, k1woE⟩ =>
          simp [←k1woE.left]
          exact k1woE.right
      | inr disjB =>
          match disjB with
          | ⟨q, disjB2⟩ =>
            have k1 : (∃ t, {data := [t]} = pair.fst ∧ t ∈ V) := by
              exists q
              have qInQ : q ∈ dfa.Q := by
                have FsbQ := dfa.Fsubset
                have qInQ2 := FsbQ q disjB2.right.right
                exact qInQ2
              simp [qInQ, disjB2.left]
            have k2: ((∃ t1 t2, {data := [t1, t2]} = pair.snd ∧ t1 ∈ E ∧ t2 ∈ V) ∨ (∃ t, {data := [t]} = pair.snd ∧ t ∈ E) ∨ pair.snd = {data := []}) := by
              apply Or.inr
              apply Or.inr
              simp [disjB2.right]
            simp [k1, k2]

    {V := V, E := E, S := S, P := P, bed_VEdisj := bed_VEdisj, bed_SinV := bed_SinV, bed_VarInLeft := bed_VarInLeft, bed_reg := bed_reg : RegularGrammar}

theorem deriviationsEQ1 {α : Type u} (dfa: @ DFA α)(w: Word α) :
     ∀ q1 q2 :α, (nfaDerivation dfa.toNFA q1 q2 w) -> (
      (∃ run, RunRegularGrammarSub q1 q2 (ConstructRegularGrammarFromDFA dfa) run w)):= by
  have hp := Word.objects_equal w
  rw [← hp]
  induction w.data with
  | nil =>
    intro q1
    intro q2
    intro deriviation
    simp [nfaDerivation] at deriviation
    exists []
  | cons x xs iv =>
    intro q11
    intro q22
    intro deriviation
    simp [nfaDerivation] at deriviation
    match deriviation with
    | ⟨qn, abl1⟩ =>
      have hh := iv qn q22 abl1.right
      simp [Word.concat]
      match hh with
      | ⟨run, hhz1⟩ =>
        exists ⟨Word.mk [q11], Word.mk [x,qn]⟩::run
        simp [Word.concat]
        simp [RunRegularGrammarSub]
        have hll : ∃ t1, (t1 = qn) ∧ (RunRegularGrammarSub t1 q22 (ConstructRegularGrammarFromDFA dfa) run {data := xs}) :=
          by exists qn
        simp [hll]
        have inDelta := abl1.left
        simp [Set.element, ConstructRegularGrammarFromDFA]
        apply Or.inl
        exists q11
        exists x
        exists qn

theorem notExistsEqAllSpecial : (¬∃ (runx : Word α × Word α) (runxs : List (Word α × Word α)), run2 = runx::runxs) → ∀ (runx : Word α × Word α) (runxs : List (Word α × Word α)), ¬run2 = runx::runxs := by
  intro h1
  intro h2
  intro h3
  intro ab
  have existsS : ∃ (runx : Word α × Word α) (runxs : List (Word α × Word α)), run2 = runx::runxs := by
    exists h2
    exists h3
  exact h1 existsS

theorem deriviationsEQ2 {α : Type u} (dfa: @ DFA α) (w: Word α) :
     ∀ q1 q2,(∃ run,
      (RunRegularGrammarSub q1 q2 (ConstructRegularGrammarFromDFA dfa) run w))-> (nfaDerivation dfa.toNFA q1 q2 w) := by
  have hp := Word.objects_equal w
  rw [← hp]
  induction w.data with
  | nil =>
    intro q1
    intro q2
    intro runex
    match runex with
    | ⟨run, runwo⟩ =>
      simp [RunRegularGrammarSub] at runwo
      simp [nfaDerivation, runwo]
  | cons x xs iv =>
    intro q11
    intro q22
    intro runex
    match runex with
    | ⟨run2, runwo⟩ =>
      cases (Classical.em (∃ runx runxs, run2 = runx::runxs)) with
      | inl runwoH =>
        match runwoH with
        | ⟨runx, runxs, runwoH2⟩ =>
          simp [runwoH2, RunRegularGrammarSub] at runwo
          simp [nfaDerivation]
          have runwo2 := runwo.right.right
          match runwo2 with
          | ⟨qn, runwo3⟩ =>
            exists qn
            have runwo4ex : ∃ run, RunRegularGrammarSub qn q22 (ConstructRegularGrammarFromDFA dfa) run {data := xs} := by
              exists runxs
              exact runwo3.right
            have iv2 := iv qn q22 runwo4ex
            simp [iv2]
            have pinG := runwo.left
            have runFirst := runwo.right.left
            have runRight := runwo3.left
            simp [Set.element, ConstructRegularGrammarFromDFA] at pinG
            cases pinG with
            | inl hll =>
              match hll with
              | ⟨q_1, w_1, q_r, pingnoE⟩ =>
                simp [Word.concat, runFirst, ←runRight] at pingnoE
                simp [pingnoE]
            | inr hrr =>
              match hrr with
              | ⟨q, hrr2⟩ =>
                have falseelimarg := hrr2.right.left
                simp [← runRight] at falseelimarg
      | inr hnexrun =>
        have runeqempty : run2 = [] := by
          apply EmptyNotFull
          have hn : (¬∃ runx runxs, run2 = runx::runxs) → (∀ runx runxs, ¬(run2 = runx::runxs)) := by
            intro h
            apply notExistsEqAllSpecial
            exact h
          apply hn
          exact hnexrun
        simp [runeqempty] at runwo
        simp [RunRegularGrammarSub] at runwo

theorem languageDFAeqConstructedRegularGrammar2 {α : Type u} (dfa : @DFA α) : (@nfaLanguage α dfa.toNFA) = (@LanguageRegularGrammar α (ConstructRegularGrammarFromDFA dfa)) := by
  apply Set.setext
  intro w
  apply Iff.intro
  intro wInNFALanguage
  simp [Set.element, nfaLanguage] at wInNFALanguage
  match wInNFALanguage with
  | ⟨qs, qn, wInNFALanguage2⟩ =>
    simp [Set.element, LanguageRegularGrammar]
    exists qn
    have runExists := deriviationsEQ1 dfa w qs qn wInNFALanguage2.right.right
    match runExists with
    | ⟨run, runExists2⟩ =>
      exists run
      have qsEqConstructedGrammarS : (ConstructRegularGrammarFromDFA dfa).toGrammar.S = qs := by
        simp [ConstructRegularGrammarFromDFA]
        have bedQ0 := dfa.bed_Q0
        have h := wInNFALanguage2.left
        rw [bedQ0] at h
        exact h
      rw [qsEqConstructedGrammarS]
      simp [runExists2]
      have qnToEpsilon : Grammar.P (ConstructRegularGrammarFromDFA dfa).toGrammar ({data := [qn]}, {data := []}) := by
        have qnInF := wInNFALanguage2.right.left
        simp [ConstructRegularGrammarFromDFA]
        apply Or.inr
        exists qn
      simp [qnToEpsilon]
  intro wInGrammarLanguage
  simp [Set.element, LanguageRegularGrammar] at wInGrammarLanguage
  match wInGrammarLanguage with
  | ⟨qn, run, wInGrammarLanguage2⟩ =>
    have kfalse : (∃ w1 z,
    w = w1 ∘ {data := [z]} ∧
      RunRegularGrammarSub (ConstructRegularGrammarFromDFA dfa).toGrammar.S qn (ConstructRegularGrammarFromDFA dfa)
          run w1 ∧
        Grammar.P (ConstructRegularGrammarFromDFA dfa).toGrammar ({data := [qn]}, {data := [z]})) -> False := by

      intro kfalse2
      match kfalse2 with
      | ⟨w1, z, kfalse3⟩ =>
        have kfalse4 := kfalse3.right.right
        simp [ConstructRegularGrammarFromDFA] at kfalse4
        cases (kfalse4) with
        | inl hl =>
          match hl with
          | ⟨ql, a, qr, hl2⟩ =>
            have hl3 := hl2.right.left
            simp [Word.concat] at hl3
        | inr hr =>
          match hr with
          | ⟨_, hr2⟩ =>
            exact hr2
    cases (wInGrammarLanguage2) with
    | inl hl =>
      simp [Set.element, nfaLanguage]
      have bed : (∃ run, (RunRegularGrammarSub (ConstructRegularGrammarFromDFA dfa).toGrammar.S qn (ConstructRegularGrammarFromDFA dfa) run  w)) := by
        exists run
        simp [hl.left]
      have q2 := deriviationsEQ2 dfa w (ConstructRegularGrammarFromDFA dfa).toGrammar.S qn bed
      exists (ConstructRegularGrammarFromDFA dfa).toGrammar.S
      exists qn
      simp [q2]
      simp [ConstructRegularGrammarFromDFA]
      have hlr := hl.right
      simp [ConstructRegularGrammarFromDFA] at hlr
      cases hlr with
      | inl hl5 =>
        match hl5 with
        | ⟨ql, a, qr, hl51⟩ =>
          have q0InQ0 := dfa.bed_Q0
          rw [q0InQ0]
          simp [hl51.left]
          have hfalse := hl51.right.left
          simp [Word.concat] at hfalse
      | inr hl6 =>
        match hl6 with
        | ⟨qz, hl7⟩ =>
          rw [Set.element] at hl7
          simp [hl7.left, hl7.right]
          have q0InQ0 := dfa.bed_Q0
          rw [q0InQ0]
    | inr hr =>
      have kfalse2 := kfalse hr
      apply False.elim
      exact kfalse2


-- Suma_constante_es_inyectiva.lean
-- Para todo c ∈ ℝ, la función f(x) = x+c es inyectiva.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 22-noviembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que, para todo c la función
--    f(x) = x + c
-- es inyectiva
-- ----------------------------------------------------------------------

import data.real.basic

open function

variable {c : ℝ}

-- 1ª demostración
-- ===============

example : injective (λ x, x + c) :=
begin
  assume x1 : ℝ,
  assume x2 : ℝ,
  assume h1 : (λ x, x + c) x1 = (λ x, x + c) x2,
  have h2 : x1 + c = x2 + c := h1,
  show x1 = x2,
    by exact (add_left_inj c).mp h2,
end

-- 2ª demostración
-- ===============

example : injective (λ x, x + c) :=
begin
  intros x1 x2 h,
  change x1 + c = x2 + c at h,
  apply add_right_cancel h,
end

-- 3ª demostración
-- ===============

example : injective (λ x, x + c) :=
begin
  intros x1 x2 h,
  apply (add_left_inj c).mp,
  exact h,
end

-- 4ª demostración
-- ===============

example : injective (λ x, x + c) :=
λ x1 x2 h, (add_left_inj c).mp h


/-
Copyright (c) 2023 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import field_theory.separable
import ring_theory.trace
import ring_theory.norm

/-

# Separable extensions

With infinite fields of characteristic `p`, weird things can happen with extensions, where
an irreducible polynomial can pick up repeated roots in an extension. A separable extension
is an extension where this pathology isn't occurring. Note that in examples like extensions
of number fields, this never happens, because number fields are characteristic zero.

-/

-- Let E ⊆ F be a field extension
variables (E F : Type) [field E] [field F] [algebra E F]

section separable_assumption

-- Here's how you say it's separable
variable [is_separable E F]

-- Separable extensions are algebraic by definition in Lean
example (f : F) : is_algebraic E f :=
begin
  apply is_integral.is_algebraic, -- suffices to prove f is integral over E
  apply is_separable.is_integral, -- follows from separability
end

end separable_assumption

-- finite-dimensional char 0 extensions are separable
example [finite_dimensional E F] [char_zero E] :
  is_separable E F := infer_instance -- typeclass inference can solve this as everything's a typeclass!

-- In the separable case, the minimum polynomial of `f : F` is guaranteed to have distinct roots in
-- a splitting field, and we can define traces and norms as sums or products of conjugates.
-- Let `Ω` be an algebraically closed extension of `E` (note: `Ω` doesn't have to contain `F`)

variables (Ω : Type) [field Ω] [algebra E Ω] [is_alg_closed Ω]

open_locale big_operators

example [is_separable E F] [finite_dimensional E F] (f : F) :
  algebra_map E Ω (algebra.norm E f) = ∏ σ : F →ₐ[E] Ω, σ f :=
algebra.norm_eq_prod_embeddings E Ω

example [is_separable E F] [finite_dimensional E F] (f : F) :
  algebra_map E Ω (algebra.trace E F f) = ∑ σ : F →ₐ[E] Ω, σ f :=
trace_eq_sum_embeddings Ω

-- Note the inconsistencies in both `algebra.trace/norm` inputs
-- and `trace/norm_eq_sum/prod_embeddings` inputs.

-- Now say we have a tower E ⊆ F ⊆ K
variables (K : Type) [field K] [algebra E K] [algebra F K] [is_scalar_tower E F K]

-- If the big extension is separable then so are the two smaller ones
example [is_separable E K] : is_separable E F := is_separable_tower_bot_of_is_separable E F K
example [is_separable E K] : is_separable F K := is_separable_tower_top_of_is_separable E F K

-- I can't find the claim that if F/E and K/F are separable then so is K/E. The proof I know
-- uses separable degree and I'm not sure we have that in Lean either.


import order.galois_connection
import exercise4

universe u

-- exercise 5(a)
-- Show that in a galois connection fg the right adjoint g preserves meets

lemma gPreservesMeets {A B : Type u} {f: A → B} {g: B → A} [partial_order A] [partial_order B] [gc : galois_connection f g]
      (M : set B) (m : B) (h : isMeet M m) : (isMeet (g '' M) (g m) ) :=
begin
  split,
  {
    intros n hn,
    cases hn with b hb,
    cases hb with b_in_M gb_is_n,
    have b_ge_n : b ≥ m := begin
      apply h.left,
      exact b_in_M,
    end,
    have gb_ge_gn : g b ≥ g m := begin
      apply gc.monotone_u,
      assumption,
    end,
    calc n  = g b : by rw gb_is_n
        ... ≥ g m : by exact gb_ge_gn
  },
  {
    intro a',
    intro ha',
    change a' ≤ g m,
    rw ← gc,
    apply h.right,
    intros b b_in_M,
    change f a' ≤ b,
    rw gc,
    apply ha',
    use b,
    use b_in_M,
  }
end

-- exercise 5(b)
-- Show that in a galois connection fg the left adjoint f preserves joins
lemma fPreservesJoins {A B : Type u} {f: A → B} {g: B → A} [partial_order A] [partial_order B] [gc : galois_connection f g]
      (N : set A) (n : A) (h : isJoin N n) : (isJoin (f '' N) (f n) ) :=
begin
  split,
  {
    intros m hm,
    cases hm with a ha,
    cases ha with a_in_N fa_is_m,
    have a_le_n : a ≤ n := begin
      apply h.left,
      exact a_in_N,
    end,
    have fa_le_fn : f a ≤ f n := begin
      apply gc.monotone_l,
      assumption,
    end,
    calc m  = f a : by rw fa_is_m
        ... ≤ f n : by exact fa_le_fn
  },
  {
    intro b',
    intro hb',
    rw gc,
    apply h.right,
    intros a a_in_N,
    rw ← gc,
    apply hb',
    use a,
    use a_in_N,
  }
end


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    s ∩ t = t ∩ s
-- ----------------------------------------------------------------------

import tactic

open set

variable {α : Type*}
variables (s t u : set α)

-- 1ª demostración
-- ===============

example : s ∩ t = t ∩ s :=
begin
  ext x,
  simp only [mem_inter_eq],
  split,
  { rintros ⟨xs, xt⟩,
    exact ⟨xt, xs⟩ },
  { rintros ⟨xt, xs⟩,
    exact ⟨xs, xt⟩ },
end

-- Prueba
-- ======

/-
α : Type u_1,
s t : set α
⊢ s ∩ t = t ∩ s
  >> ext x,
x : α
⊢ x ∈ s ∩ t ↔ x ∈ t ∩ s
  >> simp only [mem_inter_eq],
⊢ x ∈ s ∧ x ∈ t ↔ x ∈ t ∧ x ∈ s
  >> split,
| ⊢ x ∈ s ∧ x ∈ t → x ∈ t ∧ x ∈ s
|   >> { rintros ⟨xs, xt⟩,
| xs : x ∈ s,
| xt : x ∈ t
| ⊢ x ∈ t ∧ x ∈ s
|   >>   exact ⟨xt, xs⟩ },
⊢ x ∈ t ∧ x ∈ s → x ∈ s ∧ x ∈ t
  >> { rintros ⟨xt, xs⟩,
xt : x ∈ t,
xs : x ∈ s
⊢ x ∈ s ∧ x ∈ t
  >>   exact ⟨xs, xt⟩ },
no goals
-/

-- Comentarios:
-- 1. La táctica (ext x) transforma la conclusión (s = t) en
--    (x ∈ s ↔ x ∈ t).
-- 2. Se ha usado el lema
--    + mem_inter_eq x s t : x ∈ s ∩ t = (x ∈ s ∧ x ∈ t)

-- Comprobación:
variable (x : α)
-- #check @mem_inter_eq _ x s t

-- 2ª demostración
-- ===============

example : s ∩ t = t ∩ s :=
ext $ λ x, ⟨λ ⟨xs, xt⟩, ⟨xt, xs⟩, λ ⟨xt, xs⟩, ⟨xs, xt⟩⟩

-- Comentario: La notación `f $ ...`  es equivalente a `f (...)`.

-- 3ª demostración
-- ===============

example : s ∩ t = t ∩ s :=
by ext x; simp [and.comm]

-- 4ª demostración
-- ===============

example : s ∩ t = t ∩ s :=
inf_comm

-- 5ª demostración
-- ===============

example : s ∩ t = t ∩ s :=
begin
  apply subset.antisymm,
  { rintros x ⟨xs, xt⟩,
    exact ⟨xt, xs⟩ },
  { rintros x ⟨xt, xs⟩,
    exact ⟨xs, xt⟩ },
end

-- Prueba
-- ======

/-
α : Type u_1,
s t : set α
⊢ s ∩ t = t ∩ s
  >> apply subset.antisymm,
| ⊢ s ∩ t ⊆ t ∩ s
|   >> { rintros x ⟨xs, xt⟩,
| x : α,
| xs : x ∈ s,
| xt : x ∈ t
| ⊢ x ∈ t ∩ s
|   >>   exact ⟨xt, xs⟩ },
⊢ t ∩ s ⊆ s ∩ t
  >> { rintros x ⟨xt, xs⟩,
x : α,
xt : x ∈ t,
xs : x ∈ s
⊢ x ∈ s ∩ t
  >>   exact ⟨xs, xt⟩ },
no goals
-/


-- 4.6 Exercises

-- #1

variables (α : Type*) (p q : α → Prop)

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) :=
iff.intro
(assume h: ∀ x, p x ∧ q x,
  have hp: ∀ x, p x := (assume a: α, (h a).left),
  have hq: ∀ x, q x := (assume a: α, (h a).right),
  show (∀ x, p x) ∧ (∀ x, q x),
  from ⟨hp, hq⟩
)
(assume h: (∀ x, p x) ∧ (∀ x, q x),
  have hp: ∀ x, p x := h.left,
  have hq: ∀ x, q x := h.right,
  show ∀ x, p x ∧ q x,
  from assume a: α, ⟨hp a, hq a⟩
)

example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) :=
assume hapq: ∀ x, p x → q x,
assume hap: ∀ x, p x,
assume a: α, show q a, from (hapq a) (hap a)

example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x :=
assume h: (∀ x, p x) ∨ (∀ x, q x),
assume a: α, show p a ∨ q a, from
or.elim h
(assume hap: ∀ x, p x, show p a ∨ q a, from or.inl (hap a))
(assume haq: ∀ x, q x, show p a ∨ q a, from or.inr (haq a))


/- QUICK NOTE: -/
/-
This was made by Charlie and Ben with no
consultation with Professor Sullivan.

This is just some review that we thought you should have
while Professor Sullivan is finishing HW9.

While most of these proofs were not written to be difficult,
some turned out to be particularly challenging (which I've marked below).

This is not necessarily representative of what you will see on your
exam, unlike homework 9.

As always -- if we've made any mistakes or you have any questions
please post on Piazza. :)
-/

-- For all proofs, please use provablesystems.com
-- for a reference guide on how to start these proofs

-- Also, you may see the same proofs written two different ways:
example {P : Prop} : P → P :=
begin
    assume p,
    exact p,
end

-- Prof. Sullivan likes forall more
-- so almost all proofs onward will appear with a forall
-- like this:
example : ∀ P : Prop, P → P :=
begin
    assume P,
    assume p,
    exact p,
end

/- START OF REVIEW -/

/- IMPLICATION -/
example : ∀ P, P → P :=
begin
    assume P,
    assume p,
    exact p,
end
example : ∀ P Q, P → Q → P :=
begin
    assume P Q,
    assume p q,
    exact p,
end
example : (0 = 1) → (0 = 1) :=
begin
    assume pf,
    exact pf,
end

-- You will use implication extensively throughout
-- the remainder of this review

/- TRUE -/
-- intro
example : true :=
begin
    exact true.intro,
end
example : ∀ P Q R : Prop, true :=
begin
    assume P Q R,
    exact true.intro,
end
example : (0 = 1) → true :=
begin
    assume pf,
    exact true.intro,
end

/- FALSE -/
example : false → (0 = 1) :=
begin
    assume f,
    exact false.elim f,
end
example : ∀ P : Prop, false → P :=
begin
    assume P,
    assume f,
    exact false.elim f,
end
example : ∀ P Q, P → Q → false → (P ∧ Q) :=
begin
    assume P Q,
    assume p q f,
    exact false.elim f,
end

/- EQUALITY -/
example : 3 = 3 :=
begin
    exact eq.refl 3,
end
example : ∀ n : nat, n = n :=
begin
    assume n,
    exact eq.refl n,
end

/- AND -/
-- intro (and.intro _ _)
example : ∀ P Q : Prop, P → Q → (P ∧ Q) :=
begin
    assume P Q,
    assume p q,
    exact and.intro p q,
end
example {P Q : Prop} : P → Q → (Q ∧ P) :=
begin
    assume p q,
    exact and.intro q p,
end
example : (0 = 0) ∧ (1 = 1) :=
begin
    apply and.intro,
    exact eq.refl 0,
    exact rfl, -- shortcut
end

-- elim (and.elim_left _ and and.right _ )
example : ∀ P Q : Prop, P ∧ Q → P :=
begin
    intros, -- shortcut for all assumptions (except negation assumptions)
    exact and.elim_left a,
end
example : ∀ P Q : Prop, P ∧ Q → Q :=
begin
    intros,
    exact and.elim_right a,
end
example : ∀ P Q R : Prop, P ∧ Q ∧ R → Q :=
begin
    intros,
    apply and.elim_left (and.elim_right a),
end

-- both
example : ∀ P Q R : Prop, P ∧ Q ∧ R → (P ∧ R) :=
begin
    intros,
    apply and.intro,
    exact and.elim_left a,
    exact and.elim_right (and.elim_right a),
end
example : ∀ P Q R : Prop, Q ∧ R ∧ P → (R ∧ P ∧ Q) :=
begin
    assume P Q R,
    assume qrp,
    apply and.intro,
    exact qrp.2.1, -- shortcut for and.elim_left (and.elim_right qrp)
    apply and.intro,
    exact qrp.2.2,
    exact qrp.1,
end

/- OR -/
-- intro (or.intro_left _ _, shortcut: or.inl _ and the associated right-side variant)
example : ∀ P Q, P → (P ∨ Q) :=
begin
    assume P Q,
    assume p,
    exact or.intro_left Q p,
end
example : ∀ P Q R, (P ∧ R) → (P ∨ Q) :=
begin
    assume P Q R,
    assume pr,
    exact or.inl (and.elim_left pr), -- or.inl shortcut for or.intro_left (doesn't require Prop argument)
end
example : (0 = 1) ∨ (1 = 1) :=
begin
    right, -- shortcut for or.inr (also left works for or.inl)
    refl, -- shortcut for apply/exact rfl,
end

--elim (or.elim _ _ _ )
--remember you can use the 'cases' keyword
example : ∀ P Q R, (P → R) → (Q → R) → (P ∨ Q) → R :=
begin
    assume P Q R,
    assume pr,
    assume qr,
    assume porq,
    exact or.elim porq pr qr,
end
example : ∀ P Q, (P → Q) → (P ∨ Q) → Q :=
begin
    intros,
    cases a_1,
        -- case 1
        apply a,
        exact a_1,
        -- case 2
        exact a_1,
end

/- BI-IMPLICATION -/
-- intro
example : ∀ P Q, (P → Q) → (Q → P) → (P ↔ Q) :=
begin
    assume P Q,
    assume pq qp,
    exact iff.intro pq qp,
end

-- elim (iff.elim _ )
example : ∀ P Q : Prop, (P ↔ Q) → (P → Q) :=
begin
    assume P Q,
    assume piffq,
    assume p,
    exact (iff.elim_left piffq) p,
end
example : ∀ P Q : Prop, (P ↔ Q) → ((P → Q) ∧ (Q → P)) :=
begin
    intros,
    apply and.intro,
    exact iff.elim_left a,
    exact iff.elim_right a,
end

-- both
-- this is a particularly challenging proof
example : ∀ P Q R : Prop, (P ↔ Q) → (Q ↔ R) → (R ↔ P) :=
begin
    intros,
    apply iff.intro,
    assume r,
    -- One line solution:
    -- exact (iff.elim_right a) ((iff.elim_right a_1) r),
    -- OR you can do it with 'have' statements
    have pimpq : Q → P := iff.elim_right a,
    have rimpq : R → Q := iff.elim_right a_1,
    exact pimpq (rimpq r),
    assume p,
    exact (iff.elim_left a_1) ((iff.elim_left a) p),
end

/- NEGATION -/
example : ∀ P : Prop, ¬ P → (P → false) :=
begin
    assume P,
    assume np,
    exact np,
end
example : ∀ P Q : Prop, (P ∧ Q) → ¬ P → false :=
begin
    intros,
    exact a_1 (and.elim_left a),
end

/- FORALL -/ -- which we've been using extensively up until this point already
example : ∀ P Q : Prop, (∀ p : P, Q) → (P → Q) :=
begin
    assume P Q,
    assume funcPtoQ,
    -- exact funcPtoQ, OR you can try...
    assumption, -- shortcut if your proof statement is in your context menu
end

/- EXISTS -/
-- intro
example : ∀ n : nat, ∃ m, n = m :=
begin
    assume n,
    apply exists.intro n,
    refl,
end

-- Below is the property isEven.
-- A natural number is even if there exists
-- another natural number k such that 2k = n

def isEven : ℕ → Prop :=
    λ n, ∃ k, 2 * k = n

-- Define isOdd below.
-- A natural number is odd if there exists
-- another natural number k such that 2k + 1 = n

def isOdd : ℕ → Prop :=
    λ n : nat, ∃ k : nat, (2 * k + 1) = n

-- elim/both
-- this is a particularly challenging proof
example : ∀ n : nat, (isEven n) → (isEven (2*n)) :=
begin
    unfold isEven, -- "desugars" isEven to its definition above
    intros,
    apply exists.elim a,
    assume witness,
    assume proof_that_n_is_even_with_witness,
    rewrite<- proof_that_n_is_even_with_witness,
    apply exists.intro (2*witness),
    refl,
end

-- Alternatively, because we know that 2*<any number> is even
-- you can shorten the proof:
example : ∀ n : nat, (isEven n) → (isEven (2*n)) :=
begin
    unfold isEven, -- "desugars" isEven to its definition above
    intros,
    apply exists.intro n,
    refl,
end


-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería data.set.lattice
-- 2. Abrir el espacio de nombres set.
-- 3. Declarar α una variable de tipos.
-- 4. Declarar s una vabiable sobre conjuntos de conjuntos de elementos
--    de α.
-- ----------------------------------------------------------------------

import data.set.lattice      -- 1
open set                     -- 2
variable {α : Type*}         -- 3
variable (s : set (set α))   -- 4

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ⋃₀ s = ⋃ t ∈ s, t
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example : ⋃₀ s = ⋃ t ∈ s, t :=
begin
  ext x,
  rw mem_bUnion_iff,
  refl,
end

-- Prueba
-- ======

/-
α : Type u_1,
s : set (set α)
⊢ ⋃₀ s = ⋃ (t : set α) (H : t ∈ s), t
  >> ext x,
x : α
⊢ x ∈ ⋃₀ s ↔ x ∈ ⋃ (t : set α) (H : t ∈ s), t
  >> rw mem_bUnion_iff,
⊢ x ∈ ⋃₀ s ↔ ∃ (x_1 : set α) (H : x_1 ∈ s), x ∈ x_1
  >> refl,
no goals
-/

-- Comentario: Se ha usado el lema
-- + mem_bUnion_iff: y ∈ (⋃ x ∈ s, t x) ↔ ∃ x ∈ s, y ∈ t x

-- 2ª demostración
-- ===============

example : ⋃₀ s = ⋃ t ∈ s, t :=
sUnion_eq_bUnion

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ⋂₀ s = ⋂ t ∈ s, t
-- ----------------------------------------------------------------------

-- 1ª demostración
-- ===============

example : ⋂₀ s = ⋂ t ∈ s, t :=
begin
  ext x,
  rw mem_bInter_iff,
  refl,
end

-- Prueba
-- ======

/-
α : Type u_1,
s : set (set α)
⊢ ⋂₀ s = ⋂ (t : set α) (H : t ∈ s), t
  >> ext x,
x : α
⊢ x ∈ ⋂₀ s ↔ x ∈ ⋂ (t : set α) (H : t ∈ s), t
  >> rw mem_bInter_iff,
⊢ x ∈ ⋂₀ s ↔ ∀ (x_1 : set α), x_1 ∈ s → x ∈ x_1
  >> refl,
no goals
-/

-- Comentario: Se ha usado el lema
-- + mem_bInter_iff : y ∈ (⋂ x ∈ s, t x) ↔ ∀ x ∈ s, y ∈ t x

-- 2ª demostración
-- ===============

example : ⋂₀ s = ⋂ t ∈ s, t :=
sInter_eq_bInter


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import topology.continuous_on

/-!
# Left and right continuity

In this file we prove a few lemmas about left and right continuous functions:

* `continuous_within_at_Ioi_iff_Ici`: two definitions of right continuity
  (with `(a, ∞)` and with `[a, ∞)`) are equivalent;
* `continuous_within_at_Iio_iff_Iic`: two definitions of left continuity
  (with `(-∞, a)` and with `(-∞, a]`) are equivalent;
* `continuous_at_iff_continuous_left_right`, `continuous_at_iff_continuous_left'_right'` :
  a function is continuous at `a` if and only if it is left and right continuous at `a`.

## Tags

left continuous, right continuous
-/

open set filter
open_locale topological_space

section partial_order

variables {α β : Type*} [topological_space α] [partial_order α] [topological_space β]

lemma continuous_within_at_Ioi_iff_Ici {a : α} {f : α → β} :
  continuous_within_at f (Ioi a) a ↔ continuous_within_at f (Ici a) a :=
by simp only [← Ici_diff_left, continuous_within_at_diff_self]

lemma continuous_within_at_Iio_iff_Iic {a : α} {f : α → β} :
  continuous_within_at f (Iio a) a ↔ continuous_within_at f (Iic a) a :=
@continuous_within_at_Ioi_iff_Ici (order_dual α) _ ‹topological_space α› _ _ _ f

end partial_order

variables {α β : Type*} [topological_space α] [linear_order α] [topological_space β]

lemma nhds_left_sup_nhds_right (a : α) :
  𝓝[≤] a ⊔ 𝓝[≥] a = 𝓝 a :=
by rw [← nhds_within_union, Iic_union_Ici, nhds_within_univ]

lemma nhds_left'_sup_nhds_right (a : α) :
  𝓝[<] a ⊔ 𝓝[≥] a = 𝓝 a :=
by rw [← nhds_within_union, Iio_union_Ici, nhds_within_univ]

lemma nhds_left_sup_nhds_right' (a : α) :
  𝓝[≤] a ⊔ 𝓝[>] a = 𝓝 a :=
by rw [← nhds_within_union, Iic_union_Ioi, nhds_within_univ]

lemma continuous_at_iff_continuous_left_right {a : α} {f : α → β} :
  continuous_at f a ↔ continuous_within_at f (Iic a) a ∧ continuous_within_at f (Ici a) a :=
by simp only [continuous_within_at, continuous_at, ← tendsto_sup, nhds_left_sup_nhds_right]

lemma continuous_at_iff_continuous_left'_right' {a : α} {f : α → β} :
  continuous_at f a ↔ continuous_within_at f (Iio a) a ∧ continuous_within_at f (Ioi a) a :=
by rw [continuous_within_at_Ioi_iff_Ici, continuous_within_at_Iio_iff_Iic,
  continuous_at_iff_continuous_left_right]


/- Lecture 1.2: Basics — Proofs -/

/- Computation -/

-- β-reduction (reduce λ-abstraction + application)
example {α β : Type} (f : α → β) (a : α) :
  (λx, f x) a = f a :=
by refl

-- δ-reduction (unfold definition)
section

def f (n : ℕ) : ℕ := n + n

example (m : ℕ) : f m = m + m :=
by refl

end

-- ι-reduction (projection)
example {α β : Type} (a : α) (b : β) :
  prod.fst (a, b) = a :=
by refl

-- ζ-reduction (`let`-unfolding)
example : (let x : ℕ := 2 in 2 + 2) = 4 :=
by refl

-- η-reduction
example {α β : Type} (f : α → β) : (λx, f x) = f :=
by refl

/- Natural numbers -/

namespace my_nat

def add : nat → nat → nat
| m 0            := m
| m (nat.succ n) := nat.succ (add m n)

/- Commutativity of `add` -/

lemma add_zero : ∀n : ℕ, add 0 n = n
| 0            := by refl
| (nat.succ m) := by simp [add, add_zero m]

example (n : ℕ) : add 0 n = n :=
begin
  induction n,
  { refl },
  { simp [add, n_ih] }
end

example (n : ℕ) : add 0 n = n :=
by induction n; simp [add, *]

lemma add_succ : ∀m n : ℕ, add (nat.succ m) n = nat.succ (add m n)
| m 0            := by refl
| m (nat.succ n) := by simp [add, add_succ m n]

lemma add_comm : ∀m n : ℕ, add m n = add n m
| m 0            := by simp [add, add_zero]
| m (nat.succ n) := by simp [add, add_succ, add_comm m n]

example : ∀m n : ℕ, add m n = add n m
| m 0            := by simp [add, add_zero]
| m (nat.succ n) :=
  have add_succ : ∀m n : ℕ, add (nat.succ m) n = nat.succ (add m n) :=
    by intros m n; induction n; simp [add, *],
  by simp [add, add_succ, add_comm m n]

example : ∀m n : ℕ, add m n = add n m
| m 0            := by simp [add, add_zero]
| m (nat.succ n) :=
  have ih : _ := add_comm m n,
  by simp [add, add_succ, ih]

/- Associativity of `add` -/

lemma add_assoc : ∀l m n : ℕ, add (add l m) n = add l (add m n)
| l m 0            := by refl
| l m (nat.succ n) := by simp [add, add_assoc l m n]

instance : is_commutative ℕ add := ⟨add_comm⟩
instance : is_associative ℕ add := ⟨add_assoc⟩

def mul : ℕ → ℕ → ℕ
| _ 0            := 0
| m (nat.succ n) := add m (mul m n)

/- Distributivity -/

lemma mul_add (l m : ℕ) : ∀n : ℕ, mul l (add m n) = add (mul l m) (mul l n)
| 0            := by refl
| (nat.succ l) := begin simp [add, mul, mul_add l], ac_refl end

end my_nat

/- Lists -/

def reverse {α : Type} : list α → list α
| []        := []
| (x :: xs) := reverse xs ++ [x]

/- `reverse` of `reverse` is identity -/
lemma reverse_append {α : Type} :
  ∀xs ys : list α, reverse (xs ++ ys) = reverse ys ++ reverse xs
| [] ys := by simp[reverse]
| (x :: xs) ys := begin simp[reverse, reverse_append] end

lemma reverse_reverse {α : Type} : ∀xs : list α, reverse (reverse xs) = xs
| [] := by simp[reverse]
| (x :: xs) := begin simp[reverse], simp[reverse_append], rw[reverse_reverse], rw[reverse], rw[reverse], refl  end

def map {α : Type} {β : Type} (f : α → β) : list α → list β
| [] := []
| (x :: xs) :=  f x :: map xs

-- lemma reverse_append {α : Type} :
--   ∀xs ys : list α, reverse (xs ++ ys) = reverse ys ++ reverse xs
-- | []        ys := by simp [reverse]
-- | (x :: xs) ys := begin simp [reverse, reverse_append xs] end

-- lemma reverse_reverse {α : Type} : ∀xs : list α, reverse (reverse xs) = xs
-- | []        := by refl
-- | (x :: xs) := by simp [reverse, reverse_append, reverse_reverse xs]

-- def map {α : Type} {β : Type} (f : α → β) : list α → list β
-- | []        := []
-- | (x :: xs) := f x :: map xs

/- Functorial properties of `map` -/

lemma map_ident {α : Type} : ∀xs : list α, map (λx, x) xs = xs
| []        := by refl
| (x :: xs) := begin rw[map],  end

example {α : Type} : ∀xs : list α, map (λx, x) xs = xs
| []        := by refl
| (x :: xs) :=
  have ih : map (λx, x) xs = xs := _example xs,
  begin rw map, rw ih end

example {α : Type} : ∀xs : list α, xs = map (λx, x) xs
| []        := by refl
| (x :: xs) :=
  have ih : _ := _example xs,
  begin rw [map, ←ih] end

lemma map_comp {α β γ : Type} (f : α → β) (g : β → γ) :
  ∀xs : list α, map (λx, g (f x)) xs = map g (map f xs)
| []        := by refl
| (x :: xs) := begin rw[map], rw[map_comp], rw[map], rw[map] end

lemma map_append {α β : Type} (f : α → β) :
  ∀xs ys : list α, map f (xs ++ ys) = map f xs ++ map f ys
| []        ys := by refl
| (x :: xs) ys := begin rw[map], rw[map_append], end

lemma map_reverse {α β : Type} (f : α → β) :
  ∀xs : list α, map f (reverse xs) = reverse (map f xs)
| []        := by refl
| (x :: xs) := by simp [map, reverse, map_append, map_reverse xs]

/- RGB values -/

structure rgb :=
(red green blue : ℕ)

def shuffle (c : rgb) : rgb :=
{red := c.green, green := c.blue, blue := c.red}

lemma shuffle_shuffle_shuffle (c : rgb) : shuffle (shuffle (shuffle c)) = c :=
by cases c; refl

example (c : rgb) : shuffle (shuffle (shuffle c)) = c :=
begin cases c, refl end

example : ∀c : rgb, shuffle (shuffle (shuffle c)) = c
| ⟨_, _, _⟩ := by refl


import data.nat.prime
import algebra.big_operators
import tactic

example (n : ℕ) : n.succ ≠ 0 := n.succ_ne_zero

example (m n : ℕ) (h : m.succ = n.succ) : m = n := nat.succ.inj h

def fac : ℕ → ℕ
| 0  := 1
| (n + 1) := (n + 1) * fac n

example : fac 0 = 1 := rfl
example : fac 0 = 1 := by rw fac
example : fac 0 = 1 := by simp [fac]

example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := rfl
example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := by rw fac
example (n : ℕ) : fac (n + 1) = (n + 1) * fac n := by simp [fac]

theorem fac_pos (n : ℕ) : 0 < fac n :=
begin
  induction n with n ih,
  { from zero_lt_one,},
    rw fac,
    apply mul_pos,
    from n.succ_pos,
    from ih,
end

theorem dvd_fac {i n : ℕ} (ipos : 0 < i) (ile : i ≤ n) : i ∣ fac n :=
begin
  induction n with n ih,
  { linarith,},
    rw fac,
    cases nat.of_le_succ ile with h h,
    { apply dvd_mul_of_dvd_right, from ih h,},
      rw h,
      apply dvd_mul_right,
end

theorem pow_two_le_fac (n : ℕ) : 2^(n-1) ≤ fac n :=
begin
  cases n with n,
  { simp [fac],},
    induction n with n ih,
    { simp [fac],},
      rw fac,
      simp, simp at ih,
      apply mul_le_mul',
      { rw nat.succ_eq_add_one,
        linarith,},
      from ih,
end

section
variables {α : Type*} (s : finset ℕ) (f : ℕ → ℕ) (n : ℕ)

#check finset.sum s f
#check finset.prod s f

open_locale big_operators
open finset

example : s.sum f = ∑ x in s, f x := rfl
example : s.prod f = ∏ x in s, f x := rfl

example : (range n).sum f = ∑ x in range n, f x := rfl
example : (range n).prod f = ∏ x in range n, f x := rfl

example (f : ℕ → ℕ) : ∑ x in range 0, f x = 0 :=
finset.sum_range_zero f

example (f : ℕ → ℕ) (n : ℕ): ∑ x in range n.succ, f x = (∑ x in range n, f x) + f n :=
finset.sum_range_succ f n

example (f : ℕ → ℕ) : ∏ x in range 0, f x = 1 :=
finset.prod_range_zero f

example (f : ℕ → ℕ) (n : ℕ): ∏ x in range n.succ, f x = (∏ x in range n, f x) * f n :=
finset.prod_range_succ f n

example (n : ℕ) : fac n = ∏ i in range n, (i + 1) :=
begin
  induction n with n ih,
  { simp [fac],},
  { rw [fac, prod_range_succ, ih, mul_comm],},
end

theorem sum_id (n : ℕ) : ∑ i in range (n + 1), i = n * (n + 1) / 2 :=
begin
  symmetry, apply nat.div_eq_of_eq_mul_right (by norm_num : 0 < 2),
  induction n with n ih,
  { simp,},
  { rw [sum_range_succ, mul_add 2, ←ih, nat.succ_eq_add_one],
    ring_nf,}
end

theorem sum_sqr (n : ℕ) : ∑ i in range (n + 1), i^2 = n * (n + 1) * (2 *n + 1) / 6 :=
begin
  symmetry, apply nat.div_eq_of_eq_mul_right (by norm_num : 0 < 6),
  induction n with n ih,
  { simp,},
  { rw [sum_range_succ, mul_add 6, ←ih, nat.succ_eq_add_one],
    ring,},
end

end

section
inductive my_nat
| zero : my_nat
| succ : my_nat → my_nat

namespace my_nat

def add : my_nat → my_nat → my_nat
| x zero     := x
| x (succ y) := succ (add x y)

def mul : my_nat → my_nat → my_nat
| x zero     := zero
| x (succ y) := add (mul x y) x

def pred : my_nat → my_nat
| zero := zero
| (succ x) := x

def tsub : my_nat → my_nat → my_nat
| n zero := n
| n (succ m) := tsub (n.pred) m

theorem zero_add (n : my_nat) : add zero n = n :=
begin
  induction n with n ih,
  { refl },
  rw [add, ih]
end

theorem succ_add (m n : my_nat) : add (succ m) n = succ (add m n) :=
begin
  induction n with n ih,
  { refl },
  rw [add, ih],
  refl
end

theorem add_comm (m n : my_nat) : add m n = add n m :=
begin
  induction n with n ih,
  { rw zero_add, refl },
  rw [add, succ_add, ih]
end

theorem add_assoc (m n k : my_nat) : add (add m n) k = add m (add n k) :=
begin
  induction k with k ih,
  { simp only [add],},
  { rw [add, ih, ←add, ←add],}
end

theorem mul_add  (m n k : my_nat) : mul m (add n k) = add (mul m n) (mul m k) :=
begin
  induction k with k ih,
  { simp only [add, mul],},
  { rw [add, mul, ih, add_assoc, ←mul],},
end

theorem zero_mul (n : my_nat) : mul zero n = zero :=
begin
  induction n with n ih,
  { simp [mul]},
  { rw [mul, add, ih],},
end

theorem succ_mul (m n : my_nat) : mul (succ m) n = add (mul m n) n :=
begin
  induction n with n ih,
  { simp only [mul, add],},
  { rw [mul, ih, add, mul, add_assoc, ←add, add_comm n m, ←add, ←add_assoc],}
end

theorem mul_comm (m n : my_nat) : mul m n = mul n m :=
begin
  induction n with n ih,
  { simp only [mul, zero_mul],},
  { rw [succ_mul, ←ih, mul],},
end

theorem succ_pred_self (n : my_nat) : n.succ.pred = n :=
begin
  rw pred,
end

theorem sub_self (n : my_nat) : tsub n n = zero :=
begin
  induction n with n ih,
  { simp only [tsub],},
  { rw [tsub, succ_pred_self, ih],}
end

end my_nat

end


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que para todo número real c, la función
--    f(x) x  + c
-- es suprayectiva.
-- ----------------------------------------------------------------------

import data.real.basic

variable {c : ℝ}

open function

-- 1ª demostración
-- ===============

example : surjective (λ x, x + c) :=
begin
  intro x,
  use x - c,
  dsimp,
  ring,
end

-- Su desarrollo es
--
-- c : ℝ
-- ⊢ surjective (λ (x : ℝ), x + c)
--    >> intro x,
-- ⊢ ∃ (a : ℝ), (λ (x : ℝ), x + c) a = x
--    >> use x - c,
-- ⊢ (λ (x : ℝ), x + c) (x - c) = x
--    >> dsimp,
-- ⊢ x - c + c = x
--    >> ring,
-- no goals

-- 2ª demostración
-- ===============

example : surjective (λ x, x + c) :=
begin
  intro x,
  use x - c,
  change (x - c) + c = x,
  ring,
end

-- Su desarrollo es
--
-- c : ℝ
-- ⊢ surjective (λ (x : ℝ), x + c)
--    >> intro x,
-- ⊢ ∃ (a : ℝ), (λ (x : ℝ), x + c) a = x
--    >> use x - c,
-- ⊢ (λ (x : ℝ), x + c) (x - c) = x
--    >> change (x - c) + c = x,
-- ⊢ x - c + c = x
--    >> ring,
-- no goals


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

open_locale nat rat real big_operators topological_space

theorem induction_sumkexp3eqsumksq
  (n : ℕ) :
  ∑ k in finset.range n, k^3 = (∑ k in finset.range n, k)^2 :=
begin
  symmetry,
  induction n with j IH,
  {
    refl,
  },
  {
    calc (∑ (k : ℕ) in finset.range j.succ, k)^2 = ((∑ (k : ℕ) in finset.range j, k) + j)^2 : by rw finset.sum_range_succ
   ... = (∑ (k : ℕ) in finset.range j, k)^2 + 2 * (∑ (k : ℕ) in finset.range j, k) * j + j^2 : by rw add_sq _ _
   ... = (∑ (k : ℕ) in finset.range j, k)^2 +  (∑ (k : ℕ) in finset.range j, k) * 2 * j + j^2 : by ring
   ... = (∑ (k : ℕ) in finset.range j, k)^2 + (j * (j-1)) * j + j^2 : by rw finset.sum_range_id_mul_two j
   ... = (∑ (k : ℕ) in finset.range j, k^3) + (j * (j-1)) * j + j^2 : by rw IH
   ... = (∑ (k : ℕ) in finset.range j, k^3) + j^2 * (j-1) + j^2 : by ring_nf
   ... = (∑ (k : ℕ) in finset.range j, k^3) + j^3 : by cases j ; [norm_num, ring_nf]
   ... = (∑ (k : ℕ) in finset.range j.succ, k^3) : by rw ← finset.sum_range_succ,
  }
end


/-
Copyright (c) 2020 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou, Yury Kudryashov
-/
import analysis.normed.group.basic
import algebra.indicator_function

/-!
# Indicator function and norm

This file contains a few simple lemmas about `set.indicator` and `norm`.

## Tags
indicator, norm
-/

variables {α E : Type*} [semi_normed_group E] {s t : set α} (f : α → E) (a : α)

open set

lemma norm_indicator_eq_indicator_norm :
  ∥indicator s f a∥ = indicator s (λa, ∥f a∥) a :=
flip congr_fun a (indicator_comp_of_zero norm_zero).symm

lemma nnnorm_indicator_eq_indicator_nnnorm :
  ∥indicator s f a∥₊ = indicator s (λa, ∥f a∥₊) a :=
flip congr_fun a (indicator_comp_of_zero nnnorm_zero).symm

lemma norm_indicator_le_of_subset (h : s ⊆ t) (f : α → E) (a : α) :
  ∥indicator s f a∥ ≤ ∥indicator t f a∥ :=
begin
  simp only [norm_indicator_eq_indicator_norm],
  exact indicator_le_indicator_of_subset ‹_› (λ _, norm_nonneg _) _
end

lemma indicator_norm_le_norm_self : indicator s (λa, ∥f a∥) a ≤ ∥f a∥ :=
indicator_le_self' (λ _ _, norm_nonneg _) a

lemma norm_indicator_le_norm_self : ∥indicator s f a∥ ≤ ∥f a∥ :=
by { rw norm_indicator_eq_indicator_norm, apply indicator_norm_le_norm_self }


import game.world3.level1 -- hide
import mynat.mul -- hide

namespace mynat -- hide

/-
# Multiplication World

## Level 2: `mul_one`

Remember that you can see everything you have proved so far about multiplication in
the drop-down box on the left (and that this list will grow as we proceed).

In this level we'll need to use

* `one_eq_succ_zero : 1 = succ(0)`

which was mentioned back in Addition World and
which will be a useful thing to rewrite right now, as we
begin to prove a couple of lemmas about how `1` behaves
with respect to multiplication.
-/

/- Lemma
For any natural number $m$, we have
$$ m \times 1 = m. $$
-/
lemma mul_one (m : mynat) : m * 1 = m :=
begin [nat_num_game]
  rw one_eq_succ_zero,
  rw mul_succ,
  rw mul_zero,
  rw zero_add,
  refl

end

end mynat -- hide


/-
The complex numbers.
A documented remix of part of mathlib

Our goal is to define the complex numbers, and then "extract some API".
Our first goal is to define addition and multiplication,
and prove that the complex numbers are a commutative ring.
We then do a slightly more computer-sciency worked development of the
natural inclusion from the reals to the complexes.

There are then a bunch of exercises, which can be solved in term mode
or tactic mode. .
a lot of stuff we don't need for this one precise result. As an appendix
We leave as exercises
the API extraction for the stuff we skipped, namely the following
complex conjugation and the norm function.
-/

-- We will assume that the real numbers are a field.
import data.real.basic

/-- A complex number is defined to be a structure consisting of two real numbers,
    the real part and the imaginary part of the complex number   . -/
structure complex : Type :=
(re : ℝ) (im : ℝ)

-- Let's use the usual notation for the complex numbers
notation `ℂ` := complex

-- You make the complex number with real part 3 and imaginary part 4 like this:
example : ℂ :=
{ re := 3,
  im := 4 }

-- Or like this:
example : ℂ := complex.mk 3 4

-- or like this:
example : ℂ := ⟨3, 4⟩

-- They all give the same complex number.

-- If you have a complex number, then you can get its real and
-- imaginary parts with the `complex.re` and `complex.im` functions.

example : ℝ := complex.re(complex.mk 3 4) -- this term is (3 : ℝ)

example : complex.re(complex.mk 3 4) = 3 := rfl -- true by definition.

-- We clearly don't want to be constantly saying `complex.blah` so let's
-- move into the `complex` namespace

namespace complex

-- All our theorems and definitions will now be called complex.something,
-- and we can in general just drop `complex.`

-- For example

example : re(mk 3 4) = 3 := rfl

-- Computer scientists prefer the style `z.re` to `re(z)` for some reason.

example : (mk 3 4).re = 3 := rfl

example (z : ℂ) : re(z) = z.re := rfl

-- We now prove the basic theorems and make the basic definitions for
-- complex numbers. For example, we will define addition and multiplication on
-- the complex numbers, and prove that it is a commutative ring.

/-! # Mathematical trivialities -/

-- We start with some facts about complex numbers which are so obvious that we do not
-- often explicitly state them. The first is that if z is a complex number, then
-- the complex number with real part re(z) and imaginary part im(z) is equal to z.
-- This is called eta reduction in type theory. Let's work through the
-- simple tactic mode proof.

example : ∀ z : ℂ, complex.mk z.re z.im = z :=
begin
  intro z,
  cases z with x y,
  -- goal now looks complicated, and contains terms which look
  -- like {re := a, im := b}.re which obviously simplify to a.
  -- The `dsimp` tactic will do some tidying up for us, although
  -- it is not logically necessary. `dsimp` does definitional simplification.
  dsimp,
  -- Now we see the goal can be solved by reflexivity
  refl,
end

-- The proof was "unfold everything, and it's true by definition".
-- This proof does not teach a mathematician anything, so we may as well write
-- it in term mode, because each tactic has a term equivalent.
-- The equation compiler does the `intro` and `cases` steps,
-- and `dsimp` was unnecessary -- the two sides of the equation
-- were definitionally equal.

-- It's important we give this theorem a name, because we want `simp`
-- to be able to use it. In short, the `simp` tactic tries to solve
-- goals of the form A = B, when `refl` doesn't work (i.e. the goals are
-- not definitionally equal) but when any mathematician would be able
-- to simplify A and B via "obvious" steps such as `0 + x = x` or
-- `⟨z.re, z.im⟩ = z`. These things are sometimes not true by definition,
-- but they should be tagged as being well-known ways to simplify an equality.
-- When building our API for the complex numbers, if we prove a theorem of the
-- form `A = B` where `B` is a bit simpler than `A`, we should probably
-- tag it with the `@[simp]` attribute, so `simp` can use it.

-- Note: `simp` does *not* prove "all simple things". It proves *equalities*.
-- It proves `A = B` when, and only when, it can do it by applying
-- its "simplification rules", where a simplification rule is simply a proof
-- of a theorem of the form `A = B` and `B` is simpler than `A`.
@[simp] theorem eta : ∀ z : ℂ, complex.mk z.re z.im = z
| ⟨x, y⟩ := rfl

-- The second triviality is the assertion that two complex numbers
-- with the same and imaginary parts are equal. Again this is not
-- hard to prove, and mathematicians deem it not worth documenting.

example (z w : ℂ) : z.re = w.re → z.im = w.im → z = w :=
begin
  cases z with zr zi,
  cases w,
  intros, cc,
end

-- This lemma is called extensionality by type theorists.
-- Here is another tactic mode proof. Note that we have moved
-- the z and w to the other side of the colon; this does not
-- change the fully expanded proof term. It shows the power
-- of the `rintros` tactic.

example : ∀ z w : ℂ, z.re = w.re → z.im = w.im → z = w :=
begin
  rintros ⟨zr, zi⟩ ⟨_, _⟩ ⟨rfl⟩ ⟨rfl⟩,
  refl,
end

-- `rintros` does `cases` as many times as you like using this cool `⟨, ⟩` syntax
-- for the case splits. Note that if you do cases on `h : a = b` then, because
-- `=` is notation for `eq`, an inductive type with one constructor `a = a`,
-- it will just delete `b` and change all `b`s to `a`s. That is one of the
-- things going on in the above proof.

-- Here is the same proof in term mode. Even though it's obvious, we still
-- give it a name, namely `ext`. It's important to prove it, so we can
-- tag it with the `ext` attribute. If we do this, the `ext` tactic can use it.
-- The `ext` tactic applies all theorems of the form "two
-- objects are the same if they are made from the same pieces".

@[ext]
theorem ext : ∀ {z w : ℂ}, z.re = w.re → z.im = w.im → z = w
| ⟨zr, zi⟩ ⟨_, _⟩ rfl rfl := rfl

-- The theorem `complex.ext` is trivially true to a mathematician.
-- But it is often used: for example it will be used all the time
-- in our proof that the complex numbers are a ring.

-- Note that `ext` is an implication -- if re(z)=re(w) and im(z)=im(w) then z=w.
-- The below variant `ext_iff` is the two-way implication: two complex
-- numbers are equal if and only if they have the same real and imaginary part.
-- Let's first see a tactic mode proof. See how the `ext` tactic is used?
-- After it is applied, we have two goals, both of which are hypotheses.
-- The semicolon means "apply the next tactic to all the goals produced by this one"

example (z w : ℂ) : z = w ↔ z.re = w.re ∧ z.im = w.im :=
begin
  split,
  { intro H,
    simp [H]},
  {
    rintro ⟨hre, him⟩,
    ext; assumption,
  }
end

-- Again this is easy to write in term mode, and no mathematician
-- wants to read the proof anyway.

theorem ext_iff {z w : ℂ} : z = w ↔ z.re = w.re ∧ z.im = w.im :=
⟨λ H, by simp [H], and.rec ext⟩

/-! # Main course: the complex numbers are a ring. -/

-- Our goal is to prove that the complexes are a ring. Let's
-- define the structure first; the zero, one, addition and multiplication
-- on the complexes.

/-! ## 0 -/

-- Let's define the zero complex number. Once we have done this we will be
-- able to talk about (0 : ℂ).

/-- notation: `0`, or (0 : ℂ), will mean the complex number with
  real and imaginary part equal to (0 : ℝ). -/
instance : has_zero ℂ := ⟨⟨0, 0⟩⟩

-- Let's prove its basic properties, all of which are true by definition,
-- and then tag them with the appropriate attributes.
@[simp] lemma zero_re : (0 : ℂ).re = 0 := rfl
@[simp] lemma zero_im : (0 : ℂ).im = 0 := rfl

/-! ## 1 -/

-- Now let's do the same thing for 1.

/-- Notation `1` or `(1 : ℂ)`, means `⟨(1 : ℝ), (0 : ℝ)⟩`. -/
instance : has_one ℂ := ⟨⟨1, 0⟩⟩

-- name basic properties and tag them appropriately
@[simp] lemma one_re : (1 : ℂ).re = 1 := rfl
@[simp] lemma one_im : (1 : ℂ).im = 0 := rfl

/-! ## + -/

-- Now let's define addition

/-- Notation `+` for usual addition of complex numbers-/
instance : has_add ℂ := ⟨λ z w, ⟨z.re + w.re, z.im + w.im⟩⟩

-- and state and tag its basic properties. We want to prove
-- theorems like $$a(b+c)=ab+ac$$ by checking on real and
-- imaginary parts, so we need to teach the simplifier
-- these tricks.

@[simp] lemma add_re (z w : ℂ) : (z + w).re = z.re + w.re := rfl
@[simp] lemma add_im (z w : ℂ) : (z + w).im = z.im + w.im := rfl

instance : has_neg ℂ := ⟨λ z, ⟨-z.re, -z.im⟩⟩

@[simp] lemma neg_re (z : ℂ) : (-z).re = -z.re := rfl
@[simp] lemma neg_im (z : ℂ) : (-z).im = -z.im := rfl

instance : has_mul ℂ := ⟨λ z w, ⟨z.re * w.re - z.im * w.im, z.re * w.im + z.im * w.re⟩⟩

@[simp] lemma mul_re (z w : ℂ) : (z * w).re = z.re * w.re - z.im * w.im := rfl
@[simp] lemma mul_im (z w : ℂ) : (z * w).im = z.re * w.im + z.im * w.re := rfl

/-! ## Example of what `simp` can now do -/

example (a b c : ℂ) : re(a*(b+c)) = re(a) * (re(b) + re(c)) - im(a) * (im(b) + im(c)) :=
begin
  simp,
end

/-! # Theorem:  The complex numbers are a commutative ring -/

-- Proof: we've defined all the structure, and every axiom can be checked by reducing it
-- to checking real and imaginary parts with `ext`, expanding everything out with `simp`
-- and then using the fact that the real numbers are a ring.
instance : comm_ring ℂ :=
by refine { zero := 0, add := (+), neg := has_neg.neg, one := 1, mul := (*), ..};
   { intros, apply ext_iff.2; split; simp; ring }

-- That is the end of the proof that the complexes form a ring. We built
-- a basic API which was honed towards the general idea that to prove
-- certain statements about the complex numbers, for example distributivity,
-- we could just check on real and imaginary parts. We trained the `simp`
-- lemma to simplify every

/-! # Coercion

A worked example of how coercions work from the reals to the complexes.

-/

-- Let's define a "canonical" map from ℝ to ℂ. Instead of making it a definition, we will
-- make it a coercion instance, which means that if `(r : ℝ)` is a real
-- number, then `(r : ℂ)` or `(↑r : ℂ)` will indicate the corresponding
-- complex number with no imaginary part

/-- The coercion from ℝ to ℂ sending `r` to the complex number `⟨r, 0⟩` -/
instance : has_coe ℝ ℂ := ⟨λ r, ⟨r, 0⟩⟩

-- The concept of the complex number associated
-- to a real number `r` is a new definition, so we had better formulate its basic
-- properties immediately, namely what its real and imaginary parts are,
-- and their basic behaviour. Here are two properties, both true by definition.
-- We name them because we want to tag them.
@[simp, norm_cast] lemma of_real_re (r : ℝ) : (r : ℂ).re = r := rfl
@[simp, norm_cast] lemma of_real_im (r : ℝ) : (r : ℂ).im = 0 := rfl

-- The `simp` tactic will now simplify `re(↑r)` to `r` and `im(↑r)` to `0`
-- and the `norm_cast` tactic might help you if you have proved a general
-- equality about complex numbers but you want it to be about real numbers,
-- or vice-versa.

-- The map from the reals to the complexes is injective, something we
-- write in iff form so `simp` can use it; `simp` also works on `iff` goals.
@[simp, norm_cast] theorem of_real_inj {z w : ℝ} : (z : ℂ) = w ↔ z = w :=
⟨congr_arg re, congr_arg _⟩

-- We now go through all our basic constants, namely 0, 1, + and *,
-- and tell the simplifier how they behave with respect to this new function.

/-! ## 0 -/
@[simp, norm_cast] lemma of_real_zero : ((0 : ℝ) : ℂ) = 0 := rfl

@[simp] theorem of_real_eq_zero {z : ℝ} : (z : ℂ) = 0 ↔ z = 0 := of_real_inj
theorem of_real_ne_zero {z : ℝ} : (z : ℂ) ≠ 0 ↔ z ≠ 0 := not_congr of_real_eq_zero

/-! ## 1 -/
@[simp, norm_cast] lemma of_real_one : ((1 : ℝ) : ℂ) = 1 := rfl

/-! ## + -/

-- TODO: some crazy bug? in Lean is sometimes stopping me from
-- uncommenting the following example and then putting
-- some code after it. Probably the commit before this one.

-- It is a theorem that the canonical map from ℝ to ℂ commutes with addition.
-- We should prove this and tag it appropriately.

example (r s : ℝ) : ((r + s : ℝ) : ℂ) = r + s :=
begin
  -- goal: to prove two complex numbers are equal.
  ext,
  -- goal: to prove that their real and imaginary
  -- parts are equal.
  { -- real part
    simp},
  { -- imaginary part
    simp},
end

-- Here's the term mode version. It's not true by definition, but `ext` and `simp` solve it.
@[simp, norm_cast] lemma of_real_add (r s : ℝ) : ((r + s : ℝ) : ℂ) = r + s := ext_iff.2 $ by simp

/-! ## - -/
@[simp, norm_cast] lemma of_real_neg (r : ℝ) : ((-r : ℝ) : ℂ) = -r := ext_iff.2 $ by simp

/-! ## * -/

@[simp, norm_cast] lemma of_real_mul (r s : ℝ) : ((r * s : ℝ) : ℂ) = r * s := ext_iff.2 $ by simp

/-! ## Example `simp` usage -/

-- examples of the power of `simp` now. Change to -- `by squeeze_simp` to see which
-- lemmas `simp` uses
lemma smul_re (r : ℝ) (z : ℂ) : (↑r * z).re = r * z.re := by simp -- or by squeeze_simp
lemma smul_im (r : ℝ) (z : ℂ) : (↑r * z).im = r * z.im := by simp -- or by squeeze_simp

/-! ## Numerals

Feel free to skip 15 lines down to `I` if you are a mathematician.

These last two are to do with the canonical map from numerals into the complexes, e.g. `(23 : ℂ)`.
Lean stores the numeral in binary. See for example

set_option pp.numerals false
#check (37 : ℂ)-- bit1 (bit0 (bit1 (bit0 (bit0 has_one.one)))) : ℂ
-/

@[simp, norm_cast] lemma of_real_bit0 (r : ℝ) : ((bit0 r : ℝ) : ℂ) = bit0 r := ext_iff.2 $ by simp [bit0]
@[simp, norm_cast] lemma of_real_bit1 (r : ℝ) : ((bit1 r : ℝ) : ℂ) = bit1 r := ext_iff.2 $ by simp [bit1]

-- I find it unbelievable that we have written 331 lines of code about the complex numbers
-- and we've still never defined i, or j, or I, or $$\sqrt{-1}$$, or whatever it's called.
-- Why don't you try this one?

/-! # I -/

-- TODO: leave definition, sorry all proofs.

/-- complex.I is the square root of -1 above the imaginary axis -/
def I : ℂ := ⟨0, 1⟩

@[simp] lemma I_re : I.re = 0 := rfl
@[simp] lemma I_im : I.im = 1 := rfl

@[simp] lemma I_mul_I : I * I = -1 := ext_iff.2 $ by simp

lemma I_ne_zero : (I : ℂ) ≠ 0 := mt (congr_arg im) zero_ne_one.symm

lemma mk_eq_add_mul_I (a b : ℝ) : complex.mk a b = a + b * I :=
ext_iff.2 $ by simp

@[simp] lemma re_add_im (z : ℂ) : (z.re : ℂ) + z.im * I = z :=
ext_iff.2 $ by simp

/-! # Complex conjugation -/

-- TODO: leave definition, sorry all proofs.

def conj (z : ℂ) : ℂ := ⟨z.re, -z.im⟩

@[simp] lemma conj_re (z : ℂ) : (conj z).re = z.re := rfl
@[simp] lemma conj_im (z : ℂ) : (conj z).im = -z.im := rfl

@[simp] lemma conj_of_real (r : ℝ) : conj r = r := ext_iff.2 $ by simp [conj]

@[simp] lemma conj_zero : conj 0 = 0 := ext_iff.2 $ by simp [conj]
@[simp] lemma conj_one : conj 1 = 1 := ext_iff.2 $ by simp
@[simp] lemma conj_I : conj I = -I := ext_iff.2 $ by simp

@[simp] lemma conj_add (z w : ℂ) : conj (z + w) = conj z + conj w :=
ext_iff.2 $ by simp [add_comm]

@[simp] lemma conj_neg (z : ℂ) : conj (-z) = -conj z := rfl

@[simp] lemma conj_neg_I : conj (-I) = I := ext_iff.2 $ by simp

@[simp] lemma conj_mul (z w : ℂ) : conj (z * w) = conj z * conj w :=
ext_iff.2 $ by simp [add_comm]

@[simp] lemma conj_conj (z : ℂ) : conj (conj z) = z :=
ext_iff.2 $ by simp

lemma conj_involutive : function.involutive conj := conj_conj

lemma conj_bijective : function.bijective conj := conj_involutive.bijective

lemma conj_inj {z w : ℂ} : conj z = conj w ↔ z = w :=
conj_bijective.1.eq_iff

@[simp] lemma conj_eq_zero {z : ℂ} : conj z = 0 ↔ z = 0 :=
by simpa using @conj_inj z 0

lemma eq_conj_iff_real {z : ℂ} : conj z = z ↔ ∃ r : ℝ, z = r :=
⟨λ h, ⟨z.re, ext rfl $ eq_zero_of_neg_eq (congr_arg im h)⟩,
 λ ⟨h, e⟩, e.symm ▸ rfl⟩

lemma eq_conj_iff_re {z : ℂ} : conj z = z ↔ (z.re : ℂ) = z :=
eq_conj_iff_real.trans ⟨by rintro ⟨r, rfl⟩; simp, λ h, ⟨_, h.symm⟩⟩

theorem add_conj (z : ℂ) : z + conj z = (2 * z.re : ℝ) :=
ext_iff.2 $ by simp [two_mul]

/-- the ring homomorphism complex conjugation -/
def Conj : ℂ →+* ℂ :=
{ to_fun := conj,
  map_one' := begin ext; simp, end,
  map_mul' := begin intros, ext; simp end,
  map_zero' := begin ext; simp end,
  map_add' := begin intros, ext; simp end}

/-! # Norms -/

-- TODO: leave definition, sorry all proofs.

def norm_sq (z : ℂ) : ℝ := z.re * z.re + z.im * z.im

@[simp] lemma norm_sq_of_real (r : ℝ) : norm_sq r = r * r :=
by simp [norm_sq]

@[simp] lemma norm_sq_zero : norm_sq 0 = 0 := by simp [norm_sq]
@[simp] lemma norm_sq_one : norm_sq 1 = 1 := by simp [norm_sq]
@[simp] lemma norm_sq_I : norm_sq I = 1 := by simp [norm_sq]

lemma norm_sq_nonneg (z : ℂ) : 0 ≤ norm_sq z :=
add_nonneg (mul_self_nonneg _) (mul_self_nonneg _)

@[simp] lemma norm_sq_eq_zero {z : ℂ} : norm_sq z = 0 ↔ z = 0 :=
⟨λ h, ext
  (eq_zero_of_mul_self_add_mul_self_eq_zero h)
  (eq_zero_of_mul_self_add_mul_self_eq_zero $ (add_comm _ _).trans h),
 λ h, h.symm ▸ norm_sq_zero⟩

@[simp] lemma norm_sq_pos {z : ℂ} : 0 < norm_sq z ↔ z ≠ 0 :=
by rw [lt_iff_le_and_ne, ne, eq_comm]; simp [norm_sq_nonneg]

@[simp] lemma norm_sq_neg (z : ℂ) : norm_sq (-z) = norm_sq z :=
by simp [norm_sq]

@[simp] lemma norm_sq_conj (z : ℂ) : norm_sq (conj z) = norm_sq z :=
by simp [norm_sq]

@[simp] lemma norm_sq_mul (z w : ℂ) : norm_sq (z * w) = norm_sq z * norm_sq w :=
by dsimp [norm_sq]; ring

lemma norm_sq_add (z w : ℂ) : norm_sq (z + w) =
  norm_sq z + norm_sq w + 2 * (z * conj w).re :=
by dsimp [norm_sq]; ring

lemma re_sq_le_norm_sq (z : ℂ) : z.re * z.re ≤ norm_sq z :=
le_add_of_nonneg_right (mul_self_nonneg _)

lemma im_sq_le_norm_sq (z : ℂ) : z.im * z.im ≤ norm_sq z :=
le_add_of_nonneg_left (mul_self_nonneg _)

theorem mul_conj (z : ℂ) : z * conj z = norm_sq z :=
ext_iff.2 $ by simp [norm_sq, mul_comm, sub_eq_neg_add, add_comm]

end complex


import Basics.TypesAndTerms
/-!
## Type Definitions

A distinguishing feature of Lean’s calculus of inductive constructions is its built-in
support for inductive types. An _inductive type_ is a type whose values are built by
applying special constants called _constructors_. Inductive types are a concise way
of representing acyclic data in a program. You may know them under some other,
largely synonymous names, including algebraic data types, inductive data types,
freely generated data types, recursive data types, and data types.

### Natural Numbers

The “Hello, World!” example of inductive types is the type `Nat` natural numbers.
In Lean, it can be defined as follows:
-/
namespace my_nat

inductive Nat where
| zero : Nat
| succ : Nat → Nat

end my_nat
/-!
Note: we are placing this in a new nameapce `my_nat` so it does not conflict with the
built-in type.

The first line of the `inductive` type announces to the world that we are introducing a new type
called `Nat`, intended to represent the natural numbers. The second and third line declare two new
constructors, `Nat.zero` which is of type `Nat` and `Nat.succ` which has the function type `Nat →
Nat`, that can be used to build values of type `Nat`. Following an established convention in
computer science and logic, counting starts at zero. The second constructor is what makes this
inductive definition interesting—it requires an argument of type `Nat` to produce a value of type
`Nat`. The terms
```lean
Nat.zero
Nat.succ Nat.zero
Nat.succ (Nat.succ Nat.zero)
...
```
denote the different values of type nat—zero, its successor, its successor’s successor, and so on.
This notation is called _unary_, or _Peano_, after the logician Giuseppe Peano. For an alternative
explanation of Peano numbers in Lean (and some groovy video game graphics), see Kevin Buzzard’s
article [Can computers prove theorems?](http://chalkdustmagazine.com/features/can-computers-prove-theorems/).

The general format of type declarations is
```lean
inductive type-name (params₁ : type₁) . . . (paramsₖ : typeₖ) : Type
| constructor-name₁ : constructor-type₁
.
.
.
| constructor-nameₙ : constructor-typeₙ
```

You can also use literals `0, 1, 2, . . .` and Lean will be able to infer that they are of type `Nat`
when they are used in that context.

We can inspect an earlier definition at any point in Lean by using the `#print` command. For example,
`#print Nat` within the `my_nat` namespace displays the following information:
-/
#print my_nat.Nat
/-!
```
inductive my_nat.Nat : Type
number of parameters: 0
constructors:
my_nat.Nat.zero : my_nat.Nat
my_nat.Nat.succ : my_nat.Nat → my_nat.Nat
```

The focus on natural numbers is one of the many features of this guide that reveal a bias towards
computer science. Number theorists would be more interested in the integers `ℤ` and the rational
numbers `ℚ`; analysts would want to work with the real numbers `ℝ` and the complex numbers `ℂ`. But the
natural numbers are ubiquitous in computer science and enjoy a very simple definition as an
inductive type. They can also be used to build other types, as we will see in Chapter 13.

### Arithmetic Expressions

If we were to specify a calculator program or a programming language, we would
likely need to define a type to represent arithmetic expressions. The next example
shows how this could be done in Lean:
-/
inductive aexp  where
| num : Int → aexp
| var : String → aexp
| add : aexp → aexp → aexp
| sub : aexp → aexp → aexp
| mul : aexp → aexp → aexp
| div : aexp → aexp → aexp

#check aexp.num 1     -- aexp.num 1 : aexp

/-!
Mathematically, this definition is equivalent to defining the type `aexp` inductively
by the following formation rules:

1. For every integer `i`, we have that `aexp.num i` is an `aexp` value.
2. For every character string `x`, we have that `aexp.var x` is an `aexp` value.
3. If `e1` and `e2` are `aexp` values, then so are `aexp.add e1 e2`, `aexp.sub e1 e2`,
`aexp.mul e1 e2`, and `aexp.div e1 e2`.

The above definition is exhaustive. The only possible values for `aexp` are those built using
formation rules 1 to 3. Moreover, `aexp` values built using different formation rules are distinct.
These two properties of inductive types are captured by the motto “No junk, no confusion,” due to
Joseph Goguen.

### Comparison with Java

It may be instructive to compare the concise Lean specification of `aexp` above with a Java program
that achieves the same. The program consists of one interface and six classes that implement it,
corresponding to the `aexp` type and its six constructors:

```java
public interface AExp { }
public class Num implements AExp {
  public int num;
  public Num(int num) { this.num = num; }
}
public class Var implements AExp {
  public String var;
  public Var(String var) { this.var = var; }
}
public class Add implements AExp {
  public AExp left;
  public AExp right;
  public Add(AExp left, AExp right)
  { this.left = left; this.right = right; }
}
public class Sub implements AExp {
  public AExp left;
  public AExp right;
  public Sub(AExp left, AExp right)
  { this.left = left; this.right = right; }
}
public class Mul implements AExp {
  public AExp left;
  public AExp right;
  public Mul(AExp left, AExp right)
  { this.left = left; this.right = right; }
}
public class Div implements AExp {
  public AExp left;
  public AExp right;
  public Div(AExp left, AExp right)
  { this.left = left; this.right = right; }
}
```

### Comparison with C

In C, the natural counterpart of an inductive type is a tagged union. The type
declarations would be as follows:

```C
#include <stddef.h>
#include <stdlib.h>
enum AExpKind {
  AET_NUM, AET_VAR, AET_ADD, AET_SUB, AET_MUL, AET_DIV
};
struct aexp;
struct aexp_num {
  int num;
};
struct aexp_var {
  char var[1024];
};
struct aexp_binop {
  struct aexp *left;
  struct aexp *right;
};
struct aexp {
  enum AExpKind kind;
  union {
    struct aexp_num anum;
    struct aexp_var avar;
    struct aexp_binop abinop;
  } data;
};
```
Corresponding to each constructor in Lean, we would need to write a function
to allocate an `aexp` object of the right size in memory. Here is the definition of the
function corresponding to the first constructor, `aexp.num`:

```C
struct aexp *create_num(int num)
{
  struct aexp *res = malloc(offsetof(struct aexp, data) +
  sizeof(struct aexp_num));
  res->kind = AET_NUM;
  res->data.anum.num = num;
  return res;
}
```

The subtle pointer arithmetic for the `malloc` call is needed to allocate exactly the
right amount of memory.

### Lists

The next type we consider is that of finite lists (shown here in a temporary namespace
so it doesn't conflict with the same built-in type):
-/
namespace my_list

inductive List (α : Type) where
  | nil : List α
  | cons (head : α) (tail : List α) : List α

end my_list
/-!
The type is _polymorphic_: It is parameterized by a type α, which we can instantiate with concrete
types. For example, `List ℤ`  is the type of lists over integers, and `List (List ℝ)` is the type of
lists of lists of real numbers. The type constructor `List` takes a type as argument and returns a
type. Polymorphism is related to generics (in Java) and templates (in C++). The general idea in all
cases is to have parameterized types.

The following commands display the constructors’ types:
-/
#check List.nil       -- [] : List ?m.6639
#check List.cons      -- List.cons : ?m.6641 → List ?m.6641 → List ?m.6641

/-!
Informally:
- The `nil` constructor takes a type α as argument and produces a result of type
`List α`. The type was not defined here so you see `?m.6639` which represents
an unresolved metavariable in the Lean compilation.
- The `cons` constructor takes an element (the _head_) of some arbitrary type
`?m.6641` as argument and a list over `?m.6641` (the tail) and produces a result of
type list `?m.6641`. Unlike for nil, there is no need to pass a type argument to
cons—the type is inferred from the first argument. If we want to pass the type
argument explicitly, we need to write an at sign (@) in front of the constant:
`@List.cons`.
-/
#check @List.cons Nat   -- List.cons : ℕ → List ℕ → List ℕ
/-!
Even if we try to restrict ourselves to a fragment of Lean’s language, Lean often
exposes us to more advanced constructs in the output, such as `?m.6641` above, `Sort u`,
or `Type 1`. Our advice is to adopt a sporty attitude: Do not worry if you do not
always understand everything the first time. Use your common sense and your
imagination. And, above all, do not hesitate to ask.

Lean’s built-in lists offer syntactic sugar for writing lists:

- `[]` for `List.nil`
- `x :: xs` for `List.cons x xs`
- `[x₁, . . ., xₙ]` for `x₁ :: . . . :: xₙ :: []`

The `::` operator, like all other binary operators, binds less tightly than function
application. Thus, `f x :: reverse ys` is parsed as `(f x) :: (reverse ys)`. It is good
practice to avoid needless parentheses. They can quickly impair readability. In
addition, it is important to put spaces around infix operators, to suggest the right
precedence; it is all too easy to misread `f x::reverse ys` as `f (x::reverse) ys` otherwise.

Functional programmers often use plural names such as `xs`, `ys`, `zs` for lists
(or more generally collections). A list contains many elements, so a plural form is natural.
A list of `cat` objects might be called `cats`; a list of list of `cat` objects, `catss`.
When a nonempty list is presented as a head and a tail, we usually write, say, `x :: xs` or `cat :: cats`.
-/