-- p q r s are used in all the examples so they are defined only here
variables p q r s : Prop

-- Conjunction
-- Prove p, q -> p ∧ q
example (hp : p) (hq : q): p ∧ q := and.intro hp hq
-- Prove p ∧ q -> q
example (h : p ∧ q) : p := and.elim_left h
-- Prove p ∧ q -> p
example (h : p ∧ q) : q := and.elim_right h

-- Some examples of type assignment
variables (h : p) (i : q)
#check and.elim_left ⟨h, i⟩
#reduce and.elim_left ⟨h, i⟩
#check and.elim_right ⟨h, i⟩
#reduce and.elim_right ⟨h, i⟩

#check (⟨h, i⟩ : p ∧ q)
#reduce (⟨h, i⟩ : p ∧ q)

-- Prove q ∧ p → p ∧ q
example (h : p ∧ q) : q ∧ p :=
and.intro (and.elim_right h) (and.elim_left h)
-- Lean allows us to use anonymous constructor notation ⟨arg1, arg2, ...⟩
-- In situations like these, when the relevant type is an inductive type and can be inferred from the context.
-- In particular, we can often write ⟨hp, hq⟩ instead of and.intro hp hq:
example (h : p ∧ q) : q ∧ p :=
⟨and.elim_right h, and.elim_left h⟩

-- More examples of using the constructor notation
example (h : p ∧ q) : q ∧ p ∧ q:=
⟨h.right, ⟨h.left, h.right⟩⟩

example (h : p ∧ q) : q ∧ p ∧ q:=
⟨h.right, h.left, h.right⟩

-- Disjunction
-- ∨ introduction left
example (hp : p): p ∨ q := or.intro_left q hp
-- ∨ introduction right
example (hq : q): p ∨ q := or.intro_right p hq

-- Prove p ∨ p → p
example (h: p ∨ p): p :=
or.elim h
(assume hpr : p, show p, from hpr)
(assume hpl : p, show p, from hpl)

-- Prove p ∨ p → p, simplified notation
example (h: p ∨ p): p :=
or.elim h
(assume hpr : p, hpr)
(assume hpl : p, hpl)

-- Prove  p ∨ q, p → r, q -> r, -> r
example (h : p ∨ q) (i : p → r) (j : q -> r) : r :=
or.elim h
(assume hp : p, show r, from i hp)
(assume hq : q, show r, from j hq)

-- Prove p ∨ q → q ∨ p
example (h : p ∨ q) : q ∨ p :=
or.elim h
(assume hp : p, show q ∨ p, from or.intro_right q hp)
(assume hq : q, show q ∨ p, from or.intro_left p hq)

-- Modus tollens, deriving a contradiction from p to obtain false.
-- Everything follows from false, in this case ¬p
-- order of propositions matters when using absurd
example (hpq : p → q) (hnq : ¬q) : ¬p :=
assume hp : p, show false, from  hnq (hpq hp)

-- Equivalent to the previous one
example (hpq : p → q) (hnq : ¬q) : ¬p :=
assume hp : p, show false, from  false.elim (hnq (hpq hp))
-- Equivalent to the previous one
example (hpq : p → q) (hnq : ¬q) : ¬p :=
assume hp : p, show false, from  (hnq (hpq hp))

-- More examples of false and absurd use
example (hp : p) (hnp : ¬p) : q := false.elim (hnp hp)
-- order of propositions matters when using absurd
example (hp : p) (hnp : ¬p) : q := absurd hp hnp

-- Prove ¬p → q → (q → p) → r, premise ¬p must be added
example (hnp : ¬p) (hq : q) (hqp : q → p): r :=
false.elim (hnp (hqp hq))
-- equivalent proove as the above
example (hnp : ¬p) (hq : q) (hqp : q → p): r :=
absurd (hqp hq) hnp

-- Other variant from the above
-- Prove ¬p → q → (q → p) → r, premise ¬p must be added
example (hnp : ¬ p) (hnpq : ¬p → q) (hqp : q → p): r :=
absurd (hqp (hnpq hnp)) hnp

example (hnp : ¬ p) (hnpq : ¬p → q) (hqp : q → p): r :=
false.elim (hnp (hqp (hnpq hnp)))

-- Prove p ∧ q ↔ q ∧ p
example : (p ∧ q) ↔ (q ∧ p) :=
iff.intro
  (assume h : p ∧ q,
    show q ∧ p,
    from and.intro (and.elim_right h) (and.elim_left h))
  (assume h : q ∧ p,
    show p ∧ q,
    from and.intro (and.elim_right h) (and.elim_left h))

 -- Derive q ∧ p from p ∧ q
 example (h : p ∧ q) : (q ∧ p) :=
 and.intro (and.elim_right h) (and.elim_left h)

 -- Define a theorem and use the anomimous constructor to prove p ∧ q ↔ q ∧ p
 theorem and_swap : p ∧ q ↔ q ∧ p :=
⟨ λ h, ⟨h.right, h.left⟩, λ h, ⟨h.right, h.left⟩ ⟩
-- Prove, using and_swap theorem that p ∧ q → q ∧ p
example (h : p ∧ q) : q ∧ p := (and_swap p q).mp h

-- Introducing auxiliary subgoals with `have`
example (h : p ∧ q) : q ∧ p :=
have hp : p, from and.left h,
have hq : q, from and.right h,
show q ∧ p, from and.intro hq hp

-- Introducing auxiliary subgoals with `suffices`
example (h : p ∧ q) : q ∧ p :=
have hp : p, from and.left h,
suffices hq : q, from and.intro hq hp,
show q, from and.right h

-- Classical logic
-- Prove double negation
-- To use `em` we can invoke it using classical namespace like so: `classical.em`
-- or import the classical module with 'open classical' and just use 'em' directly,
-- no need to prefix it with namespace name
theorem dne {p : Prop} (h : ¬¬p) : p :=
  or.elim (classical.em p)
    (assume hp : p, show p, from hp)
    (assume hnp : ¬p, show p, from absurd hnp h)

-- Prove double negation, p is defined at the top of the file as p : Prop,
-- so no need to add {p : Prop} to the premises
example (h : ¬¬p) : p :=
or.elim (classical.em p)
  (assume hp : p, show p, from hp)
  (assume hnp : ¬p, show p, from absurd hnp h)

-- Proof by cases
example (h : ¬¬p) : p :=
classical.by_cases
(assume h1 : p, h1)
(assume h1 : ¬p, absurd h1 h)

-- Proof by contradiction
example (h : ¬¬p) : p :=
classical.by_contradiction
(assume h1 : ¬p, absurd h1 h)

example (h : ¬¬p) : p :=
classical.by_contradiction
(assume h1 : ¬p, show false, from h h1)

-- Prove ¬(p ∧ q) → ¬p ∨ ¬q
example (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
or.elim (classical.em p)
(assume hp : p,
  or.inr (show ¬q, from
          assume hq : q,
          h ⟨hp, hq⟩))
(assume hnp : ¬p, show ¬p ∨ ¬q, from or.inl hnp)

-- Prove p ∧ q → (p → q) :=
example : p ∧ q → (p → q) :=
assume hpq : p ∧ q,
assume hp : p, show q, from and.elim_right hpq

-- Prove p → q → (p ∧ q) :=
example : p → q → (p ∧ q) :=
assume hp : p,
assume hq : q, show  p ∧ q, from and.intro hp hq

-- 3.6. Examples of Propositional Validities

-- commutativity of ∧ and ∨

-- Prove p ∨ q ↔ q ∨ p
example : p ∨ q ↔ q ∨ p :=
iff.intro
(assume h : p ∨ q, show q ∨ p,
  from or.elim h
    (assume hp : p, show q ∨ p, from or.intro_right q hp)
    (assume hq : q, show q ∨ p, from or.intro_left p hq))
(assume h : q ∨ p, show p ∨ q,
  from or.elim h
    (assume hq : q, show p ∨ q, from or.intro_right p hq)
    (assume hp : p, show p ∨ q, from or.intro_left q hp))

-- Associativity of ∧ and ∨ --
-- Prove (p ∧ q) ∧ r ↔ p ∧ (q ∧ r)
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
iff.intro
  (assume hpqr : (p ∧ q) ∧ r, show p ∧ (q ∧ r), from
    ⟨hpqr.left.left, ⟨hpqr.left.right, hpqr.right⟩⟩)
  (assume hpqr : p ∧ (q ∧ r), show (p ∧ q) ∧ r, from
    ⟨⟨hpqr.left, hpqr.right.left⟩, hpqr.right.right⟩)

-- Prove (p ∨ q) ∨ r ↔ p ∨ (q ∨ r)
example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
iff.intro
  (assume hpqr : (p ∨ q) ∨ r, show p ∨ (q ∨ r), from
    or.elim hpqr
      (assume hpq : p ∨ q, show p ∨ (q ∨ r), from
        or.elim hpq
        (assume hp : p, show p ∨ (q ∨ r), from or.inl hp)
        (assume hq : q, show p ∨ (q ∨ r), from or.inr (or.inl hq)))
      (assume hr : r, show p ∨ (q ∨ r), from or.inr (or.inr hr)))
  (assume hpqr : p ∨ (q ∨ r), show (p ∨ q) ∨ r, from
    or.elim hpqr
    (assume hp : p, show (p ∨ q) ∨ r, from or.inl (or.inl hp))
    (assume hqr : q ∨ r, show (p ∨ q) ∨ r, from or.elim hqr
      (assume hq: q, show (p ∨ q) ∨ r, from or.inl (or.inr hq ))
      (assume hr : r, show (p ∨ q) ∨ r, from or.inr hr)))

-- Distributivity --
-- Prove p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r)
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
iff.intro
  (assume h : p ∧ (q ∨ r),
    have hp : p, from h.left,
    or.elim (h.right)
      (assume hq : q,
        show (p ∧ q) ∨ (p ∧ r), from or.inl ⟨hp, hq⟩)
      (assume hr : r,
        show (p ∧ q) ∨ (p ∧ r), from or.inr ⟨hp, hr⟩))
  (assume h : (p ∧ q) ∨ (p ∧ r),
   or.elim h
   (assume hpq : (p ∧ q),
     show p ∧ (q ∨ r), from and.intro hpq.left (or.inl hpq.right))
   (assume hpr : (p ∧ r),
      show p ∧ (q ∨ r), from and.intro hpr.left (or.inr hpr.right)))

-- Prove p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := sorry
example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
iff.intro
(assume h: p ∨ (q ∧ r),
  show (p ∨ q) ∧ (p ∨ r),
  from or.elim h
  (assume hp: p, show (p ∨ q) ∧ (p ∨ r), from ⟨or.inl hp, or.inl hp⟩)
  (assume hqr: (q ∧ r), show (p ∨ q) ∧ (p ∨ r), from ⟨or.inr hqr.left, or.inr hqr.right⟩))
(assume h: (p ∨ q) ∧ (p ∨ r),
  show p ∨ (q ∧ r),
  from  or.elim (h.left)
  (assume hp : p, show p ∨ (q ∧ r), from or.inl hp)
  (assume hq : q, show p ∨ (q ∧ r), from or.elim h.right
    (assume hp : p, show p ∨ (q ∧ r), from or.inl hp)
    (assume hr : r, show p ∨ (q ∧ r), from or.inr ⟨hq, hr⟩)
   ))

-- Other properties
-- Prove (p → (q → r)) ↔ (p ∧ q → r)
example : (p → (q → r)) ↔ (p ∧ q → r) :=
iff.intro
(assume hpqr : p → (q → r), show p ∧ q → r, from
  (assume hpq : p ∧ q,
    have hp : p, from hpq.left,
    have hq : q, from hpq.right,
    show r, from (hpqr hp) hq))
(assume hpqr : (p ∧ q → r), show (p → (q → r)), from
  (assume hp : p, show q → r, from
    (assume hq, show r, from (hpqr ⟨hp, hq⟩))))

-- Prove ((p ∨ q) → r) ↔ (p → r) ∧ (q → r)
example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
iff.intro
(assume hpqr : (p ∨ q) → r, show (p → r) ∧ (q → r), from
  ⟨(assume hp : p, show r, from hpqr(or.inl hp)),
   (assume hq : q, show r, from hpqr(or.inr hq))⟩)
(assume hprqr : (p → r) ∧ (q → r), show (p ∨ q) → r, from
  (assume hpq : p ∨ q, show r, from
    or.elim hpq
    (assume hp : p, show r, from hprqr.left hp)
    (assume hq : q, show r, from hprqr.right hq)
  )
)

-- Prove ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
example (p q : Prop) : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
⟨λ h, ⟨λ hp, h (or.inl hp), λ hq, h (or.inr hq)⟩,
  λ hn h, or.elim h hn.1 hn.2⟩

-- Another proof for ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
example (p q : Prop) : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
iff.intro
(assume hnpq : ¬(p ∨ q), show ¬p ∧ ¬q, from
  and.intro
  (assume hp : p, show false, from hnpq (or.inl hp))
  (assume hq : q, show false, from hnpq (or.inr hq)))
(assume (hnpnq : ¬p ∧ ¬q),
 show ¬(p ∨ q), from
  assume hnpq : p ∨ q, show false, from
   or.elim hnpq (and.left hnpnq) (and.right hnpnq))

-- Prove ¬p ∨ ¬q → ¬(p ∧ q) := sorry
example : ¬p ∨ ¬q → ¬(p ∧ q) :=
(assume hnpnq : ¬p ∨ ¬q,
 assume hpq : p ∧ q,
  show false, from
  (or.elim hnpnq
    (assume hnp : ¬p, show false, from hnp (and.left hpq))
    (assume hnq : ¬q, show false, from hnq (and.right hpq))
  )
)

-- Prove ¬(p ∧ ¬p) := sorry
example : ¬(p ∧ ¬p) :=
assume h : p ∧ ¬p,
show false, from h.right h.left

example : ¬(p ∧ ¬p) :=
assume h,
absurd h.left h.right

-- Prove p ∧ ¬q → ¬(p → q)
example : p ∧ ¬q → ¬(p → q) :=
assume hpnq : p ∧ ¬ q,
assume hpq : p → q,
absurd (hpq hpnq.left) hpnq.right

example : p ∧ ¬q → ¬(p → q) :=
assume hpnq : p ∧ ¬ q,
assume hpq : p → q,
show false, from hpnq.right (hpq hpnq.left)

-- Prove ¬p → (p → q) := sorry
example : ¬p → (p → q) :=
assume hnp,
assume hp,
absurd hp hnp

example : ¬p → (p → q) :=
assume hnp,
assume hp,
show q , from absurd hp hnp

example : ¬p → (p → q) :=
assume hp,
assume hnp,
show q , from false.elim(hp hnp)

-- Prove (¬p ∨ q) → (p → q)
example : (¬p ∨ q) → (p → q) :=
(assume hnpq : ¬p ∨ q,
or.elim hnpq
(assume hnp : ¬p,
 assume hp : p,
 show q, from absurd hp hnp)
(assume hq : q,
 assume hp : p,
 show q, from hq))

example : (¬p ∨ q) → (p → q) :=
(assume hnpq : ¬p ∨ q,
or.elim hnpq
(assume hnp : ¬p,
 assume hp : p,
 show q, from false.elim (hnp hp))
(assume hq : q,
 assume hp : p,
 show q, from hq))

-- Prove p ∨ false ↔ p
example : p ∨ false ↔ p :=
iff.intro
(assume hpf,
or.elim hpf
  (assume hp, show p, from hp)
  (assume false, show p, from false.elim))
(assume hp,
 show p ∨ false, from or.inl hp)

 -- Prove p ∧ false ↔ false
example : p ∧ false ↔ false :=
iff.intro
(assume hpf,
show false, from hpf.right)
(assume hf,
show p ∧ false, from and.intro (show p, from hf.elim) hf)

-- Prove ¬(p ↔ ¬p)
example : ¬(p ↔ ¬p) :=
assume hpnp,
have hnp : ¬ p, from
  assume hp : p, show false, from (hpnp.elim_left hp) hp,
show false, from hnp (hpnp.elim_right hnp)

-- Prove (p → q) → (¬q → ¬p)
example : (p → q) → (¬q → ¬p) :=
assume hpq,
assume hnq,
assume hp, show false, from hnq (hpq hp)

example : (p → q) → (¬q → ¬p) :=
assume hpq,
assume hnq,
assume hp, absurd (hpq hp) hnq

-- these require classical reasoning
open classical
-- Prove (p → r ∨ s) → ((p → r) ∨ (p → s))
example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
(assume hprs, show (p → r) ∨ (p → s), from sorry)

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
(assume hprs, assume hp, show (p → r) ∨ (p → s), from sorry)

example (h : ¬¬p) : p :=
by_cases
  (assume h1 : p, h1)
  (assume h1 : ¬p, absurd h1 h)

example (h : ¬¬p) : p :=
by_contradiction
  (assume h1 : ¬p,
    show false, from h h1)

example (h : ¬(p ∧ q)) : ¬p ∨ ¬q :=
or.elim (em p)
  (assume hp : p,
    or.inr
      (show ¬q, from
        assume hq : q,
        h ⟨hp, hq⟩))
  (assume hp : ¬p,
    or.inl hp)

-- Prove ¬(p ∧ q) → ¬p ∨ ¬q
example : ¬(p ∧ q) → ¬p ∨ ¬q :=
assume hnpq,
or.elim (em p)
(assume hp, show ¬p ∨ ¬q, from or.inr (assume hq, hnpq (and.intro hp hq)))
(assume hnp, show ¬p ∨ ¬q, from or.inl hnp)

-- Prove: ¬(p ∧ ¬q) → (p → q)
example : ¬(p ∧ ¬q) → (p → q) :=
assume hnpnq :  ¬(p ∧ ¬q), show p → q, from (assume hp: p, show q, from
  by_contradiction (assume hnq: ¬q, show false,
    from hnpnq (and.intro hp hnq)))

-- Prove ¬(p → q) → p ∧ ¬q
example : ¬(p → q) → p ∧ ¬q :=
assume hnpq : ¬(p → q),
show p ∧ ¬q, from by_contradiction (
  assume hnpnq : ¬(p ∧ ¬q), show false, from
    hnpq
    (assume hp: p, show q, from
      by_contradiction (assume hnq: ¬q, show false,
        from hnpnq (and.intro hp hnq))
    )
)

-- Prove (p → q) → (¬p ∨ q)
example : (p → q) → (¬p ∨ q) :=
assume hpq : p → q,
show ¬p ∨ q, from
or.elim (em p)
  (assume hp, show ¬p ∨ q, from or.inr(hpq hp))
  (assume hnp, show ¬p ∨ q, from or.inl hnp)

-- Prove (¬q → ¬p) → (p → q)
example : (¬q → ¬p) → (p → q) :=
assume hnpnq : ¬q → ¬p,
show p → q, from (
  assume hp: p, show q, from by_contradiction (
    assume hnq: ¬q, show false, from (hnpnq hnq) hp
  )
)

-- Prove p ∨ ¬p
example : p ∨ ¬p :=
by_contradiction(assume hnpnp: ¬(p ∨ ¬p), show false, from
  or.elim (em p)
  (assume hp : p, show false, from
    false.elim(hnpnp (or.inl hp)))
  (assume hnp : ¬p, show false, from
    false.elim(hnpnp (or.inr hnp)))
)

-- Prove (((p → q) → p) → p)
example : (((p → q) → p) → p) :=
assume hpqp : (p → q) → p, show p, from
  by_contradiction(
      assume hnp : ¬p, show false, from
      false.elim(
        hnp
        (hpqp (assume hp : p,
          show q, from false.elim(hnp hp)))
      )
  )


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import analysis.complex.basic
import data.complex.exponential
import data.polynomial.algebra_map
import ring_theory.polynomial.chebyshev

/-!
# Multiple angle formulas in terms of Chebyshev polynomials

This file gives the trigonometric characterizations of Chebyshev polynomials, for both the real
(`real.cos`) and complex (`complex.cos`) cosine.
-/

namespace polynomial.chebyshev
open polynomial

variables {R A : Type*} [comm_ring R] [comm_ring A] [algebra R A]

@[simp] lemma aeval_T (x : A) (n : ℕ) : aeval x (T R n) = (T A n).eval x :=
by rw [aeval_def, eval₂_eq_eval_map, map_T]

@[simp] lemma aeval_U (x : A) (n : ℕ) : aeval x (U R n) = (U A n).eval x :=
by rw [aeval_def, eval₂_eq_eval_map, map_U]

@[simp] lemma algebra_map_eval_T (x : R) (n : ℕ) :
  algebra_map R A ((T R n).eval x) = (T A n).eval (algebra_map R A x) :=
by rw [←aeval_algebra_map_apply_eq_algebra_map_eval, aeval_T]

@[simp] lemma algebra_map_eval_U (x : R) (n : ℕ) :
  algebra_map R A ((U R n).eval x) = (U A n).eval (algebra_map R A x) :=
by rw [←aeval_algebra_map_apply_eq_algebra_map_eval, aeval_U]

@[simp, norm_cast]

@[simp, norm_cast] lemma complex_of_real_eval_U : ∀ x n, ((U ℝ n).eval x : ℂ) = (U ℂ n).eval x :=
@algebra_map_eval_U ℝ ℂ _ _ _

/-! ### Complex versions -/

section complex
open complex

variable (θ : ℂ)

/-- The `n`-th Chebyshev polynomial of the first kind evaluates on `cos θ` to the
value `cos (n * θ)`. -/
@[simp] lemma T_complex_cos : ∀ n, (T ℂ n).eval (cos θ) = cos (n * θ)
| 0       := by simp only [T_zero, eval_one, nat.cast_zero, zero_mul, cos_zero]
| 1       := by simp only [eval_X, one_mul, T_one, nat.cast_one]
| (n + 2) :=
begin
  simp only [eval_X, eval_one, T_add_two, eval_sub, eval_bit0, nat.cast_succ, eval_mul],
  rw [T_complex_cos (n + 1), T_complex_cos n],
  have aux : sin θ * sin θ = 1 - cos θ * cos θ,
  { rw ← sin_sq_add_cos_sq θ, ring, },
  simp only [nat.cast_add, nat.cast_one, add_mul, cos_add, one_mul, sin_add, mul_assoc, aux],
  ring,
end

/-- The `n`-th Chebyshev polynomial of the second kind evaluates on `cos θ` to the
value `sin ((n + 1) * θ) / sin θ`. -/
@[simp] lemma U_complex_cos (n : ℕ) : (U ℂ n).eval (cos θ) * sin θ = sin ((n + 1) * θ) :=
begin
  induction n with d hd,
  { simp only [U_zero, nat.cast_zero, eval_one, mul_one, zero_add, one_mul] },
  { rw U_eq_X_mul_U_add_T,
    simp only [eval_add, eval_mul, eval_X, T_complex_cos, add_mul, mul_assoc, hd, one_mul],
    conv_rhs { rw [sin_add, mul_comm] },
    push_cast,
    simp only [add_mul, one_mul] }
end

end complex

/- ### Real versions -/

section real
open real

variables (θ : ℝ) (n : ℕ)

/-- The `n`-th Chebyshev polynomial of the first kind evaluates on `cos θ` to the
value `cos (n * θ)`. -/
@[simp] lemma T_real_cos : (T ℝ n).eval (cos θ) = cos (n * θ) :=
by exact_mod_cast T_complex_cos θ n

/-- The `n`-th Chebyshev polynomial of the second kind evaluates on `cos θ` to the
value `sin ((n + 1) * θ) / sin θ`. -/
@[simp] lemma U_real_cos : (U ℝ n).eval (cos θ) * sin θ = sin ((n + 1) * θ) :=
by exact_mod_cast U_complex_cos θ n

end real

end polynomial.chebyshev


-- Imagen_inversa_de_la_interseccion.lean
-- Imagen inversa de la intersección.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 30-abril-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- En Lean, la imagen inversa de un conjunto s (de elementos de tipo β)
-- por la función f (de tipo α → β) es el conjunto `f ⁻¹' s` de
-- elementos x (de tipo α) tales que `f x ∈ s`.
--
-- Demostrar que
--    f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v
-- ----------------------------------------------------------------------

import data.set.basic

open set

variables {α : Type*} {β : Type*}
variable  f : α → β
variables u v : set β

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

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
begin
  ext x,
  split,
  { intro h,
    split,
    { apply mem_preimage.mpr,
      rw mem_preimage at h,
      exact mem_of_mem_inter_left h, },
    { apply mem_preimage.mpr,
      rw mem_preimage at h,
      exact mem_of_mem_inter_right h, }},
  { intro h,
    apply mem_preimage.mpr,
    split,
    { apply mem_preimage.mp,
      exact mem_of_mem_inter_left h,},
    { apply mem_preimage.mp,
      exact mem_of_mem_inter_right h, }},
end

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

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
begin
  ext x,
  split,
  { intro h,
    split,
    { simp at *,
      exact h.1, },
    { simp at *,
      exact h.2, }},
  { intro h,
    simp at *,
    exact h, },
end

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

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
-- by hint
by finish

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

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
-- by library_search
preimage_inter

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

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
rfl


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

import tactic -- imports all the Lean tactics
import group_theory.subgroup.basic -- import Lean's subgroups

/-

# Group homomorphisms

mathlib has group homomorphisms. The type of group homomorphisms from `G` to `H` is called
`monoid_hom G H`, but we hardly ever use that name; instead we use the notation, which
is `G →* H`, i.e. "`*`-preserving map between groups". Note in particular that we do *not*
write `f : G → H` for a group homomorphism and then have some
function `is_group_hom : (G → H) → Prop` saying that it's a group homomorphism, we just have a
completely new type, whose terms are pairs consisting of the function and the axiom
that `f(g₁g₂)=f(g₁)f(g₂)` for all g₁ and g₂.
-/

-- Let `G` and `H` be groups.
variables {G H : Type} [group G] [group H]

-- let `φ : G → H` be a group homomorphism
variable (φ : G →* H)

-- Even though `φ` is not technically a function (it's a pair consisting of a function and
-- a proof), we can still evaluate `φ` at a term of type `G` and get a term of type `H`.

-- let `a` be an element of G
variable (a : G)

-- let's make the element `φ(a)` of `H`
example : H := φ a

-- If you use this in a proof, you'll see that actually this is denoted `⇑φ g`; what this
-- means is that `φ` is not itself a function, but there is a coercion from `G →* H`
-- to `G → H` sending `φ` to the underlying function from `G` to `H` (so, it forgets the
-- fact that φ is a group homomorphism and just remembers the function bit.

-- Here's the basic API for group homomorphisms

example (a b : G) : φ (a * b) = φ a * φ b := φ.map_mul a b
example : φ 1 = 1 := φ.map_one
example (a : G) : φ (a⁻¹) = (φ a)⁻¹ := φ.map_inv a

-- The identity group homomorphism from `G` to `G` is called `monoid_hom.id G`

example : monoid_hom.id G a = a :=
begin
  refl, -- true by definition
end

-- Let K be a third group.
variables (K : Type) [group K]

-- Let `ψ : H →* K` be another group homomorphism
variable (ψ : H →* K)

-- The composite of ψ and φ can't be written `ψ ∘ φ` in Lean, because `∘` is notation
-- for function composition, and `φ` and `ψ` aren't functions, they're collections of
-- data containing a function and some other things. So we use `monoid_hom.comp` to
-- compose functions. We can use dot notation for this.

example : G →* K := ψ.comp φ

-- When are two group homomorphisms equal? When they agree on all inputs. The `ext` tactic
-- knows this.

-- The next three lemmas are pretty standard, but they are also in fact
-- the axioms that show that groups form a category.

lemma comp_id : φ.comp (monoid_hom.id G) = φ :=
begin
  ext x,
  refl,
end

lemma id_comp : (monoid_hom.id H).comp φ = φ :=
begin
  ext x,
  refl,
end

lemma comp_assoc {L : Type} [group L] (ρ : K →* L) :
  (ρ.comp ψ).comp φ = ρ.comp (ψ.comp φ) :=
begin
  refl,
end

-- The kernel of a group homomorphism `φ` is a subgroup of the source group.
-- The elements of the kernel are *defined* to be `{x | φ x = 1}`.
-- Note the use of dot notation to save us having to write `monoid_hom.ker`.
-- `φ.ker` *means* `monoid_hom.ker φ` because `φ` has type `monoid_hom [something]`

example (φ : G →* H) : subgroup G := φ.ker -- or `monoid_hom.ker φ`

example (φ : G →* H)  (x : G) : x ∈ φ.ker ↔ φ x = 1 :=
begin
  refl -- true by definition
end

-- Similarly the image is defined in the obvious way, with `monoid_hom.range`

example (φ : G →* H) : subgroup H := φ.range

example (φ : G →* H) (y : H) : y ∈ φ.range ↔ ∃ x : G, φ x = y :=
begin
  refl -- true by definition
end

-- `subgroup.map` is used for the image of a subgroup under a group hom

example (φ : G →* H) (S : subgroup G) : subgroup H := S.map φ

example (φ : G →* H) (S : subgroup G) (y : H) : y ∈ S.map φ ↔ ∃ x, x ∈ S ∧ φ x = y :=
begin
  refl,
end

-- and `subgroup.comap` is used for the preimage of a subgroup under a group hom.

example (φ : G →* H) (S : subgroup H) : subgroup G := S.comap φ

example (φ : G →* H) (T : subgroup H) (x : G) : x ∈ T.comap φ ↔ φ x ∈ T :=
begin
  refl,
end

-- Here are some basic facts about these constructions.

-- Preimage of a subgroup along the identity map is the same subgroup
example (S : subgroup G) : S.comap (monoid_hom.id G) = S :=
begin
  ext x,
  refl,
end

-- Image of a subgroup along the identity map is the same subgroup
example (S : subgroup G) : S.map (monoid_hom.id G) = S :=
begin
  ext x,
  split,
  { rintro ⟨y, hy, rfl⟩,
    exact hy, },
  { intro hx,
    exact ⟨x, hx, rfl⟩, },
end

-- preimage preserves `≤` (i.e. if `S ≤ T` are subgroups of `H` then `φ⁻¹(S) ≤ φ⁻¹(T)`)
example (φ : G →* H) (S T : subgroup H) (hST : S ≤ T) : S.comap φ ≤ T.comap φ :=
begin
  intros g hg,
  apply hST,
  exact hg,
end

-- image preserves `≤` (i.e. if `S ≤ T` are subgroups of `G` then `φ(S) ≤ φ(T)`)
example (φ : G →* H) (S T : subgroup G) (hST : S ≤ T) : S.map φ ≤ T.map φ :=
begin
  rintros h ⟨g, hg, rfl⟩,
  refine ⟨g, _, rfl⟩,
  exact hST hg,
end

-- Pulling a subgroup back along one homomorphism and then another, is equal
-- to pulling it back along the composite of the homomorphisms.
example (φ : G →* H) (ψ : H →* K) (U : subgroup K) :
  U.comap (ψ.comp φ) = (U.comap ψ).comap φ :=
begin
  refl,
end

-- Pushing a subgroup along one homomorphism and then another is equal to
--  pushing it forward along the composite of the homomorphisms.
example (φ : G →* H) (ψ : H →* K) (S : subgroup G) :
  S.map (ψ.comp φ) = (S.map φ).map ψ :=
begin
  ext c,
  split,
  { rintro ⟨a, ha, rfl⟩,
    refine ⟨φ a, _, rfl⟩,
    exact ⟨a, ha, rfl⟩, },
  { rintro ⟨b, ⟨a, ha, rfl⟩, rfl⟩,
    exact ⟨a, ha, rfl⟩, },
end


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Robert Y. Lewis

! This file was ported from Lean 3 source module ring_theory.witt_vector.defs
! leanprover-community/mathlib commit f1944b30c97c5eb626e498307dec8b022a05bd0a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.RingTheory.WittVector.StructurePolynomial

/-!
# Witt vectors

In this file we define the type of `p`-typical Witt vectors and ring operations on it.
The ring axioms are verified in `ring_theory/witt_vector/basic.lean`.

For a fixed commutative ring `R` and prime `p`,
a Witt vector `x : 𝕎 R` is an infinite sequence `ℕ → R` of elements of `R`.
However, the ring operations `+` and `*` are not defined in the obvious component-wise way.
Instead, these operations are defined via certain polynomials
using the machinery in `structure_polynomial.lean`.
The `n`th value of the sum of two Witt vectors can depend on the `0`-th through `n`th values
of the summands. This effectively simulates a “carrying” operation.

## Main definitions

* `witt_vector p R`: the type of `p`-typical Witt vectors with coefficients in `R`.
* `witt_vector.coeff x n`: projects the `n`th value of the Witt vector `x`.

## Notation

We use notation `𝕎 R`, entered `\bbW`, for the Witt vectors over `R`.

## References

* [Hazewinkel, *Witt Vectors*][Haze09]

* [Commelin and Lewis, *Formalizing the Ring of Witt Vectors*][CL21]
-/

noncomputable section

/- ./././Mathport/Syntax/Translate/Command.lean:424:34: infer kinds are unsupported in Lean 4: mk [] -/
/-- `witt_vector p R` is the ring of `p`-typical Witt vectors over the commutative ring `R`,
where `p` is a prime number.

If `p` is invertible in `R`, this ring is isomorphic to `ℕ → R` (the product of `ℕ` copies of `R`).
If `R` is a ring of characteristic `p`, then `witt_vector p R` is a ring of characteristic `0`.
The canonical example is `witt_vector p (zmod p)`,
which is isomorphic to the `p`-adic integers `ℤ_[p]`. -/
structure WittVector (p : ℕ) (R : Type _) where mk ::
  coeff : ℕ → R
#align witt_vector WittVector

variable {p : ℕ}

-- mathport name: expr𝕎
/- We cannot make this `localized` notation, because the `p` on the RHS doesn't occur on the left
Hiding the `p` in the notation is very convenient, so we opt for repeating the `local notation`
in other files that use Witt vectors. -/
local notation "𝕎" => WittVector p

-- type as `\bbW`
namespace WittVector

variable (p) {R : Type _}

/-- Construct a Witt vector `mk p x : 𝕎 R` from a sequence `x` of elements of `R`. -/
add_decl_doc WittVector.mk

/-- `x.coeff n` is the `n`th coefficient of the Witt vector `x`.

This concept does not have a standard name in the literature.
-/
add_decl_doc WittVector.coeff

@[ext]
theorem ext {x y : 𝕎 R} (h : ∀ n, x.coeff n = y.coeff n) : x = y :=
  by
  cases x
  cases y
  simp only at h
  simp [Function.funext_iff, h]
#align witt_vector.ext WittVector.ext

theorem ext_iff {x y : 𝕎 R} : x = y ↔ ∀ n, x.coeff n = y.coeff n :=
  ⟨fun h n => by rw [h], ext⟩
#align witt_vector.ext_iff WittVector.ext_iff

theorem coeff_mk (x : ℕ → R) : (mk p x).coeff = x :=
  rfl
#align witt_vector.coeff_mk WittVector.coeff_mk

/- These instances are not needed for the rest of the development,
but it is interesting to establish early on that `witt_vector p` is a lawful functor. -/
instance : Functor (WittVector p)
    where
  map α β f v := mk p (f ∘ v.coeff)
  mapConst α β a v := mk p fun _ => a

instance : LawfulFunctor (WittVector p)
    where
  mapConst_eq α β := rfl
  id_map := fun α ⟨v, _⟩ => rfl
  comp_map α β γ f g v := rfl

variable (p) [hp : Fact p.Prime] [CommRing R]

include hp

open MvPolynomial

section RingOperations

/-- The polynomials used for defining the element `0` of the ring of Witt vectors. -/
def wittZero : ℕ → MvPolynomial (Fin 0 × ℕ) ℤ :=
  wittStructureInt p 0
#align witt_vector.witt_zero WittVector.wittZero

/-- The polynomials used for defining the element `1` of the ring of Witt vectors. -/
def wittOne : ℕ → MvPolynomial (Fin 0 × ℕ) ℤ :=
  wittStructureInt p 1
#align witt_vector.witt_one WittVector.wittOne

/-- The polynomials used for defining the addition of the ring of Witt vectors. -/
def wittAdd : ℕ → MvPolynomial (Fin 2 × ℕ) ℤ :=
  wittStructureInt p (X 0 + X 1)
#align witt_vector.witt_add WittVector.wittAdd

/-- The polynomials used for defining repeated addition of the ring of Witt vectors. -/
def wittNsmul (n : ℕ) : ℕ → MvPolynomial (Fin 1 × ℕ) ℤ :=
  wittStructureInt p (n • X 0)
#align witt_vector.witt_nsmul WittVector.wittNsmul

/-- The polynomials used for defining repeated addition of the ring of Witt vectors. -/
def wittZsmul (n : ℤ) : ℕ → MvPolynomial (Fin 1 × ℕ) ℤ :=
  wittStructureInt p (n • X 0)
#align witt_vector.witt_zsmul WittVector.wittZsmul

/-- The polynomials used for describing the subtraction of the ring of Witt vectors. -/
def wittSub : ℕ → MvPolynomial (Fin 2 × ℕ) ℤ :=
  wittStructureInt p (X 0 - X 1)
#align witt_vector.witt_sub WittVector.wittSub

/-- The polynomials used for defining the multiplication of the ring of Witt vectors. -/
def wittMul : ℕ → MvPolynomial (Fin 2 × ℕ) ℤ :=
  wittStructureInt p (X 0 * X 1)
#align witt_vector.witt_mul WittVector.wittMul

/-- The polynomials used for defining the negation of the ring of Witt vectors. -/
def wittNeg : ℕ → MvPolynomial (Fin 1 × ℕ) ℤ :=
  wittStructureInt p (-X 0)
#align witt_vector.witt_neg WittVector.wittNeg

/-- The polynomials used for defining repeated addition of the ring of Witt vectors. -/
def wittPow (n : ℕ) : ℕ → MvPolynomial (Fin 1 × ℕ) ℤ :=
  wittStructureInt p (X 0 ^ n)
#align witt_vector.witt_pow WittVector.wittPow

variable {p}

omit hp

/-- An auxiliary definition used in `witt_vector.eval`.
Evaluates a polynomial whose variables come from the disjoint union of `k` copies of `ℕ`,
with a curried evaluation `x`.
This can be defined more generally but we use only a specific instance here. -/
def peval {k : ℕ} (φ : MvPolynomial (Fin k × ℕ) ℤ) (x : Fin k → ℕ → R) : R :=
  aeval (Function.uncurry x) φ
#align witt_vector.peval WittVector.peval

/-- Let `φ` be a family of polynomials, indexed by natural numbers, whose variables come from the
disjoint union of `k` copies of `ℕ`, and let `xᵢ` be a Witt vector for `0 ≤ i < k`.

`eval φ x` evaluates `φ` mapping the variable `X_(i, n)` to the `n`th coefficient of `xᵢ`.

Instantiating `φ` with certain polynomials defined in `structure_polynomial.lean` establishes the
ring operations on `𝕎 R`. For example, `witt_vector.witt_add` is such a `φ` with `k = 2`;
evaluating this at `(x₀, x₁)` gives us the sum of two Witt vectors `x₀ + x₁`.
-/
def eval {k : ℕ} (φ : ℕ → MvPolynomial (Fin k × ℕ) ℤ) (x : Fin k → 𝕎 R) : 𝕎 R :=
  mk p fun n => peval (φ n) fun i => (x i).coeff
#align witt_vector.eval WittVector.eval

variable (R) [Fact p.Prime]

instance : Zero (𝕎 R) :=
  ⟨eval (wittZero p) ![]⟩

instance : Inhabited (𝕎 R) :=
  ⟨0⟩

instance : One (𝕎 R) :=
  ⟨eval (wittOne p) ![]⟩

instance : Add (𝕎 R) :=
  ⟨fun x y => eval (wittAdd p) ![x, y]⟩

instance : Sub (𝕎 R) :=
  ⟨fun x y => eval (wittSub p) ![x, y]⟩

instance hasNatScalar : SMul ℕ (𝕎 R) :=
  ⟨fun n x => eval (wittNsmul p n) ![x]⟩
#align witt_vector.has_nat_scalar WittVector.hasNatScalar

instance hasIntScalar : SMul ℤ (𝕎 R) :=
  ⟨fun n x => eval (wittZsmul p n) ![x]⟩
#align witt_vector.has_int_scalar WittVector.hasIntScalar

instance : Mul (𝕎 R) :=
  ⟨fun x y => eval (wittMul p) ![x, y]⟩

instance : Neg (𝕎 R) :=
  ⟨fun x => eval (wittNeg p) ![x]⟩

instance hasNatPow : Pow (𝕎 R) ℕ :=
  ⟨fun x n => eval (wittPow p n) ![x]⟩
#align witt_vector.has_nat_pow WittVector.hasNatPow

instance : NatCast (𝕎 R) :=
  ⟨Nat.unaryCast⟩

instance : IntCast (𝕎 R) :=
  ⟨Int.castDef⟩

end RingOperations

section WittStructureSimplifications

@[simp]
theorem wittZero_eq_zero (n : ℕ) : wittZero p n = 0 :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_zero, wittStructureRat, bind₁, aeval_zero', constantCoeff_xInTermsOfW,
    RingHom.map_zero, AlgHom.map_zero, map_wittStructureInt]
#align witt_vector.witt_zero_eq_zero WittVector.wittZero_eq_zero

@[simp]
theorem wittOne_zero_eq_one : wittOne p 0 = 1 :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_one, wittStructureRat, xInTermsOfW_zero, AlgHom.map_one, RingHom.map_one,
    bind₁_X_right, map_wittStructureInt]
#align witt_vector.witt_one_zero_eq_one WittVector.wittOne_zero_eq_one

@[simp]
theorem wittOne_pos_eq_zero (n : ℕ) (hn : 0 < n) : wittOne p n = 0 :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_one, wittStructureRat, RingHom.map_zero, AlgHom.map_one, RingHom.map_one,
    map_wittStructureInt]
  revert hn; apply Nat.strong_induction_on n; clear n
  intro n IH hn
  rw [xInTermsOfW_eq]
  simp only [AlgHom.map_mul, AlgHom.map_sub, AlgHom.map_sum, AlgHom.map_pow, bind₁_X_right,
    bind₁_C_right]
  rw [sub_mul, one_mul]
  rw [Finset.sum_eq_single 0]
  · simp only [invOf_eq_inv, one_mul, inv_pow, tsub_zero, RingHom.map_one, pow_zero]
    simp only [one_pow, one_mul, xInTermsOfW_zero, sub_self, bind₁_X_right]
  · intro i hin hi0
    rw [Finset.mem_range] at hin
    rw [IH _ hin (Nat.pos_of_ne_zero hi0), zero_pow (pow_pos hp.1.Pos _), MulZeroClass.mul_zero]
  · rw [Finset.mem_range]
    intro
    contradiction
#align witt_vector.witt_one_pos_eq_zero WittVector.wittOne_pos_eq_zero

@[simp]
theorem wittAdd_zero : wittAdd p 0 = X (0, 0) + X (1, 0) :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_add, wittStructureRat, AlgHom.map_add, RingHom.map_add, rename_X,
    xInTermsOfW_zero, map_X, wittPolynomial_zero, bind₁_X_right, map_wittStructureInt]
#align witt_vector.witt_add_zero WittVector.wittAdd_zero

@[simp]
theorem wittSub_zero : wittSub p 0 = X (0, 0) - X (1, 0) :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_sub, wittStructureRat, AlgHom.map_sub, RingHom.map_sub, rename_X,
    xInTermsOfW_zero, map_X, wittPolynomial_zero, bind₁_X_right, map_wittStructureInt]
#align witt_vector.witt_sub_zero WittVector.wittSub_zero

@[simp]
theorem wittMul_zero : wittMul p 0 = X (0, 0) * X (1, 0) :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_mul, wittStructureRat, rename_X, xInTermsOfW_zero, map_X, wittPolynomial_zero,
    RingHom.map_mul, bind₁_X_right, AlgHom.map_mul, map_wittStructureInt]
#align witt_vector.witt_mul_zero WittVector.wittMul_zero

@[simp]
theorem wittNeg_zero : wittNeg p 0 = -X (0, 0) :=
  by
  apply MvPolynomial.map_injective (Int.castRingHom ℚ) Int.cast_injective
  simp only [witt_neg, wittStructureRat, rename_X, xInTermsOfW_zero, map_X, wittPolynomial_zero,
    RingHom.map_neg, AlgHom.map_neg, bind₁_X_right, map_wittStructureInt]
#align witt_vector.witt_neg_zero WittVector.wittNeg_zero

@[simp]
theorem constantCoeff_wittAdd (n : ℕ) : constantCoeff (wittAdd p n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [add_zero, RingHom.map_add, constant_coeff_X]
#align witt_vector.constant_coeff_witt_add WittVector.constantCoeff_wittAdd

@[simp]
theorem constantCoeff_wittSub (n : ℕ) : constantCoeff (wittSub p n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [sub_zero, RingHom.map_sub, constant_coeff_X]
#align witt_vector.constant_coeff_witt_sub WittVector.constantCoeff_wittSub

@[simp]
theorem constantCoeff_wittMul (n : ℕ) : constantCoeff (wittMul p n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [MulZeroClass.mul_zero, RingHom.map_mul, constant_coeff_X]
#align witt_vector.constant_coeff_witt_mul WittVector.constantCoeff_wittMul

@[simp]
theorem constantCoeff_wittNeg (n : ℕ) : constantCoeff (wittNeg p n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [neg_zero, RingHom.map_neg, constant_coeff_X]
#align witt_vector.constant_coeff_witt_neg WittVector.constantCoeff_wittNeg

@[simp]
theorem constantCoeff_wittNsmul (m : ℕ) (n : ℕ) : constantCoeff (wittNsmul p m n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [smul_zero, map_nsmul, constant_coeff_X]
#align witt_vector.constant_coeff_witt_nsmul WittVector.constantCoeff_wittNsmul

@[simp]
theorem constantCoeff_wittZsmul (z : ℤ) (n : ℕ) : constantCoeff (wittZsmul p z n) = 0 :=
  by
  apply constantCoeff_wittStructureInt p _ _ n
  simp only [smul_zero, map_zsmul, constant_coeff_X]
#align witt_vector.constant_coeff_witt_zsmul WittVector.constantCoeff_wittZsmul

end WittStructureSimplifications

section Coeff

variable (p R)

@[simp]
theorem zero_coeff (n : ℕ) : (0 : 𝕎 R).coeff n = 0 :=
  show (aeval _ (wittZero p n) : R) = 0 by simp only [witt_zero_eq_zero, AlgHom.map_zero]
#align witt_vector.zero_coeff WittVector.zero_coeff

@[simp]
theorem one_coeff_zero : (1 : 𝕎 R).coeff 0 = 1 :=
  show (aeval _ (wittOne p 0) : R) = 1 by simp only [witt_one_zero_eq_one, AlgHom.map_one]
#align witt_vector.one_coeff_zero WittVector.one_coeff_zero

@[simp]
theorem one_coeff_eq_of_pos (n : ℕ) (hn : 0 < n) : coeff (1 : 𝕎 R) n = 0 :=
  show (aeval _ (wittOne p n) : R) = 0 by simp only [hn, witt_one_pos_eq_zero, AlgHom.map_zero]
#align witt_vector.one_coeff_eq_of_pos WittVector.one_coeff_eq_of_pos

variable {p R}

omit hp

@[simp]
theorem v2_coeff {p' R'} (x y : WittVector p' R') (i : Fin 2) :
    (![x, y] i).coeff = ![x.coeff, y.coeff] i := by fin_cases i <;> simp
#align witt_vector.v2_coeff WittVector.v2_coeff

include hp

theorem add_coeff (x y : 𝕎 R) (n : ℕ) : (x + y).coeff n = peval (wittAdd p n) ![x.coeff, y.coeff] :=
  by simp [(· + ·), eval]
#align witt_vector.add_coeff WittVector.add_coeff

theorem sub_coeff (x y : 𝕎 R) (n : ℕ) : (x - y).coeff n = peval (wittSub p n) ![x.coeff, y.coeff] :=
  by simp [Sub.sub, eval]
#align witt_vector.sub_coeff WittVector.sub_coeff

theorem mul_coeff (x y : 𝕎 R) (n : ℕ) : (x * y).coeff n = peval (wittMul p n) ![x.coeff, y.coeff] :=
  by simp [(· * ·), eval]
#align witt_vector.mul_coeff WittVector.mul_coeff

theorem neg_coeff (x : 𝕎 R) (n : ℕ) : (-x).coeff n = peval (wittNeg p n) ![x.coeff] := by
  simp [Neg.neg, eval, Matrix.cons_fin_one]
#align witt_vector.neg_coeff WittVector.neg_coeff

theorem nsmul_coeff (m : ℕ) (x : 𝕎 R) (n : ℕ) :
    (m • x).coeff n = peval (wittNsmul p m n) ![x.coeff] := by
  simp [SMul.smul, eval, Matrix.cons_fin_one]
#align witt_vector.nsmul_coeff WittVector.nsmul_coeff

theorem zsmul_coeff (m : ℤ) (x : 𝕎 R) (n : ℕ) :
    (m • x).coeff n = peval (wittZsmul p m n) ![x.coeff] := by
  simp [SMul.smul, eval, Matrix.cons_fin_one]
#align witt_vector.zsmul_coeff WittVector.zsmul_coeff

theorem pow_coeff (m : ℕ) (x : 𝕎 R) (n : ℕ) : (x ^ m).coeff n = peval (wittPow p m n) ![x.coeff] :=
  by simp [Pow.pow, eval, Matrix.cons_fin_one]
#align witt_vector.pow_coeff WittVector.pow_coeff

theorem add_coeff_zero (x y : 𝕎 R) : (x + y).coeff 0 = x.coeff 0 + y.coeff 0 := by
  simp [add_coeff, peval]
#align witt_vector.add_coeff_zero WittVector.add_coeff_zero

theorem mul_coeff_zero (x y : 𝕎 R) : (x * y).coeff 0 = x.coeff 0 * y.coeff 0 := by
  simp [mul_coeff, peval]
#align witt_vector.mul_coeff_zero WittVector.mul_coeff_zero

end Coeff

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittAdd_vars (n : ℕ) : (wittAdd p n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_add_vars WittVector.wittAdd_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittSub_vars (n : ℕ) : (wittSub p n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_sub_vars WittVector.wittSub_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittMul_vars (n : ℕ) : (wittMul p n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_mul_vars WittVector.wittMul_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittNeg_vars (n : ℕ) : (wittNeg p n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_neg_vars WittVector.wittNeg_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittNsmul_vars (m : ℕ) (n : ℕ) :
    (wittNsmul p m n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_nsmul_vars WittVector.wittNsmul_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittZsmul_vars (m : ℤ) (n : ℕ) :
    (wittZsmul p m n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_zsmul_vars WittVector.wittZsmul_vars

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem wittPow_vars (m : ℕ) (n : ℕ) : (wittPow p m n).vars ⊆ Finset.univ ×ˢ Finset.range (n + 1) :=
  wittStructureInt_vars _ _ _
#align witt_vector.witt_pow_vars WittVector.wittPow_vars

end WittVector


-- Estudante: Lucas Emanuel Resck Domingues

-- Exercise 1

section
    parameters {A : Type} {R : A → A → Prop}
    parameter (irreflR : irreflexive R)
    parameter (transR : transitive R)

    local infix < := R

    def R' (a b : A) : Prop := R a b ∨ a = b
    local infix ≤ := R'

    theorem reflR' (a : A) : a ≤ a :=
        show a < a ∨ a = a, from or.inr rfl

    theorem transR' {a b c : A} (h1 : a ≤ b) (h2 : b ≤ c):
        a ≤ c :=
                have h3 : a < b ∨ a = b, from h1,
                have h4 : b < c ∨ b = c, from h2,
            show a < c ∨ a = c, from or.elim h3
                (assume h5 : a < b,
                or.elim h4
                    (assume h6 : b < c,
                        have h7 : a < c, from transR h5 h6,
                    or.inl h7)
                    (assume h6 : b = c,
                        have h7 : a < c, from eq.subst h6 h5,
                    or.inl h7))
                (assume h5 : a = b,
                or.elim h4
                    (assume h6 : b < c,
                        have h7 : a < c, from eq.substr h5 h6,
                    or.inl h7)
                    (assume h6 : b = c,
                        have h7 : a = c, from eq.substr h5 h6,
                    or.inr h7))

    include irreflR
    include transR

    theorem antisymmR' {a b : A} (h1 : a ≤ b) (h2 : b ≤ a) :
    a = b :=
        begin
            cases h1,
                cases h2,
                    have h3, from transR h1 h2,
                    have h4, from irreflR a,
                    contradiction,
                apply eq.symm,
                assumption,
            assumption
        end
end

-- Exercise 2

section
    parameters {A : Type} {R : A → A → Prop}
    parameter (reflR : reflexive R)
    parameter (transR : transitive R)

    def S (a b : A) : Prop := R a b ∧ R b a

    include transR

    example : transitive S :=
        begin
            intros a b c h1 h2,
            cases h1 with h3 h4,
            cases h2 with h5 h6,
            apply and.intro,
                have h7, from transR h3 h5,
                assumption,
            exact transR h6 h4
        end
end

--Exercise 3

section
    parameters {A : Type} {a b c : A} {R : A → A → Prop}
    parameter (Rab : R a b)
    parameter (Rbc : R b c)
    parameter (nRac : ¬ R a c)

    -- Prove one of the following two theorems:

    theorem R_is_strict_partial_order :
    irreflexive R ∧ transitive R :=
    sorry

    -- Because nRac, R is not transitive, that is, it's not strict partial order.

    include Rab
    include Rbc
    include nRac

    theorem R_is_not_strict_partial_order :
    ¬(irreflexive R ∧ transitive R) :=
        begin
            intro h1,
            cases h1 with h2 h3,
            have Rac, from h3 Rab Rbc,
            contradiction
        end
end

-- Exercise 4

section
    open nat

    example : 1 ≤ 4 :=
        calc
            1 ≤ 1 : le_refl 1
          ... ≤ 2 : le_succ 1
          ... ≤ 3 : le_succ 2
          ... ≤ 4 : le_succ 3
end


/-
Copyright (c) 2023 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser

! This file was ported from Lean 3 source module algebra.monoid_algebra.ideal
! leanprover-community/mathlib commit 72c366d0475675f1309d3027d3d7d47ee4423951
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.MonoidAlgebra.Division
import Mathbin.RingTheory.Ideal.Basic

/-!
# Lemmas about ideals of `monoid_algebra` and `add_monoid_algebra`
-/

variable {k A G : Type _}

/-- If `x` belongs to the ideal generated by generators in `s`, then every element of the support of
`x` factors through an element of `s`.

We could spell `∃ d, m = d * m` as `mul_opposite.op m' ∣ mul_opposite.op m` but this would be worse.
-/
theorem MonoidAlgebra.mem_ideal_span_of_image [Monoid G] [Semiring k] {s : Set G}
    {x : MonoidAlgebra k G} :
    x ∈ Ideal.span (MonoidAlgebra.of k G '' s) ↔ ∀ m ∈ x.support, ∃ m' ∈ s, ∃ d, m = d * m' :=
  by
  let RHS : Ideal (MonoidAlgebra k G) :=
    { carrier := { p | ∀ m : G, m ∈ p.support → ∃ m' ∈ s, ∃ d, m = d * m' }
      add_mem' := fun x y hx hy m hm => by
        classical exact (Finset.mem_union.1 <| Finsupp.support_add hm).elim (hx m) (hy m)
      zero_mem' := fun m hm => by cases hm
      smul_mem' := fun x y hy m hm =>
        by
        replace hm := finset.mem_bUnion.mp (Finsupp.support_sum hm)
        obtain ⟨xm, hxm, hm⟩ := hm
        replace hm := finset.mem_bUnion.mp (Finsupp.support_sum hm)
        obtain ⟨ym, hym, hm⟩ := hm
        replace hm := finset.mem_singleton.mp (Finsupp.support_single_subset hm)
        obtain rfl := hm
        refine' (hy _ hym).imp fun sm => Exists.imp fun hsm => _
        rintro ⟨d, rfl⟩
        exact ⟨xm * d, (mul_assoc _ _ _).symm⟩ }
  change _ ↔ x ∈ RHS
  constructor
  · revert x
    refine' Ideal.span_le.2 _
    rintro _ ⟨i, hi, rfl⟩ m hm
    refine' ⟨_, hi, 1, _⟩
    obtain rfl := finset.mem_singleton.mp (Finsupp.support_single_subset hm)
    exact (one_mul _).symm
  · intro hx
    rw [← Finsupp.sum_single x]
    apply Ideal.sum_mem _ fun i hi => _
    obtain ⟨d, hd, d2, rfl⟩ := hx _ hi
    convert Ideal.mul_mem_left _ (id <| Finsupp.single d2 <| x (d2 * d) : MonoidAlgebra k G) _
    pick_goal 3
    refine' Ideal.subset_span ⟨_, hd, rfl⟩
    rw [id.def, MonoidAlgebra.of_apply, MonoidAlgebra.single_mul_single, mul_one]
#align monoid_algebra.mem_ideal_span_of_image MonoidAlgebra.mem_ideal_span_of_image

/-- If `x` belongs to the ideal generated by generators in `s`, then every element of the support of
`x` factors additively through an element of `s`.
-/
theorem AddMonoidAlgebra.mem_ideal_span_of'_image [AddMonoid A] [Semiring k] {s : Set A}
    {x : AddMonoidAlgebra k A} :
    x ∈ Ideal.span (AddMonoidAlgebra.of' k A '' s) ↔ ∀ m ∈ x.support, ∃ m' ∈ s, ∃ d, m = d + m' :=
  @MonoidAlgebra.mem_ideal_span_of_image k (Multiplicative A) _ _ _ _
#align add_monoid_algebra.mem_ideal_span_of'_image AddMonoidAlgebra.mem_ideal_span_of'_image


-- sums over sets
import algebra.big_operators

-- positive naturals
import data.pnat

namespace nat

open list

/-- returns the finset of divisors of a positive natural -/
definition factors (d : ℕ+) : list ℕ :=
  filter (λ e, e ∣ d) (range (d+1))

#eval factors 6 -- [1, 2, 3, 6]

lemma mem_factors_iff_divides (d : ℕ+) (e : ℕ) : e ∈ factors d ↔ e ∣ d :=
by simp [factors, -add_comm, nat.lt_succ_iff];
   exact and_iff_right_of_imp (le_of_dvd d.2)

lemma nodup_factors (d : ℕ+) : nodup (factors d) :=
nodup_filter _ (nodup_range (d+1) : nodup (range (d+1)))

/-- returns the sum of f(e) as e ranges over the divisors of d (positive nat) -/
definition divisor_sum
  {β : Type*} [add_comm_monoid β] (f : ℕ → β) (d : ℕ+) : β :=
finset.sum (⟨quotient.mk (factors d),nodup_factors d⟩) f

end nat

open nat

#eval divisor_sum (id) (6) -- it's a perfect number!


import algebra.group_power tactic.norm_num algebra.big_operators

theorem Q2 (n : ℕ) : n ≥ 2 → nat.pow 4 n > nat.pow 3 n + nat.pow 2 n :=
begin
intro H_n_ge_2,
cases n with n1,
  exfalso,revert H_n_ge_2, exact dec_trivial,
cases n1 with n2,
  exfalso,revert H_n_ge_2, exact dec_trivial,
clear H_n_ge_2,
induction n2 with d Hd,
  exact dec_trivial,
let e := nat.succ (nat.succ d),
show nat.pow 4 e*4>nat.pow 3 e*3+nat.pow 2 e*2,
change nat.pow 4 (nat.succ (nat.succ d)) > nat.pow 3 (nat.succ (nat.succ d)) + nat.pow 2 (nat.succ (nat.succ d))
with nat.pow 4 e>nat.pow 3 e+nat.pow 2 e at Hd,
exact calc
nat.pow 4 e * 4 > (nat.pow 3 e + nat.pow 2 e) * 4 : mul_lt_mul_of_pos_right Hd (dec_trivial)
... = nat.pow 3 e*4+nat.pow 2 e*4 : add_mul _ _ _
... ≥ nat.pow 3 e*3+nat.pow 2 e*4 : add_le_add_right (nat.mul_le_mul_left _ (dec_trivial)) _
... ≥ nat.pow 3 e*3+nat.pow 2 e*2 : add_le_add_left (nat.mul_le_mul_left _ (dec_trivial)) _,
end


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

! This file was ported from Lean 3 source module category_theory.subobject.types
! leanprover-community/mathlib commit 610955826b3be3caaab5170fef04ecd5458521bf
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Subobject.WellPowered
import Mathbin.CategoryTheory.Types

/-!
# `Type u` is well-powered

By building a categorical equivalence `mono_over α ≌ set α` for any `α : Type u`,
we deduce that `subobject α ≃o set α` and that `Type u` is well-powered.

One would hope that for a particular concrete category `C` (`AddCommGroup`, etc)
it's viable to prove `[well_powered C]` without explicitly aligning `subobject X`
with the "hand-rolled" definition of subobjects.

This may be possible using Lawvere theories,
but it remains to be seen whether this just pushes lumps around in the carpet.
-/

universe u

open CategoryTheory

open CategoryTheory.Subobject

open CategoryTheory.Type

theorem subtype_val_mono {α : Type u} (s : Set α) : Mono (↾(Subtype.val : s → α)) :=
  (mono_iff_injective _).mpr Subtype.val_injective
#align subtype_val_mono subtype_val_mono

attribute [local instance] subtype_val_mono

/-- The category of `mono_over α`, for `α : Type u`, is equivalent to the partial order `set α`.
-/
@[simps]
noncomputable def Types.monoOverEquivalenceSet (α : Type u) : MonoOver α ≌ Set α
    where
  Functor :=
    { obj := fun f => Set.range f.1.Hom
      map := fun f g t =>
        homOfLE
          (by
            rintro a ⟨x, rfl⟩
            exact ⟨t.1 x, congr_fun t.w x⟩) }
  inverse :=
    { obj := fun s => MonoOver.mk' (Subtype.val : s → α)
      map := fun s t b =>
        MonoOver.homMk (fun w => ⟨w.1, Set.mem_of_mem_of_subset w.2 b.le⟩)
          (by
            ext
            simp) }
  unitIso :=
    NatIso.ofComponents
      (fun f =>
        MonoOver.isoMk (Equiv.ofInjective f.1.Hom ((mono_iff_injective _).mp f.2)).toIso (by tidy))
      (by tidy)
  counitIso := NatIso.ofComponents (fun s => eqToIso Subtype.range_val) (by tidy)
#align types.mono_over_equivalence_set Types.monoOverEquivalenceSet

instance : WellPowered (Type u) :=
  wellPowered_of_essentiallySmall_monoOver fun α =>
    EssentiallySmall.mk' (Types.monoOverEquivalenceSet α)

/-- For `α : Type u`, `subobject α` is order isomorphic to `set α`.
-/
noncomputable def Types.subobjectEquivSet (α : Type u) : Subobject α ≃o Set α :=
  (Types.monoOverEquivalenceSet α).thinSkeletonOrderIso
#align types.subobject_equiv_set Types.subobjectEquivSet


/-
Copyright (c) 2022 Jireh Loreaux. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jireh Loreaux
-/
import analysis.normed_space.star.basic
import analysis.normed_space.spectrum
import algebra.star.module
import analysis.normed_space.star.exponential

/-! # Spectral properties in C⋆-algebras
In this file, we establish various propreties related to the spectrum of elements in C⋆-algebras.
-/

local postfix `⋆`:std.prec.max_plus := star

open_locale topological_space ennreal
open filter ennreal spectrum cstar_ring

section unitary_spectrum

variables
{𝕜 : Type*} [normed_field 𝕜]
{E : Type*} [normed_ring E] [star_ring E] [cstar_ring E]
[normed_algebra 𝕜 E] [complete_space E] [nontrivial E]

lemma unitary.spectrum_subset_circle (u : unitary E) :
  spectrum 𝕜 (u : E) ⊆ metric.sphere 0 1 :=
begin
  refine λ k hk, mem_sphere_zero_iff_norm.mpr (le_antisymm _ _),
  { simpa only [cstar_ring.norm_coe_unitary u] using norm_le_norm_of_mem hk },
  { rw ←unitary.coe_to_units_apply u at hk,
    have hnk := ne_zero_of_mem_of_unit hk,
    rw [←inv_inv (unitary.to_units u), ←spectrum.map_inv, set.mem_inv] at hk,
    have : ∥k∥⁻¹ ≤ ∥↑((unitary.to_units u)⁻¹)∥, simpa only [norm_inv] using norm_le_norm_of_mem hk,
    simpa using inv_le_of_inv_le (norm_pos_iff.mpr hnk) this }
end

lemma spectrum.subset_circle_of_unitary {u : E} (h : u ∈ unitary E) :
  spectrum 𝕜 u ⊆ metric.sphere 0 1 :=
unitary.spectrum_subset_circle ⟨u, h⟩

end unitary_spectrum

section complex_scalars

open complex

variables {A : Type*}
[normed_ring A] [normed_algebra ℂ A] [complete_space A] [star_ring A] [cstar_ring A]

local notation `↑ₐ` := algebra_map ℂ A

lemma spectral_radius_eq_nnnorm_of_self_adjoint {a : A} (ha : a ∈ self_adjoint A) :
  spectral_radius ℂ a = ∥a∥₊ :=
begin
  have hconst : tendsto (λ n : ℕ, (∥a∥₊ : ℝ≥0∞)) at_top _ := tendsto_const_nhds,
  refine tendsto_nhds_unique _ hconst,
  convert (spectrum.pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius (a : A)).comp
      (nat.tendsto_pow_at_top_at_top_of_one_lt (by linarith : 1 < 2)),
  refine funext (λ n, _),
  rw [function.comp_app, nnnorm_pow_two_pow_of_self_adjoint ha, ennreal.coe_pow, ←rpow_nat_cast,
    ←rpow_mul],
  simp,
end

lemma spectral_radius_eq_nnnorm_of_star_normal (a : A) [is_star_normal a] :
  spectral_radius ℂ a = ∥a∥₊ :=
begin
  refine (ennreal.pow_strict_mono (by linarith : 2 ≠ 0)).injective _,
  have ha : a⋆ * a ∈ self_adjoint A,
    from self_adjoint.mem_iff.mpr (by simpa only [star_star] using (star_mul a⋆ a)),
  have heq : (λ n : ℕ, ((∥(a⋆ * a) ^ n∥₊ ^ (1 / n : ℝ)) : ℝ≥0∞))
    = (λ x, x ^ 2) ∘ (λ n : ℕ, ((∥a ^ n∥₊ ^ (1 / n : ℝ)) : ℝ≥0∞)),
  { funext,
    rw [function.comp_apply, ←rpow_nat_cast, ←rpow_mul, mul_comm, rpow_mul, rpow_nat_cast,
      ←coe_pow, sq, ←nnnorm_star_mul_self, commute.mul_pow (star_comm_self' a), star_pow], },
  have h₂ := ((ennreal.continuous_pow 2).tendsto (spectral_radius ℂ a)).comp
    (spectrum.pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius a),
  rw ←heq at h₂,
  convert tendsto_nhds_unique h₂ (pow_nnnorm_pow_one_div_tendsto_nhds_spectral_radius (a⋆ * a)),
  rw [spectral_radius_eq_nnnorm_of_self_adjoint ha, sq, nnnorm_star_mul_self, coe_mul],
end

/-- Any element of the spectrum of a selfadjoint is real. -/
theorem self_adjoint.mem_spectrum_eq_re [star_module ℂ A] [nontrivial A] {a : A}
  (ha : a ∈ self_adjoint A) {z : ℂ} (hz : z ∈ spectrum ℂ a) : z = z.re :=
begin
  let Iu := units.mk0 I I_ne_zero,
  have : exp ℂ ℂ (I • z) ∈ spectrum ℂ (exp ℂ A (I • a)),
    by simpa only [units.smul_def, units.coe_mk0]
      using spectrum.exp_mem_exp (Iu • a) (smul_mem_smul_iff.mpr hz),
  exact complex.ext (of_real_re _)
    (by simpa only [←complex.exp_eq_exp_ℂ_ℂ, mem_sphere_zero_iff_norm, norm_eq_abs, abs_exp,
      real.exp_eq_one_iff, smul_eq_mul, I_mul, neg_eq_zero]
      using spectrum.subset_circle_of_unitary (self_adjoint.exp_i_smul_unitary ha) this),
end

/-- Any element of the spectrum of a selfadjoint is real. -/
theorem self_adjoint.mem_spectrum_eq_re' [star_module ℂ A] [nontrivial A]
  (a : self_adjoint A) {z : ℂ} (hz : z ∈ spectrum ℂ (a : A)) : z = z.re :=
self_adjoint.mem_spectrum_eq_re a.property hz

/-- The spectrum of a selfadjoint is real -/
theorem self_adjoint.coe_re_map_spectrum [star_module ℂ A] [nontrivial A] {a : A}
  (ha : a ∈ self_adjoint A) : spectrum ℂ a = (coe ∘ re '' (spectrum ℂ a) : set ℂ) :=
le_antisymm (λ z hz, ⟨z, hz, (self_adjoint.mem_spectrum_eq_re ha hz).symm⟩) (λ z, by
  { rintros ⟨z, hz, rfl⟩,
    simpa only [(self_adjoint.mem_spectrum_eq_re ha hz).symm, function.comp_app] using hz })

/-- The spectrum of a selfadjoint is real -/
theorem self_adjoint.coe_re_map_spectrum' [star_module ℂ A] [nontrivial A] (a : self_adjoint A) :
  spectrum ℂ (a : A) = (coe ∘ re '' (spectrum ℂ (a : A)) : set ℂ) :=
self_adjoint.coe_re_map_spectrum a.property

end complex_scalars


--                            |   Premisa  |
lemma Example_1 (A B : Prop) : A ∧ (A → B) → B :=
begin
    -- Declaramos nuestra premisa, correspondiente a A ∧ (A → B)
    -- Notemos que buscamos concluir B
    intro h,
    -- h contiene dos partes claves, su lado derecho tiene el camino para llegar a B
    -- Entonces lo tomamos.
    apply and.right h,
    -- Pero para concluir B, requerimos que A sea valido, mas el lado izquierdo de h nos permite aseverarlo.
    apply and.left h
    -- Ganamos :D
end
--                                |         Premisas          |
lemma Example_2 (A B C D : Prop) : (A ∨ B) → (A → C) → (B → D) → C ∨ D :=
begin
    -- Declaramos nuestras premisas
    intros h1 h2 h3,
    -- Notando que h2 y h3 son las implicancias que nos acercan a C y D, respectivamente, necesitamos hacer
    -- validas las proposiciones necesarias. Para ello, hacemos un analisis por caso en base a h1.
    apply or.elim h1,
        -- Declaramos temporalmente a A como hipotesis.
        intro ha,
            -- Partimos demostrando el lado izquierdo de la conclusion, C.
            apply or.inl,
            -- Como tenemos h2, (A → C), y ha, A, podemos utilizar la eliminacion del implica, lo que nos demuestra C.
            apply h2,
            apply ha,
            -- Y ganamos por el lado izquierdo :D
        -- Ahora declaramos B como una hipotesis temporal, recordemos que es el segundo caso. Noten que ahora buscamos
        -- demostrar el lado derecho de nuestra conclusion, D, cuyos pasos son analogos a lo que hicimos para demostrar C.
        intro hb,
            -- Ojo que aqui declaramos la introduccion del or por la derecha, es decir, generar la patita derecha que nos falta
            -- de la conclusion, D.
            apply or.inr,
            apply h3,
            apply hb
    -- Y ganamoooos c:
end
--                            |  Premisa |
lemma Example_3 (A B : Prop) : ¬ (A ∧ B) → (A → ¬ B) :=
begin
    -- Declaramos nuestra premisa
    intro h,
    -- Lo que haremos es introducir el implica, donde asumimos A temporalmente.
    intro ha,
    -- De esta forma, debemos demostrar ¬ B, pero recordemos que esto es equivalente a decir B → false. Entonces nuevamente
    -- introducimos el implica, asumiento temporalmente B.
    intro hb,
    -- Asi, lo que buscamos es demostrar false. Para ello utilizaremos h, que es analogo a decir (A ∧ B) → false.
    apply h,
    -- Pero para cerrar, ahora necesitamos demostrar que tenemos (A ∧ B). Mas, notemos que somos personas bknes y ya tenemos las
    -- piezas necesarias, ha y hb. De tal forma que introducimos el and.
    apply and.intro,
    -- Entregando A
    apply ha,
    -- Entregando B
    apply hb
    -- Y asi demostramos que tenemos (A ∧ B), GANAMOS :DDDD
end


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

! This file was ported from Lean 3 source module data.nat.choose.cast
! leanprover-community/mathlib commit bb168510ef455e9280a152e7f31673cabd3d7496
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Nat.Choose.Basic
import Mathlib.Data.Nat.Factorial.Cast

/-!
# Cast of binomial coefficients

This file allows calculating the binomial coefficient `a.choose b` as an element of a division ring
of characteristic `0`.
-/

open Nat

variable (K : Type _) [DivisionRing K] [CharZero K]

namespace Nat

theorem cast_choose {a b : ℕ} (h : a ≤ b) : (b.choose a : K) = b ! / (a ! * (b - a)!) := by
  have : ∀ {n : ℕ}, (n ! : K) ≠ 0 := Nat.cast_ne_zero.2 (factorial_ne_zero _)
  rw [eq_div_iff_mul_eq (mul_ne_zero this this)]
  rw_mod_cast [← mul_assoc, choose_mul_factorial_mul_factorial h]
#align nat.cast_choose Nat.cast_choose

theorem cast_add_choose {a b : ℕ} : ((a + b).choose a : K) = (a + b)! / (a ! * b !) := by
  rw [cast_choose K (_root_.le_add_right le_rfl), add_tsub_cancel_left]
#align nat.cast_add_choose Nat.cast_add_choose

theorem cast_choose_eq_pochhammer_div (a b : ℕ) :
    (a.choose b : K) = (pochhammer K b).eval ↑(a - (b - 1)) / b ! := by
  rw [eq_div_iff_mul_eq (cast_ne_zero.2 b.factorial_ne_zero : (b ! : K) ≠ 0), ← cast_mul,
    mul_comm, ← descFactorial_eq_factorial_mul_choose, ← cast_descFactorial]
#align nat.cast_choose_eq_pochhammer_div Nat.cast_choose_eq_pochhammer_div

theorem cast_choose_two (a : ℕ) : (a.choose 2 : K) = a * (a - 1) / 2 := by
  rw [← cast_descFactorial_two, descFactorial_eq_factorial_mul_choose, factorial_two, mul_comm,
    cast_mul, cast_two, eq_div_iff_mul_eq (two_ne_zero : (2 : K) ≠ 0)]
#align nat.cast_choose_two Nat.cast_choose_two

end Nat