-- import solutions to level 1
import complex.I

/-!

# Level 2: Complex conjugation

-/

namespace complex

-- First complete the definition using `complex.mk` or `⟨x, y⟩` notation

/-- The complex conjugate of a complex number -/
def conj (z : ℂ) : ℂ := ⟨z.re,-z.im⟩

-- Now prove how it interacts with everything else

/-! ## Real and imaginary parts -/

@[simp] lemma conj_re (z : ℂ) : re(conj z) = re(z) := begin refl end
@[simp] lemma conj_im (z : ℂ) : im(conj z) = -im(z) := begin refl end

/-! ## Behaviour with respect to 0, 1 and I -/

@[simp] lemma conj_zero : conj 0 = 0 :=
begin
    rw conj,
    ext,
    simp,
    simp,
end

@[simp] lemma conj_one : conj 1 = 1 :=
begin
    rw conj,
    simp,
    refl,
end

@[simp] lemma conj_I : conj I = -I :=
begin
 /-     ext;
        simp, -/
    rw conj,
    simp,
    rw I,
    ext,
    simp,
    simp,
end

@[simp] lemma conj_neg_I : conj (-I) = I :=
begin
    rw conj,
    ext,
    simp,
    simp,
end

/-! # Behaviour with respect to +, - and * -/

@[simp] lemma conj_add (z w : ℂ) : conj (z + w) = conj z + conj w :=
begin
    rw conj,
    simp,
    ext,
    simp,
    simp,
    ring,
end

@[simp] lemma conj_neg (z : ℂ) : conj (-z) = -conj z :=
begin
    rw conj,
    simp,
    rw conj,
    ext,
    simp,
    simp,
end

@[simp] lemma conj_mul (z w : ℂ) : conj (z * w) = conj z * conj w :=
begin
    repeat {rw conj},
    simp,
    ext,
    simp,
    simp,
    ring,

end

/-! # Properties of the `conj` map -/

@[simp] lemma conj_conj (z : ℂ) : conj (conj z) = z :=
begin
    rw conj,
    rw conj,
    ext,
    simp,
    simp,
end

lemma conj_inj {z w : ℂ} : conj z = conj w ↔ z = w :=
begin
    rw conj,
    rw conj,
    simp,
    symmetry,
    rw ext_iff,
end

@[simp] lemma conj_eq_zero {z : ℂ} : conj z = 0 ↔ z = 0 :=
begin
    rw conj,
    rw ext_iff,
    simp,
    symmetry,
    rw ext_iff,
    simp,
end

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

-- Two optional lemmas which computer scientists like,
-- giving us easy access to some basic properties
-- of conj

open function

lemma conj_involutive : involutive conj :=
begin
    apply conj_conj,
end

lemma conj_bijective : bijective conj :=
begin
    unfold bijective,
    unfold injective,
    unfold surjective,
    split,
    intros,
    rw conj_inj at a,
    exact a,
    intro,
    have b.conj.conj,
    exact b,
    sorry
end

end complex


import tactic
import tactic.rewrite_search.frontend
import .«1_part_i»
noncomputable theory

namespace notes

universe u

--------------------------------------------------------------------------------
-- Definitions from the Natural Number Game (modified)

-- Definition of ℕ as an inductive type
inductive mynat : Type
| zero :         mynat
| succ : mynat → mynat

namespace mynat

  instance : has_zero mynat := ⟨mynat.zero⟩
  theorem mynat_zero_eq_zero : mynat.zero = 0 := rfl

  def one : mynat := succ 0
  instance : has_one mynat := ⟨mynat.one⟩

-- Addition
  def add : mynat → mynat → mynat
  | m 0        := m
  | m (succ n) := succ (add m n)
  instance : has_add mynat := ⟨add⟩

  @[simp, rewrite] def add_zero (m : mynat) : m + 0 = m := rfl
  @[simp, rewrite] def add_succ (m n : mynat) : m + succ n = succ (m + n) := rfl

  -- (Numerals now work)
  example : mynat := 37

-- Multiplication
  def mul : mynat → mynat → mynat
  | m 0        := 0
  | m (succ n) := mul m n + m
  instance : has_mul mynat := ⟨mul⟩

  @[simp, rewrite] def mul_zero (m : mynat) : m * 0 = 0 := rfl
  @[simp, rewrite] def mul_succ (m n : mynat) : m * succ n = m * n + m := rfl

-- Power
/-
  def pow : mynat → mynat → mynat
  | m 0        := 1
  | m (succ n) := pow m n * m
  instance : has_pow mynat mynat := ⟨pow⟩

  @[simp, rewrite] def pow_zero (m : mynat) : m ^ (0 : mynat) = 1 := rfl
  @[simp, rewrite] def pow_succ (m n : mynat) : m ^ succ n = m ^ n * m := rfl
-/

-- Less or equal than
  def le (m n : mynat) : Prop :=
    ∃ (v : mynat), n = m + v
  instance has_le : has_le mynat := ⟨le⟩

-- Less than
  def lt (m n : mynat) : Prop :=
    ∃ (v : mynat), v ≠ 0 ∧ n = m + v
  instance : has_lt mynat := ⟨lt⟩

end mynat

-- Add this line in the beginning of each file to override the default ℕ
local notation `ℕ` : 1024 := mynat

namespace mynat

-- Peano axioms (here they are all derived theorems!)
-- For proving `no_confusion` with the recursor, refer to TPIL notes
  theorem succ_ne_zero : ∀ (n : ℕ), succ n ≠ 0 :=
    λ n h, mynat.no_confusion h

  theorem succ_inj : ∀ (m n : ℕ), succ m = succ n → m = n :=
    λ m n h, (@mynat.no_confusion (m = n) (succ m) (succ n) h) id

-- (Just a special case of the recursor!)
  theorem axiom_of_induction : ∀ (P : ℕ → Prop),
    P 0 → (∀ n, P n → P (succ n)) → ∀ n, P n :=
      @mynat.rec

-- (Anothor special case of the recursor!)
  def def_by_recursion {α : Type u} : Π (x₀ : α) (F : ℕ → α → α), ℕ → α :=
    @mynat.rec (λ _, α)

-- (For demonstration only; use `rfl` instead)
  variables {α : Type u} (x₀ : α) (F : ℕ → α → α)
  local notation `R` := def_by_recursion x₀ F
  theorem axiom_of_recursion : (R 0 = x₀) ∧ (∀ x, R (succ x) = F x (R x)) :=
    ⟨rfl, (λ x, rfl)⟩

end mynat

--------------------------------------------------------------------------------
-- # Natural Numbers

namespace mynat

section
  variables (x y z : ℕ)

  @[simp, rewrite]
  theorem add_assoc :
    (x + y) + z = x + (y + z)
  :=
  begin
    induction z with z ih,
    { refl },
    { change (x + y + z).succ = (x + (y + z)).succ,
      rw ih }
  end

  @[simp, rewrite]
  theorem zero_add :
    0 + x = x
  :=
  begin
    induction x with x ih,
    { refl },
    { change (0 + x).succ = x.succ,
      rw ih, }
  end

  @[simp, rewrite]
  theorem succ_add :
    succ x + y = succ (x + y)
  :=
  begin
    induction y with y ih,
    { refl },
    { change (x.succ + y).succ = (x + y.succ).succ,
      rw ih, refl }
  end

  @[rewrite] -- No @[simp] in commutativity lemmas
  theorem add_comm :
    x + y = y + x
  :=
  begin
    induction y with y ihy generalizing x,
    { erw zero_add, refl },
    { induction x with x ihx,
      { erw zero_add, refl },
      { change (x.succ + y).succ = (y.succ + x).succ,
        rw [← ihx, ihy],
        change (y + x).succ.succ = (x + y).succ.succ,
        rw ihy }}
  end

  @[simp, rewrite]
  theorem add_mul :
    (x + y) * z = x * z + y * z
  :=
  begin
    induction z with z ih,
    { refl },
    { change (x + y) * z + (x + y) = (x * z + x) + (y * z + y),
      rw [ih, add_assoc, ← add_assoc (y * z) x, add_comm (y * z) x],
      simp only [add_assoc] }
    -- TODO: "simp normal forms"
    -- TODO: permute using `assoc` and `comm`? (requires: tactic programming)
  end

  @[rewrite] -- No @[simp] in commutativity lemmas
  theorem mul_comm :
    x * y = y * x
  :=
  begin
    induction y with y ihy generalizing x,
    { induction x with x ihx,
      { refl },
      { exact ihx }},
    { induction x with x ihx,
      { change 0 * y = 0,
        rw ihy, refl },
      { change x.succ * y + x.succ = y.succ * x + y.succ,
        rw [← ihx, ihy],
        change y * x + y + (x + 1) = x * y + x + (y + 1),
        rw [ihy, add_assoc, ← add_assoc y x 1, add_comm y x],
        simp only [add_assoc] }}
  end

  @[simp, rewrite]
  lemma mul_add : x * (y + z) = x * y + x * z :=
    by rw [mul_comm, add_mul, mul_comm y x, mul_comm z x]

  @[simp, rewrite]
  lemma zero_mul : 0 * x = 0 :=
    by { rw [mul_comm], refl }

  @[simp, rewrite]
  lemma succ_mul : succ x * y = x * y + y :=
    by { rw [mul_comm, mul_comm x y], refl }

  @[simp, rewrite]
  theorem one_mul :
    1 * x = x
  :=
  begin
    induction x with x ih,
    { refl },
    { change 1 * x + 1 = x.succ,
      rw ih, refl }
  end

  @[simp, rewrite]
  theorem mul_one :
    x * 1 = x
  :=
  begin
    change 0 + x = x,
    exact zero_add x,
  end

  @[simp, rewrite]
  theorem mul_assoc :
    (x * y) * z = x * (y * z)
  :=
  begin
    induction z with z ih,
    { refl },
    { change x * y * z + x * y = x * (y * z + y),
      rw [mul_add, ih] }
  end

end

--------------------------------------------------------------------------------

section
  variables (x y z : ℕ)

  theorem add_right_cancel :
    x + z = y + z → x = y
  :=
  begin
    induction z with z ih,
    { exact id },
    { intros h,
      replace h := succ.inj h,
      exact ih h }
  end

  lemma add_left_cancel : x + y = x + z → y = z :=
    by { rw [add_comm x y, add_comm x z], exact add_right_cancel _ _ _ }

  lemma zero_of_add_right_eq_self :
    x + y = x → y = 0
  :=
  begin
    intros h,
    exact add_left_cancel x _ _ h
  end

  lemma zero_of_add_zero :
    x + y = 0 → x = 0 ∧ y = 0
  :=
  begin
    intros h, split,
    { cases y with y, { exact h }, { injections }},
    { cases y with y, { refl }, { injections }}
  end

  theorem mul_right_cancel :
    z ≠ 0 → x * z = y * z → x = y
  :=
  begin
    cases z with z',
    { intros h, exfalso, exact h rfl },
    intros _ h,
    induction x with x ih generalizing y,
    { erw zero_mul at h,
      change 0 = y * z' + y at h,
      cases y with y, { refl }, { injections }},
    { rw succ_mul at h,
      cases y with y',
      { erw zero_mul at h,
        change (x * z'.succ + z').succ = 0 at h,
        injections },
      { rw succ_mul at h,
        replace h := add_right_cancel _ _ _ h,
        rw (ih y' h) }}
  end

  lemma mul_left_cancel : x ≠ 0 → x * y = x * z → y = z :=
    by { rw [mul_comm x y, mul_comm x z], exact mul_right_cancel _ _ _ }

end

--------------------------------------------------------------------------------

-- `ℕ` is a "commutative semiring" with `add_left_cancel` and `mul_left_cancel`!
-- I found some relevant typeclasses in Lean:

instance add_cancel_comm_monoid : add_cancel_comm_monoid mynat :=
  { add             := mynat.add,
    add_assoc       := mynat.add_assoc,
    add_left_cancel := mynat.add_left_cancel,
    zero            := mynat.zero,
    zero_add        := mynat.zero_add,
    add_zero        := mynat.add_zero,
    add_comm        := mynat.add_comm }

instance comm_semiring : comm_semiring mynat :=
  { mul             := mynat.mul,
    left_distrib    := mynat.mul_add,
    right_distrib   := mynat.add_mul,
    zero_mul        := mynat.zero_mul,
    mul_zero        := mynat.mul_zero,
    mul_assoc       := mynat.mul_assoc,
    one             := mynat.one,
    one_mul         := mynat.one_mul,
    mul_one         := mynat.mul_one,
    mul_comm        := mynat.mul_comm,
    ..mynat.add_cancel_comm_monoid }

-- These DO NOT cover the conditional `mul_left_cancel`, though...

--------------------------------------------------------------------------------

section
  variables (x y z : ℕ)

-- Alternative way to switch between ≤ and < (other than by definition)
  lemma le_iff_lt_or_eq :
    x ≤ y ↔ (x < y ∨ x = y)
  :=
  begin
    split,
    { rintros ⟨v, hv⟩,
      cases v with v,
      { right, exact eq.symm hv },
      { left, use v.succ, refine ⟨_, hv⟩, intros h, injections }},
    { rintros (⟨v, ⟨hv₁, hv₂⟩⟩ | h₂),
      { use v, exact hv₂ },
      { use 0, exact eq.symm h₂ }}
  end

  lemma add_right_le_of_le :
    x ≤ y → x + z ≤ y + z
  :=
  begin
    rintros ⟨c, hc⟩,
    use c,
    rw [hc, add_assoc, add_comm c z],
    simp only [add_assoc]
  end

  lemma mul_right_le_of_le :
    x ≤ y → x * z ≤ y * z
  :=
  begin
    rintros ⟨c, hc⟩,
    use c * z,
    rw [hc, add_mul]
  end

  theorem le_total_order :
    is_total_order le
  :=
  begin
    unfold is_total_order,
    refine ⟨_, _, _, _⟩,
    -- Refl
    { intros x, use 0, refl },
    -- Antisymm
    { rintros x y ⟨c, hc⟩ ⟨d, hd⟩,
      rw [hc, add_assoc] at hd,
      change x + 0 = x + (c + d) at hd,
      replace hd := eq.symm (add_left_cancel _ _ _ hd),
      cases (zero_of_add_zero _ _ hd) with hc₁ hd₁,
      rw hc₁ at hc,
      exact eq.symm hc },
    -- Trans
    { rintros x y z ⟨c, hc⟩ ⟨d, hd⟩,
      use c + d,
      rw [hd, hc, add_assoc] },
    -- Total
    { intros x y,
      induction y with y ih,
      { right, use x, erw zero_add },
      { rcases ih with ⟨c, hc⟩ | ⟨c, hc⟩,
        { left, use c.succ, rw hc, refl },
        { cases c with c',
          { left, use 1, rw hc, refl },
          { right, use c', rw hc, rw succ_add, refl }}}}
  end

  theorem lt_trichotomy :
    x = y ∨ x < y ∨ y < x
  :=
  begin
    rcases le_total_order with ⟨_, _, _, htotal⟩,
    rcases (htotal x y) with ⟨c, hc⟩ | ⟨c, hc⟩,
    { cases c with c',
      { left, exact eq.symm hc },
      { right, left, use c'.succ, split,
        { intros h, injections },
        { exact hc }}},
    { cases c with c',
      { left, exact hc },
      { right, right, use c'.succ, split,
        { intros h, injections },
        { exact hc }}}
  end

  lemma le_iff_not_gt :
    x ≤ y ↔ ¬ (y < x)
  :=
  begin
    split,
    { rintros ⟨c, hc⟩ ⟨d, hd', hd⟩,
      cases d with d, { exact hd' rfl },
      rw [hc, add_assoc] at hd,
      replace hd := zero_of_add_right_eq_self _ _ hd.symm,
      injection hd },
    { intros h,
      rcases lt_trichotomy x y with (h₁|h₂|h₃),
      { use 0, rw h₁, refl },
      { rcases h₂ with ⟨c, _, hc⟩, use c, exact hc },
      { exfalso, exact h h₃ }}
  end

  lemma lt_iff_not_ge :
    x < y ↔ ¬ (y ≤ x)
  :=
  begin
    split,
    { rintros ⟨c, hc', hc⟩ ⟨d, hd⟩,
      cases c with c, { exact hc' rfl },
      rw [hd, add_assoc] at hc,
      replace hc := zero_of_add_right_eq_self _ _ hc.symm,
      injection hc },
    { intros h,
      rcases lt_trichotomy x y with (h₁|h₂|h₃),
      { exfalso, exact h ⟨0, h₁⟩ },
      { exact h₂ },
      { rcases h₃ with ⟨c, _, hc⟩, exfalso, exact h ⟨c, hc⟩ }}
  end

-- TODO: `linarith`

end

--------------------------------------------------------------------------------

section

  theorem based_induction : ∀ (P : ℕ → Prop) (n₀ : ℕ),
    P n₀ → (∀ m, n₀ ≤ m → P m → P (succ m)) → ∀ m, n₀ ≤ m → P m
  :=
    -- (Golfing, for tactic proof see the next theorem)
    λ P n₀ h₀ h m ⟨c, hc⟩, hc.symm ▸ (mynat.rec_on c
      h₀ (λ c' ih, h (n₀ + c') ⟨c', rfl⟩ ih))

  theorem based_strong_induction : ∀ (P : ℕ → Prop) (n₀ : ℕ),
    P n₀ → (∀ m, (∀ k, n₀ ≤ k → k ≤ m → P k) → P (succ m)) → ∀ m, n₀ ≤ m → P m
  :=
  begin
    rintros P n₀ h₀ h m ⟨c, hc⟩,
    have : ∀ x y, y ≤ x → P (n₀ + y),
    { intros x, induction x with x ihx,
      { rintros y ⟨d, hd⟩,
        have hy := (zero_of_add_zero _ _ hd.symm).left,
        rw hy,
        exact h₀ },
      { rintros y ⟨d, hd⟩,
        cases d with d,
        { change x.succ = y at hd,
          rw ← hd,
          apply h (n₀ + x),
          rintros k ⟨e, he⟩ ⟨f, hf⟩,
          rw he,
          apply ihx e,
          use f,
          rw [he, add_assoc] at hf,
          exact add_left_cancel _ _ _ hf },
        { apply ihx,
          use d,
          exact mynat.succ.inj hd, }}},
    rw hc,
    exact this c c ⟨0, rfl⟩
  end

  theorem le_well_order : ∀ (X : set ℕ),
    (∃ a₀, a₀ ∈ X) → ∃ a, (a ∈ X ∧ (∀ x, x ∈ X → a ≤ x))
  :=
  begin
    rintros X ⟨a₀, ha₀⟩,
    by_contra h,
    have : ∀ n m, m ≤ n → m ∉ X,
    { intros n, induction n with n ih,
      -- Claim: 0 is not in X
      { rintros m ⟨k, hk⟩ h₁,
        have hm := (zero_of_add_zero _ _ hk.symm).left,
        apply h, use 0, split,
        { rw ← hm, exact h₁ },
        intros x _, use x, exact (zero_add x).symm, },
      -- IH   : 0 ~ n are not in X
      -- Claim: 0 ~ (succ n) are not in X
      { rintros m ⟨k, hk⟩ h₁,
        cases k with k,
        -- If (succ n) is in X, (succ n) will be the least element of X
        { apply h,
          use m, split, { exact h₁ },
          intros x hx,
          rw le_iff_not_gt,
          rintros ⟨c, ⟨hc₁, hc₂⟩⟩,
          cases c with c, { apply hc₁, refl },
          apply (ih x),
          { use c, apply mynat.succ.inj, rw hk, exact hc₂ },
          { exact hx }},
        -- If any of 0 ~ n is in X, that directly contradicts with IH
        { refine ih m _ h₁,
          use k,
          apply mynat.succ.inj,
          exact hk }}},
    refine this a₀ a₀ _ ha₀,
    use 0, refl
  end

-- TODO: well-ordering principle for decidable predicates
-- TODO: (requires well-founded recursion...?)

end

--------------------------------------------------------------------------------

-- `ℕ` is an "*ordered* commutative semiring"!
-- I found some relevant typeclasses in Lean:

#print classes

#check is_well_order -- TODO

instance linear_order : linear_order mynat :=
  let ⟨hrefl, hantisymm, htrans, htotal⟩ := mynat.le_total_order in
    { le           := mynat.le,
      le_refl      := @hrefl,
      le_antisymm  := @hantisymm,
      le_trans     := @htrans,
      le_total     := @htotal,
      decidable_le := λ (l r : ℕ), classical.prop_decidable (l ≤ r) }

instance ordered_semiring : ordered_semiring mynat :=
  { add_le_add_left         := sorry,
    le_of_add_le_add_left   := sorry,
    zero_le_one             := sorry, -- Does anyone know why this would not work?
    mul_lt_mul_of_pos_left  := sorry,
    mul_lt_mul_of_pos_right := sorry,
    ..mynat.add_cancel_comm_monoid,
    ..mynat.comm_semiring,
    ..mynat.linear_order }

end mynat

--------------------------------------------------------------------------------

end notes


import game.world2.level6 -- hide
import mynat.mul -- import the definition of multiplication on mynat

/- Axiom : mul_zero (a : mynat) :
a * 0 = 0
-/

/- Axiom : mul_succ (a b : mynat) :
a * succ(b) = a * b + a
-/

/-

# Multiplication World

A new import! This import gives you the definition of multiplication on your
natural numbers. It is defined by recursion, just like addition.
Here are the two new axioms:

  * `mul_zero (a : mynat) : a * 0 = 0`
  * `mul_succ (a b : mynat) : a * succ(b) = a * b + a`

In words, we define multiplication by "induction on the second variable",
with `a * 0` defined to be `0` and, if we know `a * b`, then `a` times
the number after `b` is defined to be `a * b + a`.

You can keep all the theorems you proved about addition, but
for multiplication, those two results above are you've got right now.
I would recommend that you sort out the bar on the left. Fold up everything,
and then unfold just Theorem Statements -> Multiplication World. This will
remind you of your two new theorems, both of which are true by definition.
If you want to be reminded of the theorems you have proved about addition,
you can just open up the Addition World theorem statements and take a look.
If you don't want to keep opening and closing these menus, why not think
a bit about the logic behind the naming of the proofs? After a while you
might find that you can guess the name of the proof you want.

Anyway, what's going on in multiplication world? Like addition, we need to go
for the proofs that multiplication
is commutative and associative, but as well as that we will
need to prove facts about the relationship between multiplication
and addition, for example `a * (b + c) = a * b + a * c`, so now
there is a lot more to do. Good luck!

We are given `mul_zero`, and the first thing to prove is `zero_mul`.
Like `zero_add`, we of course prove it by induction.

## Level 1: `zero_mul`
-/

namespace mynat -- hide

/- Lemma
For all natural numbers $m$, we have
$$ 0 \times m = 0. $$
-/
lemma zero_mul (m : mynat) : 0 * m = 0 :=
begin [nat_num_game]
  induction m with d hd,
  {
    rw mul_zero,
    refl
  },
  {
    rw mul_succ,
    rw hd,
    rw add_zero,
    refl
  }
end

end mynat -- hide


namespace Ex

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

open Nat

def one := succ zero
def two := succ one
def three := succ two
def six := succ (succ (succ three))
def eight := succ (succ six)

#eval one
#eval two
#eval three
#eval six
#eval eight

def add (m n : Nat) : Nat :=
  match n with
  | zero   => m
  | succ n => succ (add m n)

example : add two one = three
  := rfl

theorem add_zero (m : Nat) : add m zero = m :=
  rfl

theorem add_succ (m n : Nat) : add m (succ n) = succ (add m n) :=
  rfl

theorem zero_add (n : Nat) : add zero n = n := by
  induction n with
  | zero      => rfl
  | succ n ih => rw [add_succ, ih]

theorem succ_add (m n : Nat) : add (succ m) n = succ (add m n) := by
  induction n with
  | zero       => rw [add_zero, add_zero]
  | succ n' ih => rw [add_succ, add_succ, ih]

theorem add_one (n : Nat) : add n one = succ n :=
  rfl

theorem one_add (n : Nat) : add one n = succ n := by
  induction n with
  | zero       => rfl
  | succ n' ih => rw [add_succ, ih]

theorem add_comm (m n : Nat) : add m n = add n m := by
  induction n with
  | zero       => rw [add_zero, zero_add]
  | succ n' ih => rw [add_succ, succ_add, ih]

theorem add_assoc (m n k : Nat) : add (add m n) k = add m (add n k) := by
  induction k with
  | zero       => rw [add_zero, add_zero]
  | succ k' ih => rw [add_succ, add_succ, add_succ, ih]

-- Define multiplication on naturals.

def mul (m n : Nat) : Nat :=
  match n with
  | zero        => zero
  | succ n'     => add m (mul m n')

#eval mul (succ (succ zero)) (succ (succ (succ zero)))

example : mul two three = six :=
  rfl

-- Define predecessor function on naturals.

def pred (n : Nat) : Nat :=
  match n with
  | zero          => zero
  | succ Nat.zero => zero
  | succ n'       => n'

-- Define truncated subtraction on naturals.

def sub (m n : Nat) : Nat :=
  match n with
  | zero    => m
  | succ n' => sub (pred m) n'

example : sub two one = one := rfl

-- Define exponentiation on naturals.

def exp1 (m n : Nat) : Nat :=
  match n with
  | zero    => succ zero
  | succ n' => mul (succ m) (exp1 m n')

-- Then try proving . . .

theorem mul_zero (n : Nat) : mul n zero = zero :=
  rfl

theorem mul_one (n : Nat) : mul n one = n :=
  rfl

theorem mul_succ (m n : Nat) : mul m (succ n) = add m (mul m n) :=
  rfl

theorem zero_mul (n : Nat) : mul zero n = zero := by
  induction n with
  | zero      => rfl
  | succ n ih => rw [mul_succ, zero_add, ih]

theorem one_mul (n : Nat) : mul one n = n := by
  induction n with
  | zero       => rfl
  | succ n' ih => rw [mul_succ, one_add, ih]

theorem mul_assoc (n m k : Nat) : mul k (mul m n) = mul (mul k m) n := sorry

theorem mul_dist : mul (add m n) k = add (mul m k) (mul n k) := by
  induction k with
  | zero       => rw [mul_zero, mul_zero, mul_zero, add_zero]
  | succ k' ih => calc
                    mul (add m n) (succ k') = add (add m n) (mul (add m n) k')          := by rw [mul_succ]
                    _                       = add (add m n) (add (mul m k') (mul n k')) := by rw [ih]
                    _                       = add m (add n (add (mul m k') (mul n k'))) := by rw [add_assoc]
                    _                       = add m (add n (add (mul n k') (mul m k'))) := by simp [add_comm]
                    _                       = add m (add (add n (mul n k')) (mul m k')) := by rw [add_assoc]
                    _                       = add m (add (mul n (succ k')) (mul m k'))  := by rw [← mul_succ]
                    _                       = add m (add (mul m k') (mul n (succ k')))  := by simp [add_comm]
                    _                       = add (add m (mul m k')) (mul n (succ k'))  := by rw [add_assoc]
                    _                       = add (mul m (succ k')) (mul n (succ k'))   := by rw [← mul_succ]

theorem succ_mul (m n : Nat) : mul (succ m) n = add n (mul m n) := by
  induction n with
  | zero       => rw [mul_zero, zero_add, mul_zero]
  | succ n' ih => rw [mul_succ, ih, mul_succ, succ_add, succ_add, ← add_assoc, ← add_assoc]
                  simp [add_comm]

theorem mul_comm : mul m n = mul n m := by
  induction n with
  | zero       => rw [mul_zero, zero_mul]
  | succ n' ih => rw [mul_succ, succ_mul, add_comm, ih, add_comm]

theorem mul_comm' : mul m n = mul n m := by
  induction n <;> simp [*, mul_zero, zero_mul, mul_succ, succ_mul, add_comm]

theorem succ_zero : succ zero = one := rfl

theorem pred_zero : pred zero = zero := rfl

theorem pred_one : pred one = zero := rfl

example : pred two = one := rfl

theorem pred_succ (m : Nat) : pred (succ m) = m := by
  match m with
  | zero    => rfl
  | succ m' => rfl

theorem sub_succ (m n : Nat) : sub m (succ n) = sub (pred m) n := rfl

theorem zero_sub (n : Nat) : sub zero n = zero := by
  induction n with
  | zero       => rfl
  | succ n' ih => rw [sub_succ, pred_zero, ih]

theorem pred_sub (m n : Nat) : sub (pred m) n = pred (sub m n) := by
  induction m with
  | zero       => rw [pred_zero, zero_sub, pred_zero]
  | succ m' ih => sorry

theorem succ_sub (m n : Nat) : sub (succ m) n = succ (sub m n) := by
  induction n with
  | zero       => rfl
  | succ n' ih => sorry

theorem add_sub (m n : Nat) : sub (add m n) n = m := by
  induction n with
  | zero       => rfl
  | succ n' ih => sorry

example : exp1 zero zero = one := rfl

theorem exp_succ (m n : Nat) : exp1 m (succ n) = mul (succ m) (exp1 m n) := rfl

theorem one_exp (n : Nat) : exp1 zero n = one := by
  induction n with
  | zero       => rfl
  | succ n' ih => rw [exp_succ, ih, mul_one, one]

example : exp1 one one = two := rfl

example : exp1 zero two = one := rfl

example : exp1 one three = eight := rfl

theorem exp_one (n : Nat) : exp1 n one = succ n := by
  induction n with
  | zero       => rfl
  | succ n' ih => rfl

theorem exp_zero (n : Nat) : exp1 n zero = one := by
  match n with
  | zero    => rfl
  | succ n' => rfl

theorem exp_dist (k n m : Nat) : mul (exp1 k m) (exp1 k n) = exp1 k (add m n) := sorry


import Mathlib.Tactic.LeftRight
import Mathlib.Tactic.Basic
import Std.Tactic.RCases
/-!
# Advanced proposition world.

## Level 8: `and_or_distrib_left`

We know that `x(y+z)=xy+xz` for numbers, and this is called distributivity of multiplication over
addition. The same is true for `∧` and `∨` -- in fact `∧` distributes over `∨` and `∨` distributes
over `∧`. Let's prove one of these.

Some new tactics are handy here, the `rintro` tactic is a combination of the `intros` tactic with
`rcases` to allow for destructuring patterns while introducing variables. For example,
`rintro ⟨HP, HQ | HR⟩` below matches the subgoal `P ∧ (Q ∨ R)` and introduces the new hypothesis
`HP : P` and breaks the Or `Q ∨ R` into two left and right sub-goals each with
hypothesis `HQ : Q` and `HR : R`.

Notice here that you can use a semi-colon to separate multiple tactics on the same line. Another
trick shown below is the [<;> tactic](../Tactics/concatenate.lean.md). We could have written `left; constructor; assumption; assumption`
since the `constructor` produces two sub-goals we need 2 `assumption` tactics to close those, or you
can just write `<;> assumption` which runs `assumption` on both sub-goals.

## Lemma
If `P`. `Q` and `R` are true/false statements, then
`P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R).`
-/
lemma and_or_distrib_left (P Q R : Prop) : P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) := by
  constructor
  { rintro ⟨HP, HQ | HR⟩
    left; constructor <;> assumption;
    right; constructor <;> assumption
  }
  { rintro (⟨HP, HQ⟩ | ⟨HP, HR⟩)
    constructor; assumption; left; assumption
    constructor; assumption; right; assumption
  }
/-!

## Pro tip

Notice here we have used curly braces to group the answers to each of the two sub-goals produced by
the `constructor` tactic.  But you if you don't like curly braces you can also use dots like this:

-/
lemma and_or_distrib_left₂ (P Q R : Prop) : P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) := by
  constructor
  . rintro ⟨HP, HQ | HR⟩
    left; constructor <;> assumption
    right; constructor <;> assumption
  . rintro (⟨HP, HQ⟩ | ⟨HP, HR⟩)
    constructor; assumption; left; assumption
    constructor; assumption; right; assumption
/-!
Where the definition of the dot is:

> Given a goal state [g1, g2, ... gn], . tacs is a tactic which first changes the goal state to [g1],
then runs tacs. If the resulting goal state is not [], throw an error.
Then restore the remaining goals [g2, ..., gn].

Next up [Level 9](./Level9.lean.md)
-/


/-
Proof strategies.

- direct proof: use established facts

- by negation: to prove ¬ P, assume P;
  show that this yields a contradiction,
  from which a proof of false can then
  be derived. This shows P → false, and
  that is the definition of ¬ P.

  Prove that the square root of two is
  irrational.

  Prove that the square root two is NOT
  rational.

  Prove ¬ sqrt(2) is rational.

  Proof: assume sqrt(2) is rational. In
  this case, we can write sqrt(2) as some
  fraction, a/b. We now want to show that
  this leads to a contradiction. ...

  - by contradiction : to prove P, assume
    ¬ P and show that this leads to a
    contradiction, from which a proof of
    false can be derived. This proves
    ¬ P → false, which is equivalent to
    ¬ ¬ P. The apply the *classical* rule
    of negation elimination to deduce P.

    Classical rule of negation elimination:
    ∀ (P : Prop), ¬ ¬ P → P.

    Prove 0 = 0 by contradiction.

    We want to prove P (0 = 0). Assume
    ¬ (0 = 0), and show that this leads
    to a contradiction. But by the
    reflexive property of equality, which
    says everything is equal to itself,
    we know immediate that 0 = 0. That
    gives us a direct contradiction,
    between ¬ (0=0) and (0=0). From
    such a contradiction we can derive
    a proof of false, showing that
    ¬ (0=0) → false. And this just means
    ¬ (¬ 0=0). The by classical negation
    elimination, this implies 0 = 0.

    - Today: proof by induction.
-/

-- Answer to question about why contradictions imply false
axiom P : Prop
axiom p : P
axiom np : ¬ P  /- P → false -/
#check (np p)

/-
Prove this: ∀ (m : ℕ), my_add 0 m = m
-/

def my_add : ℕ → ℕ → ℕ
| nat.zero m := m
| (nat.succ n') m := nat.succ (my_add n' m)

-- 0 + m = m
-- (1 + n') + m = 1 + (n' + m)

/-
Proof: By the definition of addition,
and specifically by the first of the
two cases, which tells us that for any
m, 0 + m = m.
-/

/-
Many proofs are accomplished by mere
simplification of both sides of some
equation *using function definitions*
that are already known.
-/

/-
If one is being precise, however, there
are some unexpected consequences. One is
that sometimes something that looks easy
turns out to be a bit more complicated.

For example, try to prove this (using)
only what we know so far.
-/

example : ∀ (n : ℕ), my_add n 0 = n := _

/-
We have no rule (yet) for adding zero
on the right, so simplifying using the
definition of my_add doesn't work.
Instead, we need to try a whole different
proof strategy.
-/

/-
Here's the idea:

- every inductively defined type has a
  corresponding induction rule

- It's a rule for showing that some
  proposition is true for *every* value
  of the given type

- The induction principle for ℕ is this:
  ∀ P : (ℕ → Prop),
  P 0 →
  ((∀ n' : ℕ), P n' → P (nat.succ n')) →
  ∀ n, P n

In other words, for any predicate/property
P, to show ∀ (n : ℕ), P n, it *suffices*
to show the following:
    (1) P 0,
    (2) (∀ n' : ℕ), P n' → P (nat.succ n')
-/

/-
Example: We want to prove ∀ n, n + 0 = n.
Proof by induction. If suffices to show that
(1) Case: n = 0. By first rule of my_add.
(2) Case: n = nat.succ n':
    Show P n' → P (nat.succ n').
    Assume P n'? my_add n' 0 = n'
    Wow show P (nat.succ n'), (nat.succ n') + 0 = (nat.succ n').
    *** simp: nat.succ (my_add n' 0) = nat.succ n' --do some algebra!
    *** apply induction hypothesis: nat.succ n' = nat.succ n'
    Finish by reflexive property of equality.

def my_add : ℕ → ℕ → ℕ
| nat.zero m := m
| (nat.succ n') m := nat.succ (my_add n' m)
-/

/-
∀ n, Sum numbers from 0 to n = n * (succ n) / 2.

Proof by induction. We will apply the principle of
induction for the natural numbers to two smaller
proofs: one for n = 0, and one that shows that if
the formula is true for some n' > 0, then it must
also be true succ n'.

Base case: prove (P 0): show sum from 0 to 0 = (0 * 1)/2 = 0.
Inductive case: Show  P n' → P (n' + 1)
Assume P n'. The sum from 0 to n' = n' * (n' + 1) / 2.
Show (P (n' + 1)): The sum from 0 to n'+1 = (n'+1)((n'+1)+1)/2.
Do some algebra!

--- intuition
1+2+3+4+5 if we assume this is 5*6/2
(1+2+3+4+5)+6 show this is 6*7/2
sum 0 to 5 + 6! = 5*6/2 + 6

The sum from 0 to (n' + 1) = sum from 0 to n' + (n' + 1)
                           = (n'*(n'+1)/2) + (n' + 1)
                           ...
                           = (n'+1)((n'+1)+1)/2.

-/


import data.real.basic

/-
One of the earliest kind of proofs one encounters while learning mathematics is proving by
a calculation. It may not sound like a proof, but this is actually using lemmas expressing
properties of operations on numbers. It also uses the fundamental property of equality: if two
mathematical objects A and B are equal then, in any statement involving A, one can replace A
by B. This operation is called rewriting, and the Lean "tactic" for this is `rw`.

In the following exercises, we will use the following two lemmas:
  mul_assoc a b c : a * b * c = a * (b * c)
  mul_comm a b : a*b = b*a

Hence the command
  rw mul_assoc a b c,
will replace a*b*c by a*(b*c) in the current goal.

In order to replace backward, we use
  rw ← mul_assoc a b c,
replacing a*(b*c) by a*b*c in the current goal.

Of course we don't want to constantly invoke those lemmas, and we will eventually introduce
more powerful solutions.
-/

example (a b c : ℝ) : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc b a c,
end

-- 0001
example (a b c : ℝ) : (c * b) * a = b * (a * c) :=
begin
  rw mul_comm c b,
  rw mul_assoc b c a,
  rw mul_comm c a,
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc a b c,
  rw mul_comm a b,
  rw mul_assoc b a c,
end

/-
Now let's return to the preceding example to experiment with what happens
if we don't give arguments to mul_assoc or mul_comm.
For instance, you can start the next proof with
  rw ← mul_assoc,
Try to figure out what happens.
-/

-- 0003
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ← mul_assoc,
  rw mul_comm a b,
  rw mul_assoc,
end

/-
We can also perform rewriting in an assumption of the local context, using for instance
  rw mul_comm a b at hyp,
in order to replace a*b by b*a in assumption hyp.

The next example will use a third lemma:
  two_mul a : 2*a = a + a

Also we use the `exact` tactic, which allows to provide a direct proof term.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  rw hyp' at hyp,
  rw mul_comm d a at hyp,
  rw ← two_mul (a*d) at hyp,
  rw ← mul_assoc 2 a d at hyp,
  exact hyp, -- Our assumption hyp is now exactly what we have to prove
end

/-
And the next one can use:
  sub_self x : x - x = 0
-/

-- 0004
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  rw hyp' at hyp,
  rw mul_comm at hyp,
  rw sub_self (a*b) at hyp,
  exact hyp,
end

/-
What is written in the two preceding example is very far away from what we would write on
paper. Let's now see how to get a more natural layout.
Inside each pair of curly braces below, the goal is to prove equality with the preceding line.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b : by { rw hyp }
  ... = d*a + a*d  : by { rw hyp' }
  ... = a*d + a*d  : by { rw mul_comm d a }
  ... = 2*(a*d)    : by { rw two_mul }
  ... = 2*a*d      : by { rw mul_assoc },
end

/-
Let's note there is no comma at the end of each line of calculation. `calc` is really one
command, and the comma comes only after it's fully done.

From a practical point of view, when writing such a proof, it is convenient to:
* pause the tactic state view update in VScode by clicking the Pause icon button
  in the top right corner of the Lean Goal buffer
* write the full calculation, ending each line with ": by {}"
* resume tactic state update by clicking the Play icon button and fill in proofs between
  curly braces.

Let's return to the other example using this method.
-/

-- 0005
example (a b c d : ℝ) (hyp : c = b*a - d) (hyp' : d = a*b) : c = 0 :=
begin
  calc c = b*a - d : by { rw hyp }
  ... = b*a - a*b  : by { rw hyp' }
  ... = a*b - a*b  : by { rw mul_comm b a}
  ... = 0          : by { rw sub_self (a*b) },
end

/-
The preceding proofs have exhausted our supply of "mul_comm" patience. Now it's time
to get the computer to work harder. The `ring` tactic will prove any goal that follows by
applying only the axioms of commutative (semi-)rings, in particular commutativity and
associativity of addition and multiplication, as well as distributivity.

We also note that curly braces are not necessary when we write a single tactic proof, so
let's get rid of them.
-/

example (a b c d : ℝ) (hyp : c = d*a + b) (hyp' : b = a*d) : c = 2*a*d :=
begin
  calc c = d*a + b   : by rw hyp
     ... = d*a + a*d : by rw hyp'
     ... = 2*a*d     : by ring,
end

/-
Of course we can use `ring` outside of `calc`. Let's do the next one in one line.
-/

-- 0006
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  ring,
end

/-
This is too much fun. Let's do it again.
-/

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b :=
begin
  ring,
end

/-
Maybe this is cheating. Let's try to do the next computation without ring.
We could use:
pow_two x : x^2 = x*x
mul_sub a b c : a*(b-c) = a*b - a*c
add_mul a b c : (a+b)*c = a*c + b*c
add_sub a b c : a + (b - c) = (a + b) - c
sub_sub a b c : a - b - c = a - (b + c)
add_zero a : a + 0 = a
-/

-- 0008
example (a b : ℝ) : (a + b)*(a - b) = a^2 - b^2 :=
begin
  rw mul_sub,
  rw add_mul,
  rw add_mul,
  rw ← sub_sub,
  rw mul_comm b a,
  rw ← add_sub,
  rw sub_self,
  rw add_zero,
  rw pow_two,
  rw pow_two,
end

/- Let's stick to ring in the end. -/


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Yury Kudryashov, Yaël Dillies

! This file was ported from Lean 3 source module algebra.module.big_operators
! leanprover-community/mathlib commit 509de852e1de55e1efa8eacfa11df0823f26f226
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Module.Basic
import Mathlib.GroupTheory.GroupAction.BigOperators

/-!
# Finite sums over modules over a ring
-/

open BigOperators

variable {α β R M ι : Type _}

section AddCommMonoid

variable [Semiring R] [AddCommMonoid M] [Module R M] (r s : R) (x y : M)

theorem List.sum_smul {l : List R} {x : M} : l.sum • x = (l.map fun r ↦ r • x).sum :=
  ((smulAddHom R M).flip x).map_list_sum l
#align list.sum_smul List.sum_smul

theorem Multiset.sum_smul {l : Multiset R} {x : M} : l.sum • x = (l.map fun r ↦ r • x).sum :=
  ((smulAddHom R M).flip x).map_multiset_sum l
#align multiset.sum_smul Multiset.sum_smul

theorem Multiset.sum_smul_sum {s : Multiset R} {t : Multiset M} :
    s.sum • t.sum = ((s ×ˢ t).map fun p : R × M ↦ p.fst • p.snd).sum := by
  induction' s using Multiset.induction with a s ih
  · simp
  · simp [add_smul, ih, ← Multiset.smul_sum]
#align multiset.sum_smul_sum Multiset.sum_smul_sum

theorem Finset.sum_smul {f : ι → R} {s : Finset ι} {x : M} :
    (∑ i in s, f i) • x = ∑ i in s, f i • x := ((smulAddHom R M).flip x).map_sum f s
#align finset.sum_smul Finset.sum_smul

-- Porting note: changed `×ˢ` to `xᶠ` in the statement of the theorem to fix ambiguous notation
theorem Finset.sum_smul_sum {f : α → R} {g : β → M} {s : Finset α} {t : Finset β} :
    ((∑ i in s, f i) • ∑ i in t, g i) = ∑ p in s ×ᶠ t, f p.fst • g p.snd := by
  rw [Finset.sum_product, Finset.sum_smul, Finset.sum_congr rfl]
  intros
  rw [Finset.smul_sum]
#align finset.sum_smul_sum Finset.sum_smul_sum

end AddCommMonoid

theorem Finset.cast_card [CommSemiring R] (s : Finset α) : (s.card : R) = ∑ a in s, 1 := by
  rw [Finset.sum_const, Nat.smul_one_eq_coe]
#align finset.cast_card Finset.cast_card


/-
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 solutions.section06quotients.sheet1definitions

/-!

## The API for quotients

Right now we have the equivalence relation `R : ℤ → ℤ → Prop` and also
the "setoid" `s : setoid ℤ`. If you did `cases s` you would get
a pair, you'd get `R` and the proof `R_equivalence` that it's an
equivalence relation. But we won't ever need to take `s` apart.

Recall that we've defined `Zmod37` to be `quotient s`. Now here
is the API that we will need. Note that we are not learning
new *tactics* here, we are learning new *theorems from the maths library*.
The same old tactics (`intro`, `apply`, `simp` etc) are useful whether
you're doing algebra, analysis, geometry or whatever. But the theorems
and definitions in the library apply in more specialised domains.
In this sheet I'll show you the theorems and definitions you'll
need to start working with quotients.

-/

-- `setoid` is a class, so `s` should not just be a definition, we should
-- make it an instance by tagging it with the `instance` attribute

attribute [instance] s

-- This unlocks notation `≈` for the equivalence relation `R`
-- and `⟦x⟧` for the element of `Zmod37` corresponding to the integer `x`.

-- Uncomment the three lines below to see it in action

--def n : ℤ := 23 -- a random integer

--#check ⟦n⟧ -- it's in Zmod37

--#check n ≈ 42 -- it's a true-false statement

-- in fact `⟦a⟧` is just notation for `quotient.mk a`, the
-- function from `X` to `quotient s`. Let's check this.

example (a : ℤ) : ⟦a⟧ = quotient.mk a :=
begin
  refl
end

-- and `≈` is just notation for `R`
-- (this is handy to know; let's give it a name so we can rewrite it)
lemma equiv_def (a b : ℤ) : a ≈ b ↔ R a b :=
begin
  refl
end

-- The theorem that quotient.mk is surjective is called `surjective_quotient_mk`.

example : function.surjective (λ (a : ℤ), ⟦a⟧) :=
begin
  exact surjective_quotient_mk ℤ
  -- `surjective_quotient_mk` is a theorem in the library
  -- maybe you could have guessed its name?
end

-- The theorem that two integers are related iff their images are equal
-- is called `quotient.eq`.

example (a b : ℤ) : ⟦a⟧ = ⟦b⟧ ↔ a ≈ b :=
begin
  exact quotient.eq -- so `rw quotient.eq` is often useful
end

-- Both implications also have names

example (a b : ℤ) : ⟦a⟧ = ⟦b⟧ → a ≈ b :=
begin
  exact quotient.exact
end

example (a b : ℤ) : a ≈ b → ⟦a⟧ = ⟦b⟧ :=
begin
  exact quotient.sound
end

/-

## Being "well-defined"

Here is a concept which students sometimes find difficult.

Let's say we want to define a function from `Zmod37` to another type.
For example, when we start putting the additive abelian group structure
on `Zmod37` we will need to define the additive inverse map `neg : Zmod37 → Zmod37`.
Here's how mathematicians usually explain it.

Step 0) We're defining a function `f : Zmod37 → α` for some type `α`.
Step 1) Choose an element `z` in `Zmod37`. We want to define `f(z)`.
Step 2) Lift `z` completely randomly to an integer `a` in `ℤ` (OK because
the quotient map is surjective).
Step 3) Now write down a formula for what we want `f(z)` to be,
except that this formula will depend on `a` (for example, in the case of negation we
would define `f(z)=⟦-a⟧` ; note that this makes sense because we already have `neg : ℤ → ℤ`)
Step 4) Now check that "this function is well-defined". This means
the following: say that we had lifted `z` to a different integer `b` in `ℤ`
in step 2. Then we need to check that our recipe for `f(z)` defined using
`a` and our recipe using `b` give the same answer. In the negation case
this would boil down to checking that if `a ≈ b` then `⟦-a⟧ = ⟦-b⟧`.

See if you can prove the key lemma needed to show that
negation on `Zmod37` is well-defined.
-/

namespace Zmod

lemma negation_is_well_defined_key_lemma (a b : ℤ) (h : a ≈ b) : ⟦-a⟧ = ⟦-b⟧ :=
begin
  sorry,
end

-- The lemma above is somehow the key ingredient to make those
-- steps above work. Lean has a function called `quotient.lift` which
-- does all the steps at once

def neg : Zmod37 → Zmod37 := quotient.lift (λ a, ⟦-a⟧) (negation_is_well_defined_key_lemma)

/-
The general set-up is as follows: if `Y` is the quotient of `X` by `≈`
then if we want to define a function from `Y` to a type `α` then we
can do it by defining a function `F` from `X` to `α` and then proving
that equivalent elements of `X` get sent to equal elements of `α`.
Then `quotient.lift` in Lean's library can be fed the function `F`
and the proof, and it will spit out the function `f` from `Y` to `α`
with the property that `f(⟦x⟧)=F(x)`.

But now here's a question: Let's say we've defined `f` this way.
How do we know that `f(⟦x⟧) = F(x)`?
Here's what this question boils down to in our case:

-/

example (a : ℤ) : neg ⟦a⟧ = ⟦-a⟧ :=
begin
  refl -- true by definition. That is part of the magic of Lean's quotient types.
end

/-

## A second definition of `neg`

Here is another way of defining `neg` on `Zmod37`. We know that
there's a negation function on `ℤ` sending `n` to `-n`. We want
to "descend" this function to `Zmod37`. The Lean function `quotient.map`
does this for us. The function `quotient.map` eats a function `F : X₁ → X₂`
where `X₁` and `X₂` are types with setoid structures (i.e. equivalence
relations `≈₁` and `≈₂` in the typeclass system) and also eats a proof that
``a ≈₁ b → F a ≈₂ F b`, and spits out the function `f : Y₁ → Y₂` making
the diagram commute (i.e. such that `⟦F(x₁)⟧ = f(⟦x₁⟧)`).

-/
def neg2 : Zmod37 → Zmod37 := quotient.map (λ a, -a) begin
  -- goal looks terrifying! I don't really understand it myself!
  -- But bravely start with `intro a`, and use `dsimp` to get rid of the `lambda`s
  sorry
end

-- The diagram commutes by definition
example (a : ℤ) : neg2 ⟦a⟧ = ⟦-a⟧ :=
begin
  refl
end

-- The two ways of defining negation are definitionally equal as well
example : neg = neg2 :=
begin
  refl
end

-- We have negation; in the next sheet we'll define addition
-- and then we'll be able to prove `Zmod37` is an additive abelian group.
-- We'll then zip through multiplication and show it's a ring.

end Zmod


import data.set.basic data.set.lattice data.nat.parity
import tactic.linarith

open set nat function

open_locale classical

variables {α : Type*} {β : Type*} {γ : Type*} {I : Type*}

/-!
## Set exercises
These are collected from *Mathematics in Lean*.
We will go over the examples together, and then let you
work on the exercises.
There is more material here than can fit in the sessions,
but we will pick and choose as we go.
-/

section set_variables

variable  x : α
variables s t u : set α

/-!
### Notation
-/

#check s ⊆ t        -- \sub
#check x ∈ s        -- \in or \mem
#check x ∉ s        -- \notin
#check s ∩ t        -- \i or \cap
#check s ∪ t        -- \un or \cup
#check (∅ : set α)  -- \empty

/-!
### Examples
-/

-- Three proofs of the same fact.
-- The first two expand definitions explicitly,
-- while the third forces Lean to do the unfolding.

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  rw [subset_def, inter_def, inter_def],
  rw subset_def at h,
  dsimp,
  rintros x ⟨xs, xu⟩,
  exact ⟨h _ xs, xu⟩,
end

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  simp only [subset_def, mem_inter_eq] at *,
  rintros x ⟨xs, xu⟩,
  exact ⟨h _ xs, xu⟩,
end

example (h : s ⊆ t) : s ∩ u ⊆ t ∩ u :=
begin
  intros x xsu,
  exact ⟨h xsu.1, xsu.2⟩
end

-- Use `cases` or `rcases` or `rintros` with union.
-- Two proofs of the same fact, one longer and one shorter.

example : s ∩ (t ∪ u) ⊆ (s ∩ t) ∪ (s ∩ u) :=
begin
  intros x hx,
  have xs : x ∈ s := hx.1,
  have xtu : x ∈ t ∪ u := hx.2,
  cases xtu with xt xu,
  { left,
    show x ∈ s ∩ t,
    exact ⟨xs, xt⟩ },
  right,
  show x ∈ s ∩ u,
  exact ⟨xs, xu⟩
end

example : s ∩ (t ∪ u) ⊆ (s ∩ t) ∪ (s ∩ u) :=
begin
  rintros x ⟨xs, xt | xu⟩,
  { left, exact ⟨xs, xt⟩ },
  right, exact ⟨xs, xu⟩
end

-- Two examples with set difference.
-- Type it as ``\\``.
-- ``x ∈ s \ t`` expands to ``x ∈ s ∧ x ∉ t``.

example : s \ t \ u ⊆ s \ (t ∪ u) :=
begin
  intros x xstu,
  have xs : x ∈ s := xstu.1.1,
  have xnt : x ∉ t := xstu.1.2,
  have xnu : x ∉ u := xstu.2,
  split,
  { exact xs }, dsimp,
  intro xtu, -- x ∈ t ∨ x ∈ u
  cases xtu with xt xu,
  { show false, from xnt xt },
  show false, from xnu xu
end

example : s \ t \ u ⊆ s \ (t ∪ u) :=
begin
  rintros x ⟨⟨xs, xnt⟩, xnu⟩,
  use xs,
  rintros (xt | xu); contradiction
end

/-!
### Exercises
-/

example : (s ∩ t) ∪ (s ∩ u) ⊆ s ∩ (t ∪ u):=
begin
  rintros x (⟨xs, xt⟩ | ⟨xs, xu⟩),
  from ⟨xs, or.inl xt⟩,
  from ⟨xs, or.inr xu⟩,
end

example : s \ (t ∪ u) ⊆ s \ t \ u :=
begin
  rintros x ⟨xs, h⟩, dsimp at h,
  push_neg at h,
  use [xs, h.1, h.2],
end

/-!
### Proving two sets are equal
-/

-- the ext tactic

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

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

/-!
### Exercises
-/

example : s ∩ (s ∪ t) = s :=
begin
  ext x,
  split,
  { rintros ⟨xs, h⟩, from xs,},
  { intro xs, from ⟨xs, or.inl xs⟩,}
end

example : s ∪ (s ∩ t) = s :=
begin
  ext x, split,
  { rintro (xs | ⟨xs, xt⟩); from xs,},
  { intro xs, left, from xs,}
end

example : (s \ t) ∪ t = s ∪ t :=
begin
  ext x, split,
  { rintro (⟨xs, xnt⟩ | xt),
      left, from xs,
      right, from xt,},
  { rintro (xs | xt),
    { by_cases xt : x ∈ t,
      right, from xt,
      left, from ⟨xs, xt⟩,},
    right, from xt,}
end

example : (s \ t) ∪ (t \ s) = (s ∪ t) \ (s ∩ t) :=
begin
  ext x, split,
  { rintros (⟨xs, xnt⟩ | ⟨xt, xns⟩),
    { simp, split, left, from xs,
      intro, from xnt,},
    { simp, split, right, from xt,
      intro, contradiction,} },
  { simp, rintros (xs | xt) xsnt,
    { use [xs, xsnt xs],},
    { by_contra' h',
      from xsnt (h'.2 xt) xt,}}
end

/-!
### Set-builder notation
-/

def evens : set ℕ := {n | even n}
def odds :  set ℕ := {n | ¬ even n}

example : evens ∪ odds = univ :=
begin
  rw [evens, odds],
  ext n,
  simp,
  apply classical.em
end

example : s ∩ t = {x | x ∈ s ∧ x ∈ t} := rfl
example : s ∪ t = {x | x ∈ s ∨ x ∈ t} := rfl
example : (∅ : set α) = {x | false} := rfl
example : (univ : set α) = {x | true} := rfl

example (x : ℕ) (h : x ∈ (∅ : set ℕ)) : false :=
h

example (x : ℕ) : x ∈ (univ : set ℕ) :=
trivial

/-!
### Exercise
-/

-- Use `intro n` to unfold the definition of subset,
-- and use the simplifier to reduce the
-- set-theoretic constructions to logic.
-- We also recommend using the theorems
-- ``prime.eq_two_or_odd`` and ``even_iff``.

example : { n | nat.prime n } ∩ { n | n > 2} ⊆ { n | ¬ even n } :=
begin
  rintros n ⟨np, n2⟩ en, dsimp at *,
  cases prime.eq_two_or_odd np,
  linarith,
  rw even_iff at en,
  linarith,
end

/-!
Indexed unions
-/

-- See *Mathematics in Lean* for a discussion of
-- bounded quantifiers, which we will skip here.

section

-- We can use any index type in place of ℕ
variables A B : ℕ → set α

example : s ∩ (⋃ i, A i) = ⋃ i, (A i ∩ s) :=
begin
  ext x,
  simp only [mem_inter_eq, mem_Union],
  split,
  { rintros ⟨xs, ⟨i, xAi⟩⟩,
    exact ⟨i, xAi, xs⟩ },
  rintros ⟨i, xAi, xs⟩,
  exact ⟨xs, ⟨i, xAi⟩⟩
end

example : (⋂ i, A i ∩ B i) = (⋂ i, A i) ∩ (⋂ i, B i) :=
begin
  ext x,
  simp only [mem_inter_eq, mem_Inter],
  split,
  { intro h,
    split,
    { intro i,
      exact (h i).1 },
    intro i,
    exact (h i).2 },
  rintros ⟨h1, h2⟩ i,
  split,
  { exact h1 i },
  exact h2 i
end

end

/-!
### Exercise
-/

-- One direction requires classical logic!
-- We recommend using ``by_cases xs : x ∈ s``
-- at an appropriate point in the proof.

section

variables A B : ℕ → set α

example : s ∪ (⋂ i, A i) = ⋂ i, (A i ∪ s) :=
begin
  ext x, simp only [mem_Inter], split,
  { rintros (xs | fAi) i,
    right, from xs,
    rw mem_Inter at fAi,
    left, from fAi i,},
  { intro h, by_cases xs : x ∈ s,
    { left, from xs,},
    { right, rw mem_Inter, intro i,
      cases h i with xAi xs,
      from xAi,
      contradiction,}},
end

end

/-
Mathlib also has bounded unions and intersections,
`⋃ x ∈ s, f x` and `⋂ x ∈ s, f x`,
and set unions and intersections, `⋃₀ s` and `⋂₀ s`,
where `s : set α`.
See *Mathematics in Lean* for details.
-/

end set_variables

/-!
### Functions
-/

section function_variables

variable  f : α → β
variables s t : set α
variables u v : set β
variable  A : I → set α
variable  B : I → set β

#check f '' s
#check image f s
#check f ⁻¹' u    -- type as \inv' and then hit space or tab
#check preimage f u

example : f '' s = {y | ∃ x, x ∈ s ∧ f x = y} := rfl
example : f ⁻¹' u = {x | f x ∈ u } := rfl

example : f ⁻¹' (u ∩ v) = f ⁻¹' u ∩ f ⁻¹' v :=
by { ext, refl }

example : f '' (s ∪ t) = f '' s ∪ f '' t :=
begin
  ext y, split,
  { rintros ⟨x, xs | xt, rfl⟩,
    { left, use [x, xs] },
    right, use [x, xt] },
  rintros (⟨x, xs, rfl⟩ | ⟨x, xt, rfl⟩),
  { use [x, or.inl xs] },
  use [x, or.inr xt]
end

example : s ⊆ f ⁻¹' (f '' s) :=
begin
  intros x xs,
  show f x ∈ f '' s,
  use [x, xs]
end

/-!
### Exercises
-/

example : f '' s ⊆ u ↔ s ⊆ f ⁻¹' u :=
begin
  split,
  { intros fsc x xs,
    apply fsc, use [x, xs], },
  { rintros sfu y ⟨x, xs, fx⟩,
    rw ←fx, apply sfu, from xs,},
end

end function_variables


/-
If P and Q are of type Prop, we think
of P → Q as a proposition in the form
of an implication.

Moreover, as we've seen, the way that
we give a proof of P → Q in the logic
of Lean is to produce a function that,
if given a proof of P returns a proof
of Q.

On the other hand, nothing in Lean will
prevent our using types such as nat and
bool as P and Q. In this case we'd say
that P → Q, or nat → bool, is a type: a
type of functions from P → Q, e.g., from
nat to bool.

EXERCISE: Give two or three examples
of interesting functions from nat to
bool. A program that takes any natural
number as an argument and always returns
a Boolean value as a result is a value
(a "function", or it would be better to
say, a lambda expression) of this type,
ℕ → bool.
-/

def zero_nat (n : ℕ) : nat := 0 -- explicit return type
#check zero_nat
#check zero_nat 5
#reduce zero_nat 5

def inc (n : ℕ) : ℕ := n + 1
#reduce inc 5

def zero_nat': ℕ → ℕ := λ n : nat, (0 : nat)
def zero_nat'': ℕ → ℕ := λ n, 0
def zero_nat''' := λ n : nat, 0

/-
EXERCISE: define a function, one_nat that takes any natural
number as an argument and always returns the natural number
1. Write it in traditional function definition notation and
using lambda expressions and type inference.
-/

def one_nat : ℕ → ℕ := λ n:  nat, 1
#check one_nat
#reduce one_nat 5

def identity_nat (n : ℕ) : nat := n --explicit return type
#check identity_nat
#check identity_nat 5
#reduce identity_nat 5

/-
EXERCISE: define the same function, call it identity_nat'
and write it using a lambda expression.
-/

def double (n : ℕ) := 2 * n -- return type inferred
#check double
#check double 3
#reduce double 3

def double' := λ n: ℕ , (2 * n : ℕ)

/-
EXERCISE: Write it using a lambda expression.
-/

def square (n: ℕ) := n * n -- return type inferred
#check square
#check square 3
#reduce square 3

def square' : ℤ → ℤ := λ n: ℤ, (n * n: ℤ)

#reduce square' (-3)
-- #reduce square (-3) -- Nope

/-
EXERCISE: Write it using a lambda expression.
-/
def isPositive (n: nat) : bool :=
    if n > 0 then tt else ff

#check isPositive
#check isPositive 0
#check isPositive 1
#check isPositive 2
#reduce isPositive 0
#reduce isPositive 1
#reduce isPositive 2

/-
EXERCISE: Write it using a lambda expression.
-/

/-
Here's a function that returns boolean true
(tt) if a given natural number n is greater
than or equal to zero and less than 2^32. It
is given using lambda notation. Write it with
a prime on its name using ordinary function
definition notation (as the functions above
are written).
-/

def uint32: ℕ → bool :=
    λ n,
        if n >= 0 ∧ n < 2^32 then tt else ff

#check uint32

/-
An equivalent function definition with
a type declaration using arrow notation,
and an explicit lambda expression as a
value.
-/
def positive' : nat → bool :=
    λ n : nat,
        (if n > 0 then tt else ff : bool)

#check positive'
#check λ n : nat, (if n > 0 then tt else ff : bool)
#check λ n, (if n > 0 then tt else ff)
#check λ n, n > 0

/-
Read the first line as saying positive'
is a proof of (a value of type) nat → bool.
There are many values of this type: they
are all the functions taking nat arguments
and returning bool results. The specific
value that we use here to "prove the type"
is the function that returns true if the
argument is strictly greater than zero,
otherwise is returns false.

Read the lambda expression as follows:
A function that takes an argument, n, of
type nat, and that returns the result of
evaluating the expression, involving n,
after the comma: here, true if n > 0,
and false otherwise.

Here's an equivalent expression using
type inference to skip declaring the
type of n.
-/

def positive'' : nat → bool :=
    λ n, if n > 0 then tt else ff

/-
Here we use type inference to skip
declaring the function type, while
still using a lambda expression for
the program.
-/

def positive''' :=
    λ n, if n > 0 then tt else ff

#check positive'''
#reduce positive''' 3
#reduce positive''' 0

theorem pos_is_pos : isPositive = positive''' := rfl

def my_pow (x: nat) (y: nat) := x^y

#eval my_pow 2 16

/-
EXERCISE: Give two or three examples
of interesting functions from nat to
nat. A program that takes any natural
number as an argument and always returns
a natural as a result is a value
(a "function", or it would be better to
say, a lambda expression) of this type,
ℕ → ℕ
-/

/-
We can also pass functions as arguments
to other functions!
-/

def compose (f: ℕ → ℕ) (g: ℕ → ℕ) (x: ℕ) : ℕ :=
  f (g x)

#check compose
#check compose square double
#reduce compose double double 3
#reduce compose square double 3
#reduce square (double 3)

def do_twice (f : ℕ → ℕ) (x: ℕ) : ℕ := f (f x)
#check do_twice
#reduce do_twice square 3

/-
Let's write this as a lambda expression and
see what we get.
-/

def do_twice' : (ℕ → ℕ) → ℕ → ℕ :=
    λ (f: ℕ → ℕ) (x: ℕ), f (f x)

/-
And that's a shorthand for this!
-/

def do_twice'' : (ℕ → ℕ) → ℕ → ℕ :=
    λ (f : ℕ → ℕ),
        λ (x : ℕ),
            f (f x)

def sub_do_twice (f: ℕ → ℕ) := λ (x : ℕ), f (f x)

#check sub_do_twice

def call_sub_do_twice := sub_do_twice (λ (x: ℕ), x + 1)
#check call_sub_do_twice
#reduce call_sub_do_twice 2

theorem dt_eq_dt : do_twice = do_twice'' := rfl

/-
Inception! Here we define a function that takes
as an argument a function that takes as an argument
a function (and a natural number)
-/

def do_twice''' (f: (ℕ → ℕ) → ℕ → ℕ) (x: (ℕ → ℕ)) : (ℕ → ℕ) := f (f x)

#check do_twice'''
#check do_twice''' do_twice
#reduce do_twice'''

/-
Use #eval instead of #reduce because #reduce will
have recursion problems unless we increase our
stack space.
-/

#eval (do_twice''' do_twice) square 2
#eval 2^16


/-
Author: Justin Mayer
self study of abstract algebra. Chapter two of dummit and foote.
-/

import tactic
import algebra.group.basic
import group_theory.subgroup.basic
import data.set.basic

--#print subgroup

variables {G: Type} [group G] (H : subgroup G)

instance : has_mem G (subgroup G) :=
{mem := λ m H, m ∈ H.carrier } --this allows for g ∈ H to be written. See formalising math by Kevin Buzzard. Groups, sheet 3

instance : has_coe (subgroup G) (set G) :=
{coe := λ H, H.carrier} --just removing the need to reference H.carrier since this doesn't feel very "mathy." Once again a trick demonstrated by Kevin Buzzard. Formalising mathematics, Groups, sheet 3.
--notice that these are instances of general functions definied in lean. We then define the mem function using the lambda calculus formalism.

@[simp] lemma mem_coe {g:G} : g ∈ (H : set G) ↔ g ∈ H :=
begin
refl,
end
--always for a rewrite where if something has type g ∈ (H : set G), then it can just be written as g ∈ H. Another useful math tactic.

@[ext] def ext' (H K : subgroup G) (h : ∀ g : G, g ∈ H ↔ g ∈ K) : H = K :=
begin
ext x,
exact h x,
end
--another extensionality theorem that asserts two subgroups are equal iff every element is contained in both subgroups.

--finally, remove all refereneces to the carrier function:

theorem one_mem : (1:G) ∈ H :=
begin
apply H.one_mem',
end

theorem mul_mem {x y : G}: x ∈ H → y ∈ H → x*y ∈ H :=
begin
apply H.mul_mem',
end

theorem inv_mem {x:G} : x ∈ H → x⁻¹ ∈ H :=
begin
apply H.inv_mem',
end

--A subgroup has now been defined in a way that is natural to the notation in dummit foote. The three important theorems to use are:
-- H.one_mem :  (1:G) ∈ H
-- H.mul_mem :  x ∈ H → y ∈ H → x * y ∈ H
-- H.inv_mem : x ∈ H → x⁻¹ ∈ H

--the subgroup criterion mentioned on page 46
lemma subgroup_crit {G : Type} [group G] (H : subgroup G) : (∃ g : G, g ∈ H)  ∧ ∀ x y : G, x ∈ H → y ∈ H → x*y⁻¹ ∈ H :=
begin
split,
have h: (1:G) ∈ H := H.one_mem,
use (1:G),
exact h,
intros x y hx hy,
have h2: y⁻¹ ∈ H := H.inv_mem hy,
exact H.mul_mem hx h2,
end

--define a structure mysubgroup based on the fact that it is a nonempty subset of G with a particular multiplication relation on the subset.
--does this structure end up being a subgroup? Let's find out!

structure mysubgroup (G:Type) [group G] :=
(carrier : set G)
(non_empty' : carrier.nonempty)
(mul_con' : ∀ x y: G, x ∈ carrier → y ∈ carrier → x*y⁻¹ ∈ carrier)

variables (P : mysubgroup G)

instance : has_mem G (mysubgroup G) :=
{mem := λ m P, m ∈ P.carrier }

--#print mysubgroup
lemma mysubgroup_one_mem (P:mysubgroup G) : (1:G) ∈ P.carrier :=
begin
have h: ∃ x:G, x ∈ P.carrier := P.non_empty',
cases h with x hx,
have h2: x*x⁻¹ ∈ P.carrier := P.mul_con' x x hx hx,
simp at h2,
exact h2,
end

lemma mysubgroup_inv_mem (P:mysubgroup G) : ∀ x:G, x∈P.carrier → x⁻¹∈P.carrier :=
begin
intros x hx,
have h1: (1:G) ∈ P.carrier := mysubgroup_one_mem P,
have h2: 1*x⁻¹ ∈ P.carrier := P.mul_con' (1:G) x h1 hx,
simp at h2,
exact h2,
end

lemma mysubgroup_mul_mem (P:mysubgroup G) : ∀ x y : G, x∈P.carrier → y∈P.carrier → x*y ∈ P.carrier :=
begin
intros x y hx hy,
have h1: y⁻¹ ∈ P.carrier := mysubgroup_inv_mem P y hy,
have h2: x*y⁻¹⁻¹ ∈ P.carrier := P.mul_con' x y⁻¹ hx h1,
simp at h2,
exact h2,
end

--based on these three lemmas the definition mysubgroup is clearly a group! I think to be completely precise
--it may also make sense to show that mysubgroup is an instance of subgroup. Do this with Harlan perhaps?


-- vim: ts=2 sw=0 sts=-1 et ai tw=70

import .dvd
import .induction

namespace hidden

open mynat

def fact: mynat → mynat
| 0        := 1
| (succ n) := (fact n) * (succ n)

variables {m n p k : mynat}

@[simp] theorem fact_zero: fact 0 = 1 := rfl
@[simp] theorem fact_succ: fact (succ n) = (fact n) * (succ n) := rfl

theorem fact_nzero:
fact m ≠ 0 :=
begin
  assume h,
  induction m with n hn,
    cases h, -- Magic?
  rw [fact_succ, mul_comm] at h,
  have := @mul_integral (succ n) (fact n),
  from hn (this succ_ne_zero h),
end

theorem fact_dvd_self {m : mynat}:
m ≠ 0 → m ∣ fact m :=
begin
  assume hneq0,
  cases m, {
    simp at hneq0,
    contradiction,
  }, {
    existsi (fact m),
    refl,
  },
end

theorem fact_dvd_succ (m : mynat):
fact m ∣ fact (succ m) :=
begin
  existsi (succ m),
  rw mul_comm,
  refl,
end

theorem fact_dvd_le {m n : mynat}:
m ≤ n → fact m ∣ fact n :=
begin
  assume hmlen,
  cases hmlen with k hk,
  revert n,
  induction k with k_n k_ih, {
    intro n,
    assume hmn,
    simp [hmn],
  }, {
    intro n,
    assume hnmsk,
    cases k_ih rfl with a ha,
    existsi a * succ (m + k_n),
    rw [hnmsk, add_succ, fact_succ, ha,
        mul_assoc, mul_assoc, mul_comm (fact m)],
  },
end

theorem fact_dvd_nlt {m n : mynat}:
m ≠ 0 → m ≤ n → m ∣ fact n :=
begin
  assume hmne0,
  induction n with k hk, {
    assume hmle0,
    simp at hmle0,
    have hmeq0 := le_zero hmle0,
    exfalso, contradiction,
  }, {
    assume hmlesucc,
    have hmself := fact_dvd_self hmne0,
    have hfmfsucc := fact_dvd_le hmlesucc,
    from dvd_trans hmself hfmfsucc,
  },
end

theorem fact_ndvd_lt:
∀ k: mynat, k ≠ 1 → k ≤ m → ¬(k ∣ (fact m) + 1) :=
begin
  assume n hneq1 hleqm hdiv,
  cases hdiv with k hk,
  have : n ∣ 1, {
    apply dvd_remainder (fact m) 1 (k * n) n, {
      have hnne0 : n ≠ 0, {
        assume hn0,
        rw [hn0, mul_zero, add_comm] at hk,
        have : (1: mynat) = (0: mynat), {
          apply add_integral hk,
        },
        cases this,
      },
      from fact_dvd_nlt hnne0 hleqm,
    }, {
      rw mul_comm,
      apply dvd_mul k,
      refl,
    },
    assumption,
  },
  from hneq1 (dvd_one this),
end

end hidden


/-
This file contains a formal computer proof of the linear convergence of gradient descent with
constant stepsize for strongly-convex functions. This file only covers
functions on scalar inputs. The file "GDvec_stronglyConvex.lean" extends this
to vectors. It is self-contained other than using properties of real and
natural numbers defined in mathlib. I define the properties of strong convexity
and Lipschitz continuous gradient here and do not rely on the
mathlib-defined gradient.

We closely follow the proof of
http://www.seas.ucla.edu/~vandenbe/236C/lectures/gradient.pdf
-/

import data.real.basic

-- The method under study
-- This is a polymorphic definition in that it works for any Type α that has
-- a subtraction operation and a multiplication operation.
-- This allows us to reason about behavior for the case α = ℝ but also
-- run it when α = float. We will only prove theorems for α = ℝ.
def grad_descent {α : Type}
            [has_sub α]
            [has_mul α]
            (η : α) (x0 : α) (gradf : α → α): (ℕ → α)
| 0      := x0
| (n+1)  := grad_descent(n) - η*gradf(grad_descent(n))

/- I take the below to be the **definition** of strongly convex + Lipschitz continuous
   functions. This property can be derived fairly easily.
-/
def is_strongconvex_LipGrad (gradf : ℝ → ℝ) (m L : ℝ): Prop :=
  ∀ (x y : ℝ),  (gradf(x)-gradf(y))*(x-y) ≥
               (m*L/(m+L))*(x-y)^2 + (1/(m+L))*(gradf(x)-gradf(y))^2

-- a one-step contraction lemma
lemma strongconvex_onestep (η m L : ℝ) (x xstar : ℝ) (gradf : ℝ → ℝ)
                        (hη_low : η ≥ 0)
                        (hη_up : η ≤ 2/(m+L))
                        (h_strongconv_lip : is_strongconvex_LipGrad gradf m L)
                        (hxstar : gradf xstar = 0)
                        :
      (x - η*gradf x - xstar)^2
      ≤ (1-2*η*m*L/(m+L))*(x - xstar)^2
   :=
begin
  have h0 : x - η * gradf x - xstar = x - xstar - η * gradf x
  := sub_right_comm x (η * gradf x) xstar,
  rw h0,
  have h1 : (x - xstar - η * gradf x) ^ 2 = (x-xstar)^2 - 2*(x-xstar)*η*gradf x + (η*gradf x)^2
  := by ring,
  rw h1,

  let h2 := h_strongconv_lip x xstar,
  rw hxstar at h2,
  rw sub_zero at h2,

  let h3 := mul_le_mul_of_nonneg_left h2 hη_low,

  have h_2 : (2:ℝ) ≥ 0 := by norm_num,

  let h4 := mul_le_mul_of_nonneg_left h3 h_2,

  have h5 : 2 * (η * (m * L / (m + L) * (x - xstar) ^ 2 + 1 / (m + L) * gradf x ^ 2))
            = 2 * η * m * L / (m + L) * (x - xstar) ^ 2 + 2 * η * 1 / (m + L) * gradf x ^ 2
            := by ring,
  rw h5 at h4,

  have h6: 2 * (η * (gradf x * (x - xstar))) = 2 * (x - xstar) * η * gradf x
        := by ring,
  rw ← h6,

  have h7 : 2 * η * m * L / (m + L) * (x - xstar) ^ 2
            + 2 * η * 1 / (m + L) * gradf x ^ 2 + (x - xstar) ^ 2
            ≤ 2 * (η * (gradf x * (x - xstar))) + (x - xstar) ^ 2
          := by linarith,

  have h8 :  2 * η * 1 / (m + L) * gradf x ^ 2 + (x - xstar) ^ 2
            - 2 * (η * (gradf x * (x - xstar)))
            ≤ (x - xstar) ^ 2 - 2 * η * m * L / (m + L) * (x - xstar) ^ 2
          := by linarith,

  have h8_1 : 2 * η * 1 / (m + L) =  η * 2 / (m + L) := by ring,
  rw h8_1 at h8,

  have h8_2 : η*η ≤ η*(2 / (m + L)) := by exact mul_le_mul_of_nonneg_left hη_up hη_low,

  have h8_3 : η*(2 / (m + L)) = 2 * η * 1 / (m + L) := by ring,

  rw h8_3 at h8_2,

  have h8_4 : gradf x ^ 2 ≥ 0 := sq_nonneg (gradf x),

  have h8_5 : η*η*gradf x ^ 2 ≤ 2 * η * 1 / (m + L)*gradf x ^ 2 := by exact mul_mono_nonneg h8_4 h8_2,

  have h9 : η*η*gradf x ^ 2 + (x - xstar) ^ 2
            - 2 * (η * (gradf x * (x - xstar)))
            ≤ (x - xstar) ^ 2 - 2 * η * m * L / (m + L) * (x - xstar) ^ 2
          := by linarith,

  have h9_1 : (x - xstar) ^ 2 - 2 * η * m * L / (m + L) * (x - xstar) ^ 2
              = (1 - 2 * η * m * L / (m + L))*(x - xstar) ^ 2
            := by ring,
  rw h9_1 at h9,

  have h9_2 : η * η * gradf x ^ 2 = (η * gradf x) ^ 2 := by ring,

  rw h9_2 at h9,

  linarith,

end

-- The main theorem
-- note that we do not say that xstar is the minimizer, just that its gradient is zero.
-- Proving that all minimizers have zero gradients would be extra work.

theorem strongconvex_grad_descent_conv_rate (n : ℕ) (η m L : ℝ) (x0 xstar : ℝ) (gradf : ℝ → ℝ)
                        (hη_low : η ≥ 0)
                        (hη_up : η ≤ 2/(m+L))
                        (hm : m ≥ 0)
                        (hL : L ≥ 0)
                        (hmL : m + L ≠ 0)
                        (h_strongconv_lip : is_strongconvex_LipGrad gradf m L)
                        (hxstar : gradf xstar = 0)
                        :
      ((grad_descent η x0 gradf n) - xstar)^2
      ≤ ((1-2*η*m*L/(m+L))^n)*(x0 - xstar)^2
   :=
begin
induction n with k hk,
have h0 : grad_descent η x0 gradf 0 = x0 := by refl,
rw h0,
rw pow_zero,
rw one_mul,

have h1 : grad_descent η x0 gradf k.succ  =
          grad_descent η x0 gradf k - η*gradf(grad_descent η x0 gradf k) := by refl,
rw h1,

have h2 := strongconvex_onestep η m L (grad_descent η x0 gradf k) xstar gradf hη_low hη_up h_strongconv_lip hxstar,

rw pow_succ _ k,

have h2_1 : L*η ≤ L*(2 / (m+L)) := mul_le_mul_of_nonneg_left hη_up hL,

have h2_2 : m*(L*η) ≤ m*(L*(2 / (m+L))) := mul_le_mul_of_nonneg_left h2_1 hm,

have h2_3 : (2:ℝ)≥ 0 := by norm_num,

have h2_4 : 2*(m*(L*η)) ≤ 2*(m*(L*(2 / (m+L)))) := mul_le_mul_of_nonneg_left h2_2 h2_3,

have h2_5 : (m+L) ≥ 0 := by linarith,

have h2_6 : 2*(m*(L*η))/(m+L) ≤ 2*(m*(L*(2 / (m+L))))/(m+L) := div_le_div_of_le h2_5 h2_4,

have h2_7 : 2*(m*(L*η))/(m+L) = 2* m* L*η /(m+L) := by ring,

rw h2_7 at h2_6,

have h2_8 : 2*(m*(L*(2 / (m+L))))/(m+L)  = 2*m*L* 2 /(m+L)/(m+L) := by ring,

rw h2_8 at h2_6,

have h2_9 : 2*m*L* 2 /(m+L)/(m+L) = 4*m*L /(m+L)/(m+L) := by ring,

rw h2_9 at h2_6,

have h2_10 : 4*m*L /(m+L)/(m+L) = 4*m*L /((m+L)*(m+L)) := div_div_eq_div_mul (4 * m * L) (m + L) (m + L),

rw h2_10 at h2_6,

have h2_11 : 4*m*L /((m+L)*(m+L)) = 4*m*L /((m+L)^2),
  rw ← pow_two,
rw h2_11 at h2_6,

have h3 : 1 - 2 * m * L * η / (m + L) ≥ 1 -  4 * m * L / (m + L) ^ 2 := by linarith,

have h3_1 : (m + L) ^ 2 ≠ 0 := pow_ne_zero 2 hmL,
have h3_2 : 1 = (m + L) ^ 2 / (m + L) ^ 2 := by exact (div_self h3_1).symm,

have h3_3 : 1 -  4 * m * L / (m + L) ^ 2 = (m + L) ^ 2 / (m + L) ^ 2 -  4 * m * L / (m + L) ^ 2
    := by linarith,

rw h3_3 at h3,

have h3_4 : (m + L) ^ 2 / (m + L) ^ 2 -  4 * m * L / (m + L) ^ 2
          = ((m + L) ^ 2 -  4 * m * L) / (m + L) ^ 2
          := div_sub_div_same ((m + L) ^ 2) (4 * m * L) ((m + L) ^ 2),

rw h3_4 at h3,

have h3_5 : (m + L) ^ 2 -  4 * m * L = m^2 + 2*m*L+L^2 - 4*m*L := by ring,

rw h3_5 at h3,

have h3_6 : m^2 + 2*m*L+L^2 - 4*m*L  = m^2 - 2*m*L + L^2 := by ring,

rw h3_6 at h3,

have h3_7 : m^2 - 2*m*L + L^2 = (m-L)^2 := by ring,

rw h3_7 at h3,

have h3_8 : (m - L) ^ 2 / (m + L) ^ 2 = ((m - L) / (m + L) )^ 2 := by exact (div_pow (m - L) (m + L) 2).symm,

rw h3_8 at h3,

have h3_9 : ((m - L) / (m + L) )^ 2 ≥ 0 := sq_nonneg ((m - L) / (m + L)),

have h4 : 1 - 2 * m * L * η / (m + L) ≥ 0 := by linarith,
have h4_1 : 1 - 2 * m * L * η / (m + L)
          = 1 - 2 * η * m * L / (m + L) := by ring,
rw h4_1 at h4,

have h5 : (1 - 2 * η * m * L / (m + L)) * (grad_descent η x0 gradf k - xstar) ^ 2
    ≤ (1 - 2 * η * m * L / (m + L))*((1 - 2 * η * m * L / (m + L)) ^ k * (x0 - xstar) ^ 2)
    := by exact mul_le_mul_of_nonneg_left hk h4,

have h5_1 : (1 - 2 * η * m * L / (m + L))*((1 - 2 * η * m * L / (m + L)) ^ k * (x0 - xstar) ^ 2)
      =
      (1 - 2 * η * m * L / (m + L))*(1 - 2 * η * m * L / (m + L)) ^ k * (x0 - xstar) ^ 2
      := by ring,
rw h5_1 at h5,

linarith,

end


/-
  a type called MyNat
  a term 0 : MyNat, which is the number zero
  a function succ : MyNat -> MyNat, which is +1
  Use numerical notation, 0, 1, 2, ...
  Use + and * to represent addition and multiplication
  lemmas we already proved (add_zero, add_succ, and zero_succ_add)
  tactics rewrite and rfl
-/

import MyNat
import MyNat.lemma
import MyNat.tutorial_world

open MyNat

lemma zero_add (n : ℕ) : zero + n = n :=
  by
  induction n with
  | zero => rewrite [add_zero] rfl
  | succ n' ih => rewrite [add_succ, ih] rfl

lemma add_assoc (a b c : ℕ) :
  (a + b) + c = a + (b + c) :=
  by
  induction c with
  | zero => rewrite [add_zero, add_zero] rfl
  | succ c' ih => rewrite [add_succ, add_succ, add_succ, ih] rfl

lemma succ_add (a b : ℕ) : succ a + b = succ (a + b) :=
  by induction b with
  | zero => rewrite [add_zero, add_zero] rfl
  | succ b' ih => rewrite [add_succ a b', <- ih, add_succ] rfl

lemma add_comm (a b : ℕ) : a + b = b + a :=
  by induction b with
  | zero => rewrite [add_zero, zero_add] rfl
  | succ b' ih => rewrite [add_succ, succ_add, ih] rfl

lemma one_eq_succ_zero : 1 = succ zero := by rfl
lemma two_eq_succ_one :  2 = succ 1    := by rfl

lemma succ_eq_add_one (n : ℕ) : succ n = n + 1 :=
  by induction n with
  | zero => rewrite [one_eq_succ_zero, zero_add] rfl
  | succ n' ih => rewrite [ih, <- succ_add, ih] rfl

lemma add_right_comm (a b c : ℕ) : a + b + c = a + c + b :=
  by rewrite [add_assoc, add_comm b c, add_assoc a c b] rfl


import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level5 -- one_eq_succ_zero, succ_eq_add_one
namespace MyNat
open MyNat

/-!
# Multiplication World

## Level 3: `one_mul`

These proofs from addition world might be useful here:

* `one_eq_succ_zero : 1 = succ 0`
* `succ_eq_add_one a : succ a = a + 1`

We just proved `mul_one`, now let's prove `one_mul`. Then we will have proved, in fancy terms, that
1 is a "left and right identity" for multiplication (just like we showed that 0 is a left and right
identity for addition with `add_zero` and `zero_add`).

## Lemma

For any natural number `m`, we have `1 * m = m.`
-/
lemma one_mul (m : MyNat) : 1 * m = m := by
  induction m with
  | zero =>
    rw [zero_is_0]
    rw [mul_zero]
  | succ m ih =>
    rw [mul_succ]
    rw [ih]
    rw [succ_eq_add_one]

/-!
Next up is [Multiplication Level 4](./Level4.lean.md).
-/


-- Es_menor_o_igual_que_suma_con_no_negativo.lean
-- Es menor o igual que suma con no negativo
-- José A. Alonso Jiménez
-- Sevilla, 14 de agosto de 2020
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar si a y b son números naturales y b es no
-- negativo, entonces a ≤ a + b
-- ----------------------------------------------------------------------

import data.real.basic
variables {a b : ℝ}

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

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : by rw add_zero
     ... ≤ a + b : by exact add_le_add_left hb a,
end

-- Comentario: Se ha usado el lema
-- + add_le_add_left : a ≤ b → ∀ (c : ℝ), c + a ≤ c + b

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

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : (add_zero a).symm
     ... ≤ a + b : add_le_add_left hb a,
end

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

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
begin
  calc a = a + 0 : by ring
     ... ≤ a + b : add_le_add_left hb a,
end

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

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by simp [hb]

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

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by linarith

-- 6ª demostración
-- ===============

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
by finish

-- 7ª demostración
-- ===============

example
  (hb : 0 ≤ b)
  : a ≤ a + b :=
le_add_of_nonneg_right hb

-- Comentario: Se usa el lema
-- + le_add_of_nonneg_right : 0 ≤ b → a ≤ a + b


/-
In lean we represent: (1) propositions as
types and (2) predicates as parameterized
propositions.

A parameterized type gives rise to a whole
family of proposition, once for each value
of each parameter.

We can think of one-parameter predicates
as specifying *properties* of objects. As
an example, as defined, is_even : ℕ → Prop
expresses the property of a natural number
(an argument value) "being even". We define
the constructors to ensure that there is a
proof of (is_even n) if and only if (n : ℕ)
is (a term that represents) an even number.

One-parameter predicates thus also specify
*sets* of objects: namely all and only those
values of a given argument type that have the
specified property as shown by the existence
of a proof that such a value "satisfies the
predicate" (makes the resulting proposition
true).

The is_even predicate thus specifies the
set of all even natural numbers, which we
can write as {0, 2, 4, 6, ...} or, better,
as { n : ℕ | is_even n }.

We can think of two-parameter predicates as
specifying binary relations, that is, sets of
*pairs* of argument values that make a given
proposition true.

The most commonplace example of a two-argument
predicate is equals. We can write (_ = _) to
make it clear that equals takes two values and
yields a proposition that the first equals the
second. For example, if a and b are of type ℕ,
then (a = b) is a proposition that has a proof
if and only if the two terms really are equal
(when reduced). So, for example, 4 = 2 + 2 is
true, there is a proof of it, because reducing
2 + 2 to 4 reduces the whole proposition to
4 = 4, and there is a proof of this proposition.

The proposition, a = b, in Lean is just a nice
shorthand notation for (eq a b). The two-place
predicate, eq, implements the equality relation.
It's polymorphic: it takes a *type parameter*, α,
implicitly. It then explicitly takes two values,
a and b, of type α, and yields the proposition,
a = b (which is of course itself a type).

The eq "type builder" (taking α and two values,
a and b of type α) and yielding the proposition
(a type), a = b, provides a single constructor
with which to build proofs. It's called refl,
and it establishes the "reflexive" property of
the eq relation, by taking *any* value, a : α,
for any type, α, and by returning a proof of
(eq a a), that is, of a = a. So, by applying
eq.refl to any value, a, of any type, we get
a proof of (a = a), and those are the *only*
proofs of equality that can be constructed.
There's thus no way to prove that 0 = 1, for
example, because those terms don't reduce to
the same values, and we can only use eq.refl
to construct proofs that two terms are equal
if they really do reduce to exactly the same
value.

Finally, we will represent some, but no all,
of the logical connetives, including "and",
"or", and "iff", as polymorphic propositions.
Whereas a predicate, such as eq, will take
values of arbitrary types as arguments (e.g.,
eq can take values of type ℕ), connectives
are like polymorphic types: they take types
as arguments, but in this case the types are
of type Prop.

Consider "and" for example. If P and Q are
any two pre-existing propositions (types of
type Prop), then we can form the proposition,
P ∧ Q, and it, too, will be a proposition (a
type of type, Prop). The type of "and" is thus
Prop → Prop → Prop: it takes two propositions
(types of type Prop) as arguments and yields
a new proposition (a new type of type Prop).

To enforce the logical meaning of "and", we
define the "and" type builder to have just
one constructor, which we call "intro", that
if applied to a proof, p, of P and to a proof,
q, of Q yields a term, (and.intro p q), that
is axiomatically accepted as being a "proof"
of (and P Q), which we write using an infix
notation as (P ∧ Q).

Finally, we note that if we're given a term,
(and.intro p q), of type (P ∧ Q), that is, if
we're given a proof of (P ∧ Q), we can get a
proof of P from it, by "destructuring" the
term using pattern matching. For example, we
can obtain "p" (the proof of P "contained
inside" the proof of P ∧ Q) with the following
rule: | (and.intro p _) := p. This is how we
implement the two "elimination rules" for and.
-/

/-
Here's a statement of the proposition that
2 + 2 = 4 and a proof of it.
-/

lemma proof_of_two_plus_two_eq_four : 2 + 2 = 4 :=
    eq.refl 4

/-
1. State and prove the proposition that the
string,"Lean!", appended to the string, "Hello, "
is equal to the string, "Hello, Lean!". You may
and should use the Lean library-provided string
append operator, ++, as an infix notation for
append.
-/

-- string append example
#eval "Hi " ++ "There!"

-- fill in the blanks
lemma proof1 : _ = _

/-
2.  Sometimes we want to state and prove a
proposition without binding a name to the proof.
For that, instead of using def, theorem, or
lemma, we can use "example." Here's an example.
-/

example : 1 = 1 := _    -- Fill blank with proof.

/-
Note that we can use example for ordinary types
as well.
-/

example : ℕ := 5

/-
2. State and prove the proposition that 3 * 4 = 12,
using "example."
-/

-- Answer Here

/-
3. Use example to formally state and prove the
proposition that 1 = 1 ∧ 0 = 0.
-/

-- Here

/-
4. Complete the following program to show that
if P and Q are arbitrary propositions, and *if*
you're given a proof of P and Q, then you can
obtain and return a proof of P.

Hint: In Lean, and.intro is the one introduction
(proof creating) rule for and, and and.elim_left
and and.elim_right are the two elimination rules.
They are functions. Use one of them!
-/

theorem P_and_Q_imp_P {P Q : Prop} (pq : P ∧ Q) : P :=
    _


/-
Copyright (c) 2020. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jalex Stark, Yury Kudryashov
-/
import algebra.big_operators.basic
import data.real.basic

/-!
# Sum of the Reciprocals of the Triangular Numbers

This file proves Theorem 42 from the [100 Theorems List](https://www.cs.ru.nl/~freek/100/).

We interpret “triangular numbers” as naturals of the form $\frac{k(k+1)}{2}$ for natural `k`.
We prove that the sum of the reciprocals of the first `n` triangular numbers is $2 - \frac2n$.

## Tags

discrete_sum
-/

open_locale big_operators
open finset

/-- **Sum of the Reciprocals of the Triangular Numbers** -/
lemma inverse_triangle_sum :
  ∀ n, ∑ k in range n, (2 : ℚ) / (k * (k + 1)) = if n = 0 then 0 else 2 - (2 : ℚ) / n :=
begin
  refine sum_range_induction _ _ (if_pos rfl) _,
  rintro (_|n), { rw [if_neg, if_pos]; norm_num },
  simp_rw [if_neg (nat.succ_ne_zero _), nat.succ_eq_add_one],
  have A : (n + 1 + 1 : ℚ) ≠ 0, by { norm_cast, norm_num },
  push_cast,
  field_simp [nat.cast_add_one_ne_zero],
  ring
end


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

import Mathlib.Algebra.Divisibility.Basic
import Mathlib.Tactic.Ring
import Mathlib.Data.Nat.Prime

/-!
# IMO 1959 Q1
Prove that the fraction `(21n+4)/(14n+3)` is irreducible for every
natural number `n`.

Since Lean doesn't have a concept of "irreducible fractions" per se,
we just formalize this as saying the numerator and denominator are
relatively prime.
-/

lemma calculation
    (n k : ℕ)
    (h1 : k ∣ 21 * n + 4)
    (h2 : k ∣ 14 * n + 3) :
    k ∣ 1 := by
  have h3 : k ∣ 2 * (21 * n + 4) := h1.mul_left 2
  have h4 : k ∣ 3 * (14 * n + 3) := h2.mul_left 3
  have h5 : 3 * (14 * n + 3) = 2 * (21 * n + 4) + 1 := by ring
  exact (Nat.dvd_add_right h3).mp (h5 ▸ h4)

theorem imo1959_q1 : ∀ n : ℕ, Nat.coprime (21 * n + 4) (14 * n + 3) :=
fun n => Nat.coprime_of_dvd' <| λ k _ h1 h2 => calculation n k h1 h2


/-
Copyright (c) 2022 Oliver Nash. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Oliver Nash
-/
import data.set.pointwise.smul
import algebra.hom.iterate
import dynamics.fixed_points.basic

/-!
# Results about pointwise operations on sets with iteration.

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

open_locale pointwise
open set function

/-- Let `n : ℤ` and `s` a subset of a commutative group `G` that is invariant under preimage for
the map `x ↦ x^n`. Then `s` is invariant under the pointwise action of the subgroup of elements
`g : G` such that `g^(n^j) = 1` for some `j : ℕ`. (This subgroup is called the Prüfer subgroup when
 `G` is the `circle` and `n` is prime.) -/
@[to_additive "Let `n : ℤ` and `s` a subset of an additive commutative group `G` that is invariant
under preimage for the map `x ↦ n • x`. Then `s` is invariant under the pointwise action of the
additive subgroup of elements `g : G` such that `(n^j) • g = 0` for some `j : ℕ`. (This additive
subgroup is called the Prüfer subgroup when `G` is the `add_circle` and `n` is prime.)"]
lemma smul_eq_self_of_preimage_zpow_eq_self {G : Type*} [comm_group G]
  {n : ℤ} {s : set G} (hs : (λ x, x^n)⁻¹' s = s)
  {g : G} {j : ℕ} (hg : g^(n^j) = 1) : g • s = s :=
begin
  suffices : ∀ {g' : G} (hg' : g'^(n^j) = 1), g' • s ⊆ s,
  { refine le_antisymm (this hg) _,
    conv_lhs { rw ← smul_inv_smul g s, },
    replace hg : (g⁻¹)^(n^j) = 1, { rw [inv_zpow, hg, inv_one], },
    simpa only [le_eq_subset, set_smul_subset_set_smul_iff] using this hg, },
  rw (is_fixed_pt.preimage_iterate hs j : ((zpow_group_hom n)^[j])⁻¹' s = s).symm,
  rintros g' hg' - ⟨y, hy, rfl⟩,
  change ((zpow_group_hom n)^[j]) (g' * y) ∈ s,
  replace hg' : ((zpow_group_hom n)^[j]) g' = 1, { simpa [zpow_group_hom], },
  rwa [monoid_hom.iterate_map_mul, hg', one_mul],
end


/-
# Example: Sorting

In this example we specify a function that sorts a list into ascending order.
-/
import LeanSpec.lib.Util

/-
Informally, a sort function takes a list as argument, and returns a list that
is an ordered permutation of its argument. A predicate that specifies whether
a list is ordered might be:
-/
def Ordered₁ [LT α] : List α → Prop
 | [] | [_] => True
 | a::b::as => a < b ∧ Ordered₁ (b::as)

/-
Empty and singleton lists are trivially ordered. A list of two or more items is ordered
if the first item precedes the second item, and the tail is ordered. The type of items in
the list must admit an order relation, stated in the definition by `[LT α]`: the type `α`
is an instance of the type class `LT`.

First let's try a naive definition. Two lists are permutations of each other if they
contain the same items, irrespective of order:
-/
def Permutation₁ (as bs : List α) :=
  ∀ a : α, a ∈ as ↔ a ∈ bs

/-
With these definitions we can now specify the sorting function:
-/
def Sort₁ [LT α] (as : List α) :=
  { sas : List α // Ordered₁ sas ∧ Permutation₁ as sas }

/-
`Sort₁` takes as argument a list of items with an order relation,
and returns a list that is an ordered permutation of its input.

There is, unfortunately, a problem with the specification of `Sort₁`: it is not
possible to derive a program that meets the specification. Do you see why?

Consider this clause from the definition of `Ordered₁`:
- `a::b::as => a < b ∧ Ordered₁ (b::as)`.

What happens when the input list is `[2, 2]`? The definition of `Ordered₁` states that two
consecutive items in the list must be related by `<`, but `2 ≮ 2`. The specification does
not accommodate lists with duplicate entries.

Just as there is no guarantee a postulated mathematical theorem is provable,
there is no guarantee a program specification is implementable (they are after
all different expressions of the same concept).

Let's try again. First note the function `numOccurs` (defined in [Util](lib/Util.md)) that counts
the number of occurrences of an item in a list:
-/
#check List.numOccurs

/-
The `Ordered` predicate is largely as before except the comparison operator
is `≤` (with corresponding type class `LE`) rather than `<`:
-/
def Ordered₂ [LE α] : List α → Prop
 | [] | [_] => True
 | a::b::as => a ≤ b ∧ Ordered₂ (b::as)

/-
The `Permutation` predicate cannot simply check for membership, it must ensure
the number of occurrences of any item in one list is the same as the number of
occurrences in the other list.
-/
def Permutation₂ [BEq α] (as bs : List α) :=
  ∀ a : α, as.numOccurs a = bs.numOccurs a

/-
`Sort₂` employs the new `Ordered₂` and `Permutation₂` predicates, but otherwise is
unchanged:
-/
def Sort₂ [BEq α] [LE α] (as : List α) :=
  { sas : List α // Ordered₂ sas ∧ Permutation₂ as sas }

/-
In fact `LE` is insufficient. It only guarantees there is a binary predicate on the type, not
that it is the predicate we normally think of as _less than or equal_. The predicate must be
a partial order, but we won't go into the details here.

The predicate `Ordered₂` is typical of a traditional approach, perhaps with the
exception the result is a `Prop` rather than a `Bool`. Dependently typed
languages such as Lean offer an alternative approach; namely that of _Inductive Predicates_.
An inductive predicate is simply an inductive definition of a proposition as opposed to
an inductive definition of a type (list, tree, etc), and a member of the
inductive proposition is a proof of the proposition. For `Ordered` we could have:
-/
inductive Ordered₃ [LE α] : List α → Prop
  | empty       : Ordered₃ []
  | singleton a : Ordered₃ [a]
  | twoplus a b as (hab : a ≤ b) (hbas : Ordered₃ (b::as)) :
                  Ordered₃ (a::b::as)

/-
The three clauses state:
- `empty` is a proof that the empty list is ordered;
- `singleton a` is a proof that the singleton list containing `a` is ordered;
- `twoplus a b as hab hbas` is a proof that the list `a::b::as` is ordered,
given that `hab` is evidence `a ≤ b` and `hbas` is a proof that `b::as` is ordered.

Note the correspondence between the clauses in the inductive definition of `Ordered₃`
and the recursive definition of `Ordered₂`.

As with programming and proof, there is no single way, or best way, to specify a
program. The activity of specification is an iterative process, as the specification
is refined, and clearer ways of expressing concepts are discovered.

## Exercises

- Specify a function that, given a natural number, returns the prime factors of the number.
Use the `Prime` property from the last exercise.

- Using `Permutation₂`, specify a function that returns all permutations of an input list.

- The Lean standard library, `std4`, defines an inductive proposition `List.Chain'`. Specify
`Sort` using `Chain'`.
-/
#check List.Chain'


import .love11_logical_foundations_of_mathematics_demo

/- # LoVe Exercise 11: Logical Foundations of Mathematics -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1: Vectors as Subtypes

Recall the definition of vectors from the demo: -/

#check vector

/- The following function adds two lists of integers elementwise. If one
function is longer than the other, the tail of the longer function is
truncated. -/

def list.add : list ℤ → list ℤ → list ℤ
| []        []        := []
| (x :: xs) (y :: ys) := (x + y) :: list.add xs ys
| []        (y :: ys) := []
| (x :: xs) []        := []

/- 1.1. Show that if the lists have the same length, the resulting list also
has that length. -/

lemma list.length_add :
  ∀(xs : list ℤ) (ys : list ℤ) (h : list.length xs = list.length ys),
    list.length (list.add xs ys) = list.length xs
| []        []        :=
  sorry
| (x :: xs) (y :: ys) :=
  sorry
| []        (y :: ys) :=
  sorry
| (x :: xs) []        :=
  sorry

/- 1.2. Define componentwise addition on vectors using `list.add` and
`list.length_add`. -/

def vector.add {n : ℕ} : vector ℤ n → vector ℤ n → vector ℤ n :=
sorry

/- 1.3. Show that `list.add` and `vector.add` are commutative. -/

lemma list.add.comm :
  ∀(xs : list ℤ) (ys : list ℤ), list.add xs ys = list.add ys xs :=
sorry

lemma vector.add.comm {n : ℕ} (x y : vector ℤ n) :
  vector.add x y = vector.add y x :=
sorry

/- ## Question 2: Integers as Quotients

Recall the construction of integers from the lecture, not to be confused with
Lean's predefined type `int` (= `ℤ`): -/

#check int.rel
#check int.rel_iff
#check int

/- 2.1. Define negation on these integers. -/

def int.neg : int → int :=
sorry

/- 2.2. Prove the following lemmas about negation. -/

lemma int.neg_eq (p n : ℕ) :
  int.neg ⟦(p, n)⟧ = ⟦(n, p)⟧ :=
sorry

lemma int.neg_neg (a : int) :
  int.neg (int.neg a) = a :=
sorry

end LoVe