/-
Copyright (c) 2022 Eric Rodriguez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Rodriguez
-/
import algebra.field.ulift
import data.mv_polynomial.cardinal
import data.nat.factorization.prime_pow
import data.rat.denumerable
import field_theory.finite.galois_field
import logic.equiv.transfer_instance
import ring_theory.localization.cardinality
import set_theory.cardinal.divisibility

/-!
# Cardinality of Fields

In this file we show all the possible cardinalities of fields. All infinite cardinals can harbour
a field structure, and so can all types with prime power cardinalities, and this is sharp.

## Main statements

* `fintype.nonempty_field_iff`: A `fintype` can be given a field structure iff its cardinality is a
  prime power.
* `infinite.nonempty_field` : Any infinite type can be endowed a field structure.
* `field.nonempty_iff` : There is a field structure on type iff its cardinality is a prime power.

-/

local notation `‖` x `‖` := fintype.card x

open_locale cardinal non_zero_divisors

universe u

/-- A finite field has prime power cardinality. -/
lemma fintype.is_prime_pow_card_of_field {α} [fintype α] [field α] : is_prime_pow (‖α‖) :=
begin
  casesI char_p.exists α with p _,
  haveI hp := fact.mk (char_p.char_is_prime α p),
  let b := is_noetherian.finset_basis (zmod p) α,
  rw [module.card_fintype b, zmod.card, is_prime_pow_pow_iff],
  { exact hp.1.is_prime_pow },
  rw ←finite_dimensional.finrank_eq_card_basis b,
  exact finite_dimensional.finrank_pos.ne'
end

/-- A `fintype` can be given a field structure iff its cardinality is a prime power. -/
lemma fintype.nonempty_field_iff {α} [fintype α] : nonempty (field α) ↔ is_prime_pow (‖α‖) :=
begin
  refine ⟨λ ⟨h⟩, by exactI fintype.is_prime_pow_card_of_field, _⟩,
  rintros ⟨p, n, hp, hn, hα⟩,
  haveI := fact.mk hp.nat_prime,
  exact ⟨(fintype.equiv_of_card_eq ((galois_field.card p n hn.ne').trans hα)).symm.field⟩,
end

lemma fintype.not_is_field_of_card_not_prime_pow {α} [fintype α] [ring α] :
  ¬ is_prime_pow (‖α‖) → ¬ is_field α :=
mt $ λ h, fintype.nonempty_field_iff.mp ⟨h.to_field⟩

/-- Any infinite type can be endowed a field structure. -/
lemma infinite.nonempty_field {α : Type u} [infinite α] : nonempty (field α) :=
begin
  letI K := fraction_ring (mv_polynomial α $ ulift.{u} ℚ),
  suffices : #α = #K,
  { obtain ⟨e⟩ := cardinal.eq.1 this,
    exact ⟨e.field⟩ },
  rw ←is_localization.card (mv_polynomial α $ ulift.{u} ℚ)⁰ K le_rfl,
  apply le_antisymm,
  { refine ⟨⟨λ a, mv_polynomial.monomial (finsupp.single a 1) (1 : ulift.{u} ℚ), λ x y h, _⟩⟩,
    simpa [mv_polynomial.monomial_eq_monomial_iff, finsupp.single_eq_single_iff] using h },
  { simp }
end

/-- There is a field structure on type if and only if its cardinality is a prime power. -/
lemma field.nonempty_iff {α : Type u} : nonempty (field α) ↔ is_prime_pow (#α) :=
begin
  rw cardinal.is_prime_pow_iff,
  casesI fintype_or_infinite α with h h,
  { simpa only [cardinal.mk_fintype, nat.cast_inj, exists_eq_left',
        (cardinal.nat_lt_aleph_0 _).not_le, false_or]
      using fintype.nonempty_field_iff },
  { simpa only [← cardinal.infinite_iff, h, true_or, iff_true]
      using infinite.nonempty_field },
end


/-
The goal of this file is to prove that a linear operator
T : ℂ^n → ℂ^n is self-adjoint if and only if ⟪T v, v⟫_ℂ is
real for all v ∈ ℂ^n.

-- Checked over by:
-- Hans

-/

import .lemmas.ladr_7_lem

variable {n : ℕ}

localized "postfix `†`:1000 := linear_map.adjoint" in src

open_locale big_operators complex_conjugate matrix

variables (T : Lℂ^n) (u w : ℂ^n)

local notation `is_sa` := inner_product_space.is_self_adjoint

lemma is_self_adjoint_iff_eq_adjoint : is_sa T ↔ T = T† :=
  by rw [linear_map.eq_adjoint_iff, inner_product_space.is_self_adjoint]

/-
Linearity lemmas
-/
lemma inner_map_add_add : ⟪ T (u + w), u + w ⟫_ℂ = ⟪T u, u⟫_ℂ + ⟪T w, u⟫_ℂ + ⟪T u, w⟫_ℂ + ⟪T w, w⟫_ℂ :=
  by {rw [map_add, inner_add_left, inner_add_right, inner_add_right], ring}

lemma inner_map_sub_sub : ⟪ T (u - w), u - w ⟫_ℂ = ⟪T u, u⟫_ℂ - ⟪T w, u⟫_ℂ - ⟪T u, w⟫_ℂ + ⟪T w, w⟫_ℂ :=
  by {rw [map_sub,inner_sub_left, inner_sub_right, inner_sub_right], ring}

/-
Polarization identity
-/
lemma inner_map_polarization :
  4 * ⟪ T w, u ⟫_ℂ = ⟪T (u + w) , u + w⟫_ℂ - ⟪T (u - w) , u - w⟫_ℂ + I * ⟪T (u + I • w) , u + I • w⟫_ℂ - I * ⟪T (u - I • w), u - I • w ⟫_ℂ :=
begin
  symmetry,
  iterate {rw inner_map_add_add},
  iterate {rw inner_map_sub_sub},
  rw [linear_map.map_smul, inner_smul_left, inner_smul_right],
  ring_nf,
  rw complex.conj_I,
  ring_nf,
  rw complex.I_sq,
  ring_nf,
end

/-
If ⟪T u, u⟫_ℂ = 0 for all u ∈ ℂ^n, then ⟪T u, w⟫_ℂ = 0 for all u, w ∈ ℂ^n.
-/
lemma inner_map_eq_zero (h : ∀ (u : ℂ^n), ⟪T u, u⟫_ℂ = 0) :
  ∀ (u w : ℂ^n), ⟪T u, w ⟫_ℂ = 0 :=
begin
  have : ∀ u w : ℂ^n, 4 * ⟪T u, w ⟫_ℂ = 0 :=
  begin
    intros u w,
    rw inner_map_polarization,
    iterate {rw h},
    ring,
  end,
  intros u w,
  specialize this u w,
  simp only [false_or, bit0_eq_zero, one_ne_zero, mul_eq_zero] at this,
  exact this,
end

/-
If ⟪T u, u⟫_ℂ = 0 for all u ∈ ℂ^n, then ⟪T u, w⟫_ℂ = 0 for all u, w ∈ ℂ^n.
-/
lemma inner_map_self_eq_zero (h : ∀ u : ℂ^n, ⟪T u, u⟫_ℂ = 0) :
  T = 0 :=
begin
  have : ∀ u w : ℂ^n, ⟪T u, w ⟫_ℂ = 0 := inner_map_eq_zero T h,
  apply linear_map.ext,
  intro x,
  specialize this x (T x),
  rw inner_self_eq_zero at this,
  rw linear_map.zero_apply,
  exact this,
end

/-
If T is self-adjoint, then ⟪T v, v⟫_ℂ is real for all v ∈ ℂ
-/
lemma self_adjoint_real_inner (h : is_sa T) :
  ∀ v : ℂ^n, conj ⟪T v, v⟫_ℂ = ⟪T v, v⟫_ℂ :=
begin
  intro v,
  apply inner_product_space.is_self_adjoint.conj_inner_sym,
  exact h,
end

/-
If ⟪T v, v⟫_ℂ is real for all v ∈ ℂ^n, then T is self-adjoint.
-/
lemma real_inner_self_adjoint (h : ∀ v : ℂ^n, conj ⟪T v, v⟫_ℂ = ⟪T v, v⟫_ℂ) :
  is_sa T :=
begin
  rw is_self_adjoint_iff_eq_adjoint,
  rw ← sub_eq_zero,
  apply inner_map_self_eq_zero,
  intro v,
  specialize h v,
  rw linear_map.sub_apply,
  rw inner_sub_left,
  rw linear_map.adjoint_inner_left,
  rw ← h,
  rw inner_conj_sym,
  rw sub_self,
end

/-
T is self-adjoint if and only if ⟪T v, v⟫_ℂ
-/
lemma is_self_adjoint_iff_real_inner_map : (is_sa T) ↔ ∀ v : ℂ^n, conj ⟪T v, v⟫_ℂ = ⟪T v, v⟫_ℂ :=
begin
  split,
  intro h,
  apply self_adjoint_real_inner T h,
  intro h,
  apply real_inner_self_adjoint T h,
end


/-
Copyright (c) 2019 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Benjamin Davidson
-/
import data.nat.parity

/-!
# Parity of integers

This file contains theorems about the `even` and `odd` predicates on the integers.

## Tags

even, odd
-/

namespace int

variables {m n : ℤ}

@[simp] theorem mod_two_ne_one : ¬ n % 2 = 1 ↔ n % 2 = 0 :=
by cases mod_two_eq_zero_or_one n with h h; simp [h]

local attribute [simp] -- euclidean_domain.mod_eq_zero uses (2 ∣ n) as normal form
theorem mod_two_ne_zero : ¬ n % 2 = 0 ↔ n % 2 = 1 :=
by cases mod_two_eq_zero_or_one n with h h; simp [h]

theorem even_iff : even n ↔ n % 2 = 0 :=
⟨λ ⟨m, hm⟩, by simp [hm], λ h, ⟨n / 2, (mod_add_div n 2).symm.trans (by simp [h])⟩⟩

theorem odd_iff : odd n ↔ n % 2 = 1 :=
⟨λ ⟨m, hm⟩, by { rw [hm, add_mod], norm_num },
 λ h, ⟨n / 2, (mod_add_div n 2).symm.trans (by { rw h, abel })⟩⟩

lemma not_even_iff : ¬ even n ↔ n % 2 = 1 :=
by rw [even_iff, mod_two_ne_zero]

lemma not_odd_iff : ¬ odd n ↔ n % 2 = 0 :=
by rw [odd_iff, mod_two_ne_one]

lemma even_iff_not_odd : even n ↔ ¬ odd n :=
by rw [not_odd_iff, even_iff]

@[simp] lemma odd_iff_not_even : odd n ↔ ¬ even n :=
by rw [not_even_iff, odd_iff]

lemma is_compl_even_odd : is_compl {n : ℤ | even n} {n | odd n} :=
by simp [← set.compl_set_of, is_compl_compl]

lemma even_or_odd (n : ℤ) : even n ∨ odd n :=
or.imp_right odd_iff_not_even.2 $ em $ even n

lemma even_or_odd' (n : ℤ) : ∃ k, n = 2 * k ∨ n = 2 * k + 1 :=
by simpa only [exists_or_distrib, ← odd, ← even] using even_or_odd n

lemma even_xor_odd (n : ℤ) : xor (even n) (odd n) :=
begin
  cases even_or_odd n with h,
  { exact or.inl ⟨h, even_iff_not_odd.mp h⟩ },
  { exact or.inr ⟨h, odd_iff_not_even.mp h⟩ },
end

lemma even_xor_odd' (n : ℤ) : ∃ k, xor (n = 2 * k) (n = 2 * k + 1) :=
begin
  rcases even_or_odd n with ⟨k, rfl⟩ | ⟨k, rfl⟩;
  use k,
  { simpa only [xor, true_and, eq_self_iff_true, not_true, or_false, and_false]
      using (succ_ne_self (2*k)).symm },
  { simp only [xor, add_right_eq_self, false_or, eq_self_iff_true, not_true, not_false_iff,
               one_ne_zero, and_self] },
end

@[simp] theorem two_dvd_ne_zero : ¬ 2 ∣ n ↔ n % 2 = 1 :=
not_even_iff

instance : decidable_pred (even : ℤ → Prop) :=
λ n, decidable_of_decidable_of_iff (by apply_instance) even_iff.symm

instance decidable_pred_odd : decidable_pred (odd : ℤ → Prop) :=
λ n, decidable_of_decidable_of_iff (by apply_instance) odd_iff_not_even.symm

@[simp] theorem even_zero : even (0 : ℤ) := ⟨0, dec_trivial⟩

@[simp] theorem not_even_one : ¬ even (1 : ℤ) :=
by rw even_iff; norm_num

@[simp] theorem even_bit0 (n : ℤ) : even (bit0 n) :=
⟨n, by rw [bit0, two_mul]⟩

@[parity_simps] theorem even_add : even (m + n) ↔ (even m ↔ even n) :=
by cases mod_two_eq_zero_or_one m with h₁ h₁;
   cases mod_two_eq_zero_or_one n with h₂ h₂;
   simp [even_iff, h₁, h₂, int.add_mod];
   norm_num

theorem even.add_even (hm : even m) (hn : even n) : even (m + n) :=
even_add.2 $ iff_of_true hm hn

theorem even_add' : even (m + n) ↔ (odd m ↔ odd n) :=
by rw [even_add, even_iff_not_odd, even_iff_not_odd, not_iff_not]

theorem odd.add_odd (hm : odd m) (hn : odd n) : even (m + n) :=
even_add'.2 $ iff_of_true hm hn

@[simp] theorem not_even_bit1 (n : ℤ) : ¬ even (bit1 n) :=
by simp [bit1] with parity_simps

lemma two_not_dvd_two_mul_add_one (n : ℤ) : ¬(2 ∣ 2 * n + 1) :=
by convert not_even_bit1 n; exact two_mul n

@[parity_simps] theorem even_sub : even (m - n) ↔ (even m ↔ even n) :=
by simp [sub_eq_add_neg] with parity_simps

theorem even.sub_even (hm : even m) (hn : even n) : even (m - n) :=
even_sub.2 $ iff_of_true hm hn

theorem even_sub' : even (m - n) ↔ (odd m ↔ odd n) :=
by rw [even_sub, even_iff_not_odd, even_iff_not_odd, not_iff_not]

theorem odd.sub_odd (hm : odd m) (hn : odd n) : even (m - n) :=
even_sub'.2 $ iff_of_true hm hn

@[parity_simps] theorem even_add_one : even (n + 1) ↔ ¬ even n :=
by simp [even_add]

@[parity_simps] theorem even_mul : even (m * n) ↔ even m ∨ even n :=
by cases mod_two_eq_zero_or_one m with h₁ h₁;
   cases mod_two_eq_zero_or_one n with h₂ h₂;
   simp [even_iff, h₁, h₂, int.mul_mod];
   norm_num

theorem odd_mul : odd (m * n) ↔ odd m ∧ odd n :=
by simp [not_or_distrib] with parity_simps

theorem even.mul_left (hm : even m) (n : ℤ) : even (m * n) :=
even_mul.mpr $ or.inl hm

theorem even.mul_right (m : ℤ) (hn : even n) : even (m * n) :=
even_mul.mpr $ or.inr hn

theorem odd.mul (hm : odd m) (hn : odd n) : odd (m * n) :=
odd_mul.mpr ⟨hm, hn⟩

theorem odd.of_mul_left (h : odd (m * n)) : odd m :=
(odd_mul.mp h).1

theorem odd.of_mul_right (h : odd (m * n)) : odd n :=
(odd_mul.mp h).2

@[parity_simps] theorem even_pow {n : ℕ} : even (m ^ n) ↔ even m ∧ n ≠ 0 :=
by { induction n with n ih; simp [*, even_mul, pow_succ], tauto }

theorem even_pow' {n : ℕ} (h : n ≠ 0) : even (m ^ n) ↔ even m :=
even_pow.trans $ and_iff_left h

@[parity_simps] theorem odd_add : odd (m + n) ↔ (odd m ↔ even n) :=
by rw [odd_iff_not_even, even_add, not_iff, odd_iff_not_even]

theorem odd.add_even (hm : odd m) (hn : even n) : odd (m + n) :=
odd_add.2 $ iff_of_true hm hn

theorem odd_add' : odd (m + n) ↔ (odd n ↔ even m) :=
by rw [add_comm, odd_add]

theorem even.add_odd (hm : even m) (hn : odd n) : odd (m + n) :=
odd_add'.2 $ iff_of_true hn hm

lemma ne_of_odd_add (h : odd (m + n)) : m ≠ n :=
λ hnot, by simpa [hnot] with parity_simps using h

@[parity_simps] theorem odd_sub : odd (m - n) ↔ (odd m ↔ even n) :=
by rw [odd_iff_not_even, even_sub, not_iff, odd_iff_not_even]

theorem odd.sub_even (hm : odd m) (hn : even n) : odd (m - n) :=
odd_sub.2 $ iff_of_true hm hn

theorem odd_sub' : odd (m - n) ↔ (odd n ↔ even m) :=
by rw [odd_iff_not_even, even_sub, not_iff, not_iff_comm, odd_iff_not_even]

theorem even.sub_odd (hm : even m) (hn : odd n) : odd (m - n) :=
odd_sub'.2 $ iff_of_true hn hm

lemma even_mul_succ_self (n : ℤ) : even (n * (n + 1)) :=
begin
  rw even_mul,
  convert n.even_or_odd,
  simp with parity_simps
end

@[simp, norm_cast] theorem even_coe_nat (n : ℕ) : even (n : ℤ) ↔ even n :=
by rw_mod_cast [even_iff, nat.even_iff]

@[simp, norm_cast] theorem odd_coe_nat (n : ℕ) : odd (n : ℤ) ↔ odd n :=
by rw [odd_iff_not_even, nat.odd_iff_not_even, even_coe_nat]

@[simp] theorem nat_abs_even : even n.nat_abs ↔ even n :=
coe_nat_dvd_left.symm

@[simp] theorem nat_abs_odd : odd n.nat_abs ↔ odd n :=
by rw [odd_iff_not_even, nat.odd_iff_not_even, nat_abs_even]

-- Here are examples of how `parity_simps` can be used with `int`.

example (m n : ℤ) (h : even m) : ¬ even (n + 3) ↔ even (m^2 + m + n) :=
by simp [*, (dec_trivial : ¬ 2 = 0)] with parity_simps

example : ¬ even (25394535 : ℤ) :=
by simp

end int


/- Here is a theorem and proof.  If there was anything wrong
   with it then Lean would complain.  As the proof is correct,
   Lean just accepts it silently.  To get more positive feedback,
   we use #check, which essentially restates the theorem.
-/

theorem one_plus_one : 1 + 1 ≤ 2 :=
/-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This line states a theorem, followed by " := ".
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-/
begin
 apply refl,
end
/-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the body of the proof.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-/

theorem one_plus_one_alt : 1 + 1 = 2 := rfl
/-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 The above proof was written in tactic mode, as indicated by
 the keywords `begin` and `end`.  This mode is most appropriate
 for long proofs with complex logical flow and many local
 definitions, which is of course not the case here.  It would
 be more efficient to use term mode, as shown here.
 Note that the previous proof uses the word `refl`, which is
 the name of a proof tactic.  The current proof uses the word
 `rfl`, which is the name of the fact that `x = x` for all `x`.
 The word `rfl` counts as a proof here, because we can just
 unwind the definitions of `1`, `2` and `+` and then the two
 sides of our equation become identical.  The proof tactic
 called `refl` just applies the fact called `rfl` together with
 some similar facts (such as the fact that `n ≤ n` for all
 natural numbers `n`).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-/


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,
  rw mul_comm c a,
end

-- 0002
example (a b c : ℝ) : a * (b * c) = b * (a * c) :=
begin
  rw ←mul_assoc,
  rw mul_comm a b,
  rw mul_assoc,
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 mul_comm at hyp',
  rw hyp' at hyp,
  rwa sub_self at 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 }
  ... = 0          : by { rw sub_self }
end

/-
The preceding proofs have exhauted 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 particuler 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) := by ring

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

-- 0007
example (a b : ℝ) : (a + b) + a = 2*a + b := by ring

/-
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 := by ring

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


/-
Justin Ngo
jmn4fms
4/28/20
Sullivan 2102-001
-/

/-
HOMEWORK #9

There is no need to import our previous definitions.
For this homework you will just use Lean's built-in
notations and definitions.
-/

/-
Prove the following. Note that you can read each of
the propositions to be proved as either a logical
statement or as simply a function definition. Use
what you already know about the latter to arrive
at a proof, and then understand the proof as one
that shows that the logical statement is true.
-/

theorem t1 {P Q : Prop} (p2q : P → Q) (p : P) : Q :=
/-If you have a proof of P → Q p2q, and a proof of P,
  you can get a proof of Q -/
  p2q p

theorem t2 {P Q R : Prop} (p2q : P → Q) (q2r : Q → R): P → R :=
  λ (p : P), -- Assume I have a proof of P, called p
    q2r (p2q p)

/-
Use "example" to state and prove the preceding two
theorems but using "cases" style notation rather
than C-style. Remember, "example" is a way to state
a proposition/type and give an example of a value.
Here's an example of the use of "example". Give
your answers following this example.
-/

-- example
example : ℕ := 5

-- Your two answers here
-- by cases
-- theorem t1 {P Q : Prop} (p2q : P → Q) (p : P) : Q :=
example: ∀ {P Q : Prop}, (P → Q) → P → Q :=
λ P Q p2q,
  λ p,
    p2q p

-- theorem t2 {P Q R : Prop} (p2q : P → Q) (q2r : Q → R): P → R :=
example: ∀ {P Q R : Prop}, (P → Q) → (Q → R) → P → R :=
λ P Q R p2q q2r,
  λ p,
    q2r (p2q p)

/-
Now give English-language versions of your two proofs.

1. If for and proposition P and Q, if P implies Q, then
    if we have a proof of P we can get a proof of Q.

2. If for any propositoins P, Q, and R, if P implies Q, and
    if Q implies R, then if we have a proof of P we can get
    a proof of R.
-/

/-
Prove the following using case analysis on one
of the arguments (i.e., use match...with...end
at a key point in your proof). Use "cases" style
notation.
-/

-- false elimination, false implies anything
theorem t3' : ∀ (P : Prop), false → P
/- For any proposition P, if false if true than p is true-/
| P f := false.elim f
-- false elim on f (false) to get p

theorem t3 : ∀ (P : Prop), false → P
| P f := match f with /-no cases-/ end

/-
Prove false → true by applying t3 to a proposition.
You have to figure out which one.
-/
theorem t4 : false → true := t3 true
-- apply t3 to true to get a proof of false → true

/-
Define t5 to be the same as t3 but with P taken as
an implicit argument.
-/
theorem t5 : ∀ {P : Prop}, false → P
| P f := false.elim f

/-
Define t6 to be a proof of false → true by
applying t5 to the right argument(s).
-/
theorem t6 : false → true := t5
-- implicit so we do not need to provide proposition

/-
That is almost magic. In English, t3 proves
that false implies *any* proposition, so just
*apply* t3 to *true* in particular, but use t5
instead of t3.

What you see here is really important: Once
we've proved a general theorem (a ∀ proposition)
we can *apply the proof* to any *particular* case
to yield a proof for that specific case. This is
the elimination rule for ∀. It is also known as
universal instantiation (UI).
-/

/-
Next we see the idea that test cases are really
just equality propositions to be proved. Here,
for example, is a definition of the factorial
function.
-/

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

/-
Use "example" to write test cases for the
first ten natural number arguments to this
function.
-/

example : fac 0 = 1 := eq.refl 1
example : fac 1 = 1 := eq.refl _  -- Inferred
example : fac 2 = 2 := rfl        -- Shorthand
#check @rfl           -- infers type and value

-- The rest of your answers here
example : fac 3 = 6 := eq.refl _
example : fac 4 = 24 := eq.refl _
example : fac 5 = 120 := eq.refl _

/-
Insight: A test case is an equality proposition.
It is proved by "running" the program under test
to reduce the application of the function to input
arguments to produce an output that is then asserted
to be equal to an expected output.

In many cases, all we have to do is to simplify
the expressions on each side of the eq to see if
they reduce to exactly the same value. If so, we
can *apply* eq.refl (a universal generalization!)
to that value. Using rfl we can avoid even having
to type that value in cases where Lean can infer
it.
-/

/-
The next problem requires thave you give a proof of
a bi-implication, a proposition whose connective is
↔. To prove a bi-implication requires that one prove
an implication in each direction.

Here you are asked to prove P ∧ Q ↔ Q ∧ P. What this
formula asserts is that ∧ is commutative. To construct
a proof of this proposition you will have to apply
iff.intro to two smaller proofs, one of P → Q and
and of Q → P.

Start by "assuming" that P and Q are arbitary but
specific propositions (∀ introduction), then apply
iff.intro to two "stubbed out" arguments (underscores).
We suggest that you put the underscores in parentheses
on different lines. Then recursively fill in each of
these stubs with the required types of proofs. Study
the context that Lean shows you in its Messages panel
to see what you have to work with at each point in
your proof constructions.
-/

theorem t7 : ∀ {P Q : Prop}, P ∧ Q ↔ Q ∧ P :=
-- need to use iff.intro (to prove if)
-- and and.intro to solve
λ P Q,
  iff.intro
    (λ pandq,
      and.intro pandq.right pandq.left)
    (λ qandp,
      and.intro qandp.right qandp.left)

-- P ∧ Q -- and.intro p q
-- P ↔ Q -- iff.intro p2q q2p
  -- Assume P then show Q (P → Q)
  -- Assume Q then show P (Q → P)

theorem and_intro {P Q : Prop} (pfPtoQ : P → Q) (pfQtoP : Q → P) : P ↔ Q :=
iff.intro pfPtoQ pfQtoP
/-
and.intro _ _ (Given pfP and pfQ, proves P ∧ Q)
-/

/-
In English, when asked to prove P ↔ Q, one says, "it
will suffice to show P → Q and then to show Q → P." One
then goes on to give a proof of each implication. It
then follows from iff.intro that a proof of P ↔ Q can
be constructed, proving the bi-implication.
-/

/-
The trick here is to do case analysis on porq
(use match ... with ... end) and to show that
a proof of R can be constructed *in either case*.
-/

/-
P ∨ Q can be constructed in 2 ways:
or.inl pfP (proves anything or P)
or.inr pfQ (proves anything or Q)
-/

theorem t8
          {P Q R : Prop}
          (p2r : P → R)
          (q2r : Q → R)
          (porq : P ∨ Q) :
          R :=
  match porq with
    | or.inl p := p2r p
    | or.inr q := q2r q
    end

theorem t8'
          {P Q R : Prop}
          (p2r : P → R)
          (q2r : Q → R)
          (porq : P ∨ Q) :
          R :=
  or.elim porq p2r q2r

/- Prove P ∨ Q → R,
  it will suffice to show P → R and then Q → R
  because if we do tha then we can use
  or.elim to get a proof of R.-/

/-
We suggest that you use  "let ... in" to give
names to intermediate results that you then combine
in a final expression to finish the proof.
-/
theorem t9 : ∀ (P Q: Prop), (P → Q) → ¬ (P ∧ ¬ Q) :=
λ P Q,
  λ (p2q : (P → Q)),
    λ (pandnq : (P ∧ ¬ Q)),
    -- ¬ (P ∧ ¬ Q) == (P ∧ ¬ Q) → false
      ( let p := pandnq.left in
        let nq := pandnq.right in
        let q := p2q p in
        nq q -- adding "¬" to (pandnq :(P ∧ ¬ Q))
      )

/-
To prove ¬ P, assume P and show that this leads to
a contradiction, then from that derive a proof of false,
and use false.elim to finish the proof.

* This is called "proof by negation"
-/

/-
Proof by contradiction.
To prove P, assume ¬ P, and show that leads to
a contradiction. This shows ¬ (¬ P). Now, use
the *classical* principle of negation elimination
to deduce P
-/

/-
theorem neg_elim' : ∀ (P : Prop), ¬ ¬ P → P :=
λ P,
λ nnp,
_           -- STUCK!!
-/

theorem neg_elim : ∀ (P : Prop), (P ∨ ¬ P) → (¬ ¬ P → P):=
λ P, -- Assume P is an arbitrary proposition
    λ excl_middle, -- Assume law of excluded middle/ "p or not p" (h)
        λ nnp,
            match excl_middle with -- case analysis on excl_middle (h)
            | or.inl p := p
            | or.inr np := false.elim (nnp np)  -- false elimination on a proof of false
            end

-- nnp : (¬ P) → false
-- np : ¬ P
-- nnp np = false!

-- Let's use excl_middle/ ("p or not p")/ (h) to mean
-- There is a sub-exponential time algorithm for Boolean sat.
-- ¬ excl_middle (h) means that there's not one.
-- (excl_middle ∨ ¬ excl_middle) H ∨ ¬H

-- make Lean into a classical logic
axiom em : ∀ (P : Prop), P ∨ ¬ P
#check classical.em
open classical

theorem t10 : ∀ (P : Prop), P ∨ ¬ P :=
  classical.em
  /-λ P,                      -- forall introduction
    λ (h : P ∨ ¬P),           -- proof by negation
      let p := (h.left) in    -- and.elim_left
      let np := (h.right) in  -- and.elim_right
      (np p) -/

#check @or.inl
#check @or.inr

theorem t11 : ∀ (P Q : Prop), ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
λ P Q,
    iff.intro
        -- first case
        (λ not_porq, -- Assume proof of ¬ (P ∨ Q)
            match (classical.em P) with -- Proof of p or not p
              | or.inl p := false.elim (not_porq (or.inl p))
              -- ¬ (P ∨ Q) == (P ∨ Q → false)
              -- use false.elim when a case is not possible
              | or.inr np :=
                  match (classical.em Q) with
                    | or.inl q := false.elim (not_porq (or.inr q))
                    | or.inr nq := and.intro np nq
                  end
            end
        )
        -- second case
        (λ np_and_nq,
          λ porq,
          -- need to do case analysis on the disjuntion
            match (porq) with
              | or.inl p := and.elim_left np_and_nq p
              | or.inr q := and.elim_right np_and_nq q
            end
        )

theorem t12 : ∀ (P Q : Prop), ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
λ P Q,
  iff.intro
    (λ (n_pandq),
      match (classical.em P) with
        | or.inl p :=
            match (classical.em Q) with
              | or.inl q := false.elim (n_pandq _)
              | or.inr nq := _
            end
        | or.inr np :=
          match (classical.em P) with
            | or.inl p := _
            | or.inr np := _
      end
    )
    _

/-
For the following exercises, we assume that there is
a type called Person and a binary relation, Likes, on
pairs of people.
-/
axiom Person : Type
axiom Likes : Person → Person → Prop

/-
Prove the following
-/
theorem t13 :
  (∃ (p : Person), ∀ (q : Person), Likes q p) →
  -- If there is someone that everyone likes
  (∀ (p : Person), ∃ (q : Person), Likes p q) :=
  -- Then everyone likes someone
λ h, -- Assume there is some person everyone likes
    match h with
    | exists.intro p pf :=
        λ q,
            (exists.intro p (pf q))
    end

/-
theorem t13' :
  (∃ (p : Person), ∀ (q : Person), Likes q p) →
  -- If there is someone that everyone likes
  (∀ (p : Person), ∃ (q : Person), Likes p q) :=
  -- Then everyone likes someone
λ h, -- Assume there is some person everyone likes
  λ p,
      (match h with
        | exists.intro loved pf := exists.intro loved _
      end
      )
-/

-- Notes
/-axiom Person : Type
axioms (Nice : Person → Prop)(Old : Person → Prop)

example : (∃ (p : Person), Nice p ∨ Old p) →
            (∃ (p : Person), Nice p) ∨
            (∃ (p : Person), Old p) :=
λ p_nice_or_old,
    match p_nice_or_old with
        | exists.intro fred pf_fred_either_nice_or_old :=
            -- need to break down proof of fred nice or old, elim
            match pf_fred_either_nice_or_old with
                | or.inl n := or.inl (exists.intro fred n)
                | or.inr o := or.inr (exists.intro fred o)
            end
        end
-/

/- Notes (not answer)
Answer looks like:
Proof by induction. Our aim is to prove that
for all n, the number of combinations of n bool variables
is 2^n. It will suffice to show that [two cases]
-/

-- exists.intro and exists.elim
-- intro rule builds a proof of an or (or.inl/ or.inr)
-- and elim rule uses a proof of an or

/-
Test cases for question 4
#reduce all_zero_interp (a_var.mk 5)

#reduce an_interp (a_var.mk 0) = 3
#reduce an_interp (a_var.mk 1) = 7
#reduce an_interp (a_var.mk 2) = 1
#reduce an_interp (a_var.mk 4) = 0
-/


/-
Copyright (c) 2022 Pierre-Alexandre Bazin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Pierre-Alexandre Bazin
-/
import analysis.seminorm
/-!
# The lattice of seminorms is not distributive

We provide an example of three seminorms over the ℝ-vector space ℝ×ℝ which don't satisfy the lattice
distributivity property `(p ⊔ q1) ⊓ (p ⊔ q2) ≤ p ⊔ (q1 ⊓ q2)`.

This proves the lattice `seminorm ℝ (ℝ × ℝ)` is not distributive.

## References

* https://en.wikipedia.org/wiki/Seminorm#Examples
-/

namespace seminorm_not_distrib
open_locale nnreal

private lemma bdd_below_range_add {𝕜 E : Type*} [normed_field 𝕜] [add_comm_group E] [module 𝕜 E]
  (x : E) (p q : seminorm 𝕜 E) :
  bdd_below (set.range (λ (u : E), p u + q (x - u))) :=
by { use 0, rintro _ ⟨x, rfl⟩, exact add_nonneg (p.nonneg _) (q.nonneg _) }

@[simps] noncomputable def p : seminorm ℝ (ℝ×ℝ) :=
(norm_seminorm ℝ ℝ).comp (linear_map.fst _ _ _) ⊔ (norm_seminorm ℝ ℝ).comp (linear_map.snd _ _ _)

@[simps] noncomputable def q1 : seminorm ℝ (ℝ×ℝ) :=
(4 : ℝ≥0) • (norm_seminorm ℝ ℝ).comp (linear_map.fst _ _ _)

@[simps] noncomputable def q2 : seminorm ℝ (ℝ×ℝ) :=
(4 : ℝ≥0) • (norm_seminorm ℝ ℝ).comp (linear_map.snd _ _ _)

lemma eq_one : (p ⊔ (q1 ⊓ q2)) (1, 1) = 1 :=
begin
  dsimp [-seminorm.inf_apply],
  rw [sup_idem, norm_one, sup_eq_left],
  apply cinfi_le_of_le (bdd_below_range_add _ _ _) ((0, 1) : ℝ×ℝ), dsimp,
  simp only [norm_zero, smul_zero, sub_self, add_zero, zero_le_one]
end

/-- This is a counterexample to the distributivity of the lattice `seminorm ℝ (ℝ × ℝ)`. -/
lemma not_distrib : ¬((p ⊔ q1) ⊓ (p ⊔ q2) ≤ p ⊔ (q1 ⊓ q2)) :=
begin
  intro le_sup_inf,
  have c : ¬(4/3 ≤ (1:ℝ)) := by norm_num,
  apply c, nth_rewrite 2 ← eq_one,
  apply le_trans _ (le_sup_inf _),
  apply le_cinfi, intro x,
  cases le_or_lt x.fst (1/3) with h1 h1,
  { cases le_or_lt x.snd (2/3) with h2 h2,
    { calc 4/3 = 4 * (1 - 2/3) : by norm_num
           ... ≤ 4 * (1 - x.snd) : (mul_le_mul_left zero_lt_four).mpr (sub_le_sub_left h2 _)
           ... ≤ 4 * |1 - x.snd| : (mul_le_mul_left zero_lt_four).mpr (le_abs_self _)
           ... = q2 ((1, 1) - x) : rfl
           ... ≤ (p ⊔ q2) ((1, 1) - x) : le_sup_right
           ... ≤ (p ⊔ q1) x + (p ⊔ q2) ((1, 1) - x) : le_add_of_nonneg_left ((p ⊔ q1).nonneg _) },
    { calc 4/3 = 2/3 + (1 - 1/3) : by norm_num
           ... ≤ x.snd + (1 - x.fst) : add_le_add (le_of_lt h2) (sub_le_sub_left h1 _)
           ... ≤ |x.snd| + |1 - x.fst| : add_le_add (le_abs_self _) (le_abs_self _)
           ... ≤ p x + p ((1, 1) - x) : add_le_add le_sup_right le_sup_left
           ... ≤ (p ⊔ q1) x + (p ⊔ q2) ((1, 1) - x) : add_le_add le_sup_left le_sup_left } },
  { calc 4/3 = 4 * (1/3) : by norm_num
         ... ≤ 4 * x.fst : (mul_le_mul_left zero_lt_four).mpr (le_of_lt h1)
         ... ≤ 4 * |x.fst| : (mul_le_mul_left zero_lt_four).mpr (le_abs_self _)
         ... = q1 x : rfl
         ... ≤ (p ⊔ q1) x : le_sup_right
         ... ≤ (p ⊔ q1) x + (p ⊔ q2) ((1, 1) - x) : le_add_of_nonneg_right ((p ⊔ q2).nonneg _) }
end

end seminorm_not_distrib


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que para todo par de números reales x e y, si
-- x < |y|, entonces x < y ó x < -y.
-- ----------------------------------------------------------------------

import data.real.basic

variables {x y : ℝ}

-- 1ª Demostración
-- ===============

example : x < abs y → x < y ∨ x < -y :=
begin
  cases le_or_gt 0 y with h1 h2,
  { rw abs_of_nonneg h1,
    intro h3,
    left,
    exact h3 },
  { rw abs_of_neg h2,
    intro h4,
    right,
    exact h4 },
end

-- Prueba
-- ======

/-
x y : ℝ
⊢ x < abs y → x < y ∨ x < -y
  >> cases le_or_gt 0 y with h1 h2,
| h1 : 0 ≤ y
| ⊢ x < abs y → x < y ∨ x < -y
|   >> { rw abs_of_nonneg h1,
| ⊢ x < y → x < y ∨ x < -y
|   >>   intro h3,
| h3 : x < y
| ⊢ x < y ∨ x < -y
|   >>   left,
| ⊢ x < y
|   >>   exact h3 },
h2 : 0 > y
⊢ x < abs y → x < y ∨ x < -y
  >> { rw abs_of_neg h2,
⊢ x < -y → x < y ∨ x < -y
  >>   intro h4,
h4 : x < -y
⊢ x < y ∨ x < -y
  >>   right,
⊢ x < -y
  >>   exact h4 },
no goals
-/

-- Comentarios:
-- + La táctica (cases h with h1 h2), cuando h es una diyunción, aplica
--   la regla de eliminación de la disyunción; es decir, si h es (P ∨ Q)
--   abre dos casos, en el primero añade la hipótesis (h1 : P) y en el
--   segundo (h2 : Q).
-- + Se han usado los siguientes lemas
--   + le_or_gt x y : x ≤ y ∨ x > y
--   + abs_of_nonneg : 0 ≤ x → abs x = x
--   + abs_of_neg : x < 0 → abs x = -x

-- Comprobación
-- #check (@le_or_gt ℝ _ x y)
-- #check (@abs_of_nonneg ℝ _ x)
-- #check (@abs_of_neg ℝ _ x)

-- 2ª Demostración
-- ===============

example : x < abs y → x < y ∨ x < -y :=
lt_abs.mp

-- Comentario: Se ha usado el lema
-- + lt_abs : x < abs y ↔ x < y ∨ x < -y

-- Comprobación
-- #check (@lt_abs ℝ _ x y)


-- https://mathcs.clarku.edu/~djoyce/java/elements/bookIX/propIX20.html

-- Definitions about natural numbers and primes
import data.nat.prime

-- Library on linear arithmatic
import tactic.linarith

-- Define namespace, which is natural numbers in this case
open nat

-- Define theorem or goal to prove
theorem infinitude_of_primes: ∀ N, ∃ p >= N, prime p :=
-- between begin-end block we write tactics
begin
  -- define N to be a natural number as a part of our local hypothesis
  intro N,

  -- Continue with proof as mentioned in link provided in header
  -- let M to be N! + 1
  let M := factorial N + 1,

  -- let p be smallest prime factor of M which is not 1
  let p := min_fac M,

  -- define supporting hypothesis pp, p is prime
  have pp : prime p :=
  -- begin proof for supporting p being prime
  begin
    -- minimum factor of a number is prime, but what about if M = 1
    refine min_fac_prime _,
    -- so here we prove M != 1 or M > 1
    have : factorial N > 0 := factorial_pos N,
    -- this just automatically takes care of linear arithmatic required for proof
    linarith,
  end,

  -- before this we had existenial statement but now we have condition in p
  use p,

  -- split our goal in  2 subgoals
  split,

  -- proof by contradiction so it should output False
  {by_contradiction,

   /- hypothesis h1, p divides N! + 1 proved by
   min_fac_dvd : ∀ (n : ℕ), n.min_fac ∣ n
   -/
   have h₁ : p ∣ factorial N + 1 := min_fac_dvd M,

   -- hypothesis h2, p divides N!
   have h₂ : p ∣  factorial N :=
   begin
     refine pp.dvd_factorial.mpr _,
     -- proved p <= N, using hypothsis h
     exact le_of_not_ge h,
   end,
   /-
   proved using dvd_add_right with support from local hypothesis h₂ and h₁
   -/
   have h : p ∣ 1 := (nat.dvd_add_right h₂).mp h₁,
   -- prime not dividing one using local hypothesis pp and h
   exact prime.not_dvd_one pp h, },
   -- second part of proof is just our hypothesis pp that we already proved
  {exact pp, },
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 data.nat.prime
import tactic.ring

open nat

/-!
# 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 :=
have h3 : k ∣ 2 * (21 * n + 4), from dvd_mul_of_dvd_right h1 2,
have h4 : k ∣ 3 * (14 * n + 3), from dvd_mul_of_dvd_right h2 3,
have h5 : 3 * (14 * n + 3) = 2 * (21 * n + 4) + 1, by ring,
(nat.dvd_add_right h3).mp (h5 ▸ h4)

theorem imo1959_q1 : ∀ n : ℕ, coprime (21 * n + 4) (14 * n + 3) :=
assume n, coprime_of_dvd' $ λ k hp h1 h2, calculation n k h1 h2


/-
Group Theory in Lean

Produced while reading through:

A. Zipperer "A formalization of elementary group theory
in the proof assistant Lean"

Notation used here:

- `f : α → β` is a function

- `s : set α` and `s₁ s₂ : set α` are subsets of `α`

- `preimage f t : set α` : the preimage f⁻¹(t)
-/
-- lets hold everything in our namespace to deconflict with defaults
namespace mygroup

-- definition of a set in lean. set X is the term X → Prop and has type Type
-- definition set' := λ (A : Type), A → Prop
-- equivalent syntax
definition set' (A : Type) := A → Prop

-- add membership notation (A → Prop)(a) membership hypothesis prop
notation x ∈ S := S x

-- this is one way to define set intersection and union. Using squiggly braces, we do not
-- need to pass the argument the type of sets.
-- definition intersection {A : Type} (S : set A) (T: set A) : set A := λ x, x ∈ S ∧ x ∈ T
-- definition union {A : Type} (S : set A) (T: set A) : set A := λ x, x ∈ S ∨ x ∈ T

-- this is a better way to write the above definitions. Using sections, we can simulatneously
-- represent the type in the background and achieve the previous definitions.
section
  variable {A : Type}

  -- this is using the λ-abstract
  -- definition intersection (S : set A) (T : set A) : set A :=
  --  λ (x : A), x ∈ S ∧ x ∈ T

  -- definition union (S : set A) (T : set A) : set A :=
  --  λ (x : A), x ∈ S ∨ x ∈ T

  -- this is using set builder notation
  definition intersection (S : set A) (T : set A) : set A :=
    {a : A | a ∈ S ∧ a ∈ T}

  definition union (S : set A) (T : set A) : set A :=
    {a : A | a ∈ S ∨ a ∈ T}

  definition subset (S : set A) (T : set A) : Prop :=
    ∀ (a : A), a ∈ S → a ∈ T

  infix ` ∩ `:50 := intersection
  infix ` ∪ `:50 := union
  -- infix ` ⊆ `:50 := subset

  -- show identity bewteen (x ∈ (S₁ ∩ S₂)) and ((x ∈ S₁) ∧ (x ∈ S₂)) by reducing them to a common
  -- term
  example {X : Type} (S₁ : set X) (S₂ : set X) (x : X) : (x ∈ (S₁ ∩ S₂)) = ((x ∈ S₁) ∧ (x ∈ S₂)) :=
  begin
    refl,
  end

  -- prove that is A ⊆ B, and B ⊆ C, then A ⊆ C
  example {X : Type} (A : set X) (B : set X) (C : set X) (HAB : A ⊆ B)
    (HBC : B ⊆ C) : A ⊆ C :=
  begin
    intros x Hx,
    apply HBC,
    apply HAB,
    apply Hx,
  end
end

section
  variables {A B : Type}

  definition image (f : A → B) (S : set A) : set B :=
    {y : B | ∃ x : A, x ∈ S ∧ f x = y}

  definition preimage (f : A → B) (T : set B) : set A :=
    {x : A | f x ∈ T}

  definition injective (f : A → B) : Prop :=
    ∀ x y, f x = f y → x = y

  definition inj_on (f : A → B) (S : set A) : Prop  :=
    ∀ x y, x ∈ S → y ∈ S → f x = f y → x = y

  definition surjective (f : A → B) : Prop :=
    ∀ x, ∃ y, f x = y

  definition surj_on (f : A → B) (S : set A) (T : set B) : Prop :=
    ∀ (y : B), (y ∈ T → ∃ (x : A), (x ∈ S ∧ f x = y))
end

definition unary_operation (A : Type) := A → A
definition binary_operation (A : Type) := A → A → A
-- definition is_commutative (A : Type) (op : A → A → A) :=
--  ∀ x y, op x y = op y x
section
  variable {A : Type}

  definition is_distributive (f : A → A) (op : A → A → A) : Prop :=
    ∀ x y, f (op x y) = op (f x) (f y)
end

-- object has multiplication
class has_mul (A : Type) := (mul : A → A → A)
-- object has one
class has_one (A : Type) := (one : A)
-- object has an inverse
class has_inv (A : Type) := (inv : A → A)

class semigroup (A : Type) extends has_mul A :=
  (assoc : ∀ x y z, mul x (mul y z) = mul (mul x y) z)

class monoid (A : Type) extends semigroup A, has_one A :=
  (mul_one : ∀ x, mul x one = x)
  (one_mul : ∀ x, mul one x = x)

class group (A : Type) extends monoid A, has_inv A :=
  (mul_left_inv : ∀ x, mul (inv x) x = one)

infix * := has_mul.mul
postfix ⁻¹ := has_inv.inv
notation 1 := has_one.one

section
  variables {X : Type} [G : group X] (a : X) (b : X)

  #check a
  #check a⁻¹
  #check a * b
  #check (a * b⁻¹) * b = a
end

section
  variables {G : Type} [group G] (a b : G)
  example : (a * b⁻¹) * b = a :=
  calc (a * b⁻¹) * b = a * (b⁻¹ * b) : by rw semigroup.assoc
        ... = a * has_one.one : by rw group.mul_left_inv
        ... = a : by rw monoid.mul_one
end

section
  variable {A : Type}

  class is_mul_closed [has_mul A] (S : set A) : Prop :=
    (mul_mem : ∀ x y, x ∈ S → y ∈ S → x * y ∈ S)

  class is_inv_closed [has_inv A] (S : set A) : Prop :=
    (inv_mem : ∀ x, x ∈ S → x⁻¹ ∈ S)

  class is_one_closed [has_one A] (S : set A) : Prop :=
    (one_mem : has_one.one ∈ S)

  class is_subgroup [group A] (S : set A) extends is_mul_closed S,
    is_inv_closed S, is_one_closed S

end

section
  variables {X : Type}[has_mul X]

  definition lcoset (x : X) (S : set X):= image (has_mul.mul x) S
  definition rcoset (S : set X) (x : X) := image ((λ x y, has_mul.mul y x) x) S

  infix ` * `:55 := lcoset
  infix ` * `:55 := rcoset

  variables {A : Type} [has_mul A]

  definition normalizes (a : A) (S : set A) : Prop := a * S = S * a

  definition is_normal (S : set A) : Prop :=
    ∀ (a : A), normalizes a S

  definition normalizer (S : set A) : set A :=
    {a : A | normalizes a S}
end
end mygroup


import data.list

namespace hidden

variable {α : Type}

def len {α : Type} : list α → ℕ
 | [] := 0
 | (hd :: tl) := 1 + len tl

def concat {α : Type} : list (list α) → list α
 | [] := []
 | (hd :: tl) := hd ++ (concat tl)

def nonempty {α : Type} : list α → Prop
 | [] := false
 | (_ :: _) := true

-- If the 2nd of two lists being concatenated is non-empty then their concatenation is non-empty
lemma nonempty_tail (L M : list α) (h : nonempty M) : nonempty (L ++ M) :=
begin
    cases L,
    {simp,
    exact h},
    {simp}
end

-- If the 1st of two lists being concatenated is non-empty then their concatenation is non-empty
-- This is the lemma that is actually used in the proof of our theorem, but the other lemma
-- is needed for this proof
lemma nonempty_head (L M : list α) (h : nonempty L) : nonempty (L ++ M) :=
begin
    cases M,
    {simp,
    exact h},
    {have h₂ : nonempty (M_hd :: M_tl), begin
        dsimp [nonempty],
        trivial,
    end,
    apply nonempty_tail,
    exact h₂,
    }
end

-- Given a non-empty list of lists, all of the elements of which are also non-empty, we show that the full concatenation is non-empty
theorem nonempty_concat_of_nonempty_is_nonempty
(L : list (list α)) (h : nonempty L) (w : ∀ (m ∈ L), nonempty m) : nonempty (concat L) :=
begin
  cases L,
  {cases h},
  {dsimp [concat],
   apply nonempty_head,
   apply w,
   simp,
  }
end

end hidden


import data.real.basic
import data.int.parity

/-
In this file, we learn how to handle the ∃ quantifier.

In order to prove `∃ x, P x`, we give some x₀ using tactic `use x₀` and
then prove `P x₀`. This x₀ can be an object from the local context
or a more complicated expression.
-/
example : ∃ n : ℕ, 8 = 2*n :=
begin
  use 4,
  refl,  -- this is the tactic analogue of the rfl proof term
end

/-
In order to use `h : ∃ x, P x`, we use the `cases` tactic to fix
one x₀ that works.

Again h can come straight from the local context or can be a more
complicated expression.
-/
example (n : ℕ) (h : ∃ k : ℕ, n = k + 1) : n > 0 :=
begin
  -- Let's fix k₀ such that n = k₀ + 1.
  cases h with k₀ hk₀,
  -- It now suffices to prove k₀ + 1 > 0.
  rw hk₀,
  -- and we have a lemma about this
  exact nat.succ_pos k₀,
end

/-
The next exercises use divisibility in ℤ (beware the ∣ symbol which is
not ASCII).

By definition, a ∣ b ↔ ∃ k, b = a*k, so you can prove a ∣ b using the
`use` tactic.
-/

-- Until the end of this file, a, b and c will denote integers, unless
-- explicitly stated otherwise
variables (a b c : ℤ)

-- 0029
example (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
begin
  -- sorry
  cases h₁ with k hk,
  cases h₂ with l hl,
  use k*l,
  calc c = b*l     : hl
     ... = (a*k)*l : by rw hk
     ... = a*(k*l) : by ring,
  -- sorry
end

/-
A very common pattern is to have an assumption or lemma asserting
  h : ∃ x, y = ...
and this is used through the combo:
  cases h with x hx,
  rw hx at ...
The tactic `rcases` allows us to do recursive `cases`, as indicated by its name,
and also simplifies the above combo when the name hx is replaced by the special
name `rfl`, as in the following example.
It uses the anonymous constructor angle brackets syntax.
-/

example (h1 : a ∣ b) (h2 : a ∣ c) : a ∣ b+c :=
begin
  rcases h1 with ⟨k, rfl⟩,
  rcases h2 with ⟨l, rfl⟩,
  use k+l,
  ring,
end

/-
You can use the same `rfl` trick with the `rintros` tactic.
-/

example : a ∣ b → a ∣ c → a ∣ b+c :=
begin
  rintros ⟨k, rfl⟩ ⟨l, rfl⟩,
  use k+l,
  ring,
end

-- 0030
example : 0 ∣ a ↔ a = 0 :=
begin
  -- sorry
  split,
  { rintro ⟨k, rfl⟩,
    ring, },
  { rintro rfl,
    use 0,
    refl, },
  -- sorry
end

/-
We can now start combining quantifiers, using the definition

  surjective (f : X → Y) := ∀ y, ∃ x, f x = y

-/
open function

-- In the remaining of this file, f and g will denote functions from
-- ℝ to ℝ.
variables (f g : ℝ → ℝ)

-- 0031
example (h : surjective (g ∘ f)) : surjective g :=
begin
  -- sorry
  intro y,
  rcases h y with ⟨w, rfl⟩,
  use f w,
  -- sorry
end

/-
The above exercise can be done in three lines. Try to do the
next exercise in four lines.
-/

-- 0032
example (hf : surjective f) (hg : surjective g) : surjective (g ∘ f) :=
begin
  -- sorry
  intro z,
  rcases hg z with ⟨y, rfl⟩,
  rcases hf y with ⟨x, rfl⟩,
  use x,
  -- sorry
end


/-
Copyright (c) 2014 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn, Jeremy Avigad

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

/-!
#  Distance function on ℕ

This file defines a simple distance function on naturals from truncated subtraction.
-/

namespace Nat

/-- Distance (absolute value of difference) between natural numbers. -/
def dist (n m : ℕ) :=
  n - m + (m - n)
#align nat.dist Nat.dist

theorem dist.def (n m : ℕ) : dist n m = n - m + (m - n) :=
  rfl
#align nat.dist.def Nat.dist.def

theorem dist_comm (n m : ℕ) : dist n m = dist m n := by simp [dist.def, add_comm]
#align nat.dist_comm Nat.dist_comm

@[simp]
theorem dist_self (n : ℕ) : dist n n = 0 := by simp [dist.def, tsub_self]
#align nat.dist_self Nat.dist_self

theorem eq_of_dist_eq_zero {n m : ℕ} (h : dist n m = 0) : n = m :=
  have : n - m = 0 := Nat.eq_zero_of_add_eq_zero_right h
  have : n ≤ m := tsub_eq_zero_iff_le.mp this
  have : m - n = 0 := Nat.eq_zero_of_add_eq_zero_left h
  have : m ≤ n := tsub_eq_zero_iff_le.mp this
  le_antisymm ‹n ≤ m› ‹m ≤ n›
#align nat.eq_of_dist_eq_zero Nat.eq_of_dist_eq_zero

theorem dist_eq_zero {n m : ℕ} (h : n = m) : dist n m = 0 := by rw [h, dist_self]
#align nat.dist_eq_zero Nat.dist_eq_zero

theorem dist_eq_sub_of_le {n m : ℕ} (h : n ≤ m) : dist n m = m - n := by
  rw [dist.def, tsub_eq_zero_iff_le.mpr h, zero_add]
#align nat.dist_eq_sub_of_le Nat.dist_eq_sub_of_le

theorem dist_eq_sub_of_le_right {n m : ℕ} (h : m ≤ n) : dist n m = n - m :=
  by rw [dist_comm]; apply dist_eq_sub_of_le h
#align nat.dist_eq_sub_of_le_right Nat.dist_eq_sub_of_le_right

theorem dist_tri_left (n m : ℕ) : m ≤ dist n m + n :=
  le_trans le_tsub_add (add_le_add_right (Nat.le_add_left _ _) _)
#align nat.dist_tri_left Nat.dist_tri_left

theorem dist_tri_right (n m : ℕ) : m ≤ n + dist n m := by rw [add_comm]; apply dist_tri_left
#align nat.dist_tri_right Nat.dist_tri_right

theorem dist_tri_left' (n m : ℕ) : n ≤ dist n m + m := by rw [dist_comm]; apply dist_tri_left
#align nat.dist_tri_left' Nat.dist_tri_left'

theorem dist_tri_right' (n m : ℕ) : n ≤ m + dist n m := by rw [dist_comm]; apply dist_tri_right
#align nat.dist_tri_right' Nat.dist_tri_right'

theorem dist_zero_right (n : ℕ) : dist n 0 = n :=
  Eq.trans (dist_eq_sub_of_le_right (zero_le n)) (tsub_zero n)
#align nat.dist_zero_right Nat.dist_zero_right

theorem dist_zero_left (n : ℕ) : dist 0 n = n :=
  Eq.trans (dist_eq_sub_of_le (zero_le n)) (tsub_zero n)
#align nat.dist_zero_left Nat.dist_zero_left

theorem dist_add_add_right (n k m : ℕ) : dist (n + k) (m + k) = dist n m :=
  calc
    dist (n + k) (m + k) = n + k - (m + k) + (m + k - (n + k)) := rfl
    _ = n - m + (m + k - (n + k)) := by rw [@add_tsub_add_eq_tsub_right]
    _ = n - m + (m - n) := by rw [@add_tsub_add_eq_tsub_right]

#align nat.dist_add_add_right Nat.dist_add_add_right

theorem dist_add_add_left (k n m : ℕ) : dist (k + n) (k + m) = dist n m := by
  rw [add_comm k n, add_comm k m]; apply dist_add_add_right
#align nat.dist_add_add_left Nat.dist_add_add_left

theorem dist_eq_intro {n m k l : ℕ} (h : n + m = k + l) : dist n k = dist l m :=
  calc
    dist n k = dist (n + m) (k + m) := by rw [dist_add_add_right]
    _ = dist (k + l) (k + m) := by rw [h]
    _ = dist l m := by rw [dist_add_add_left]

#align nat.dist_eq_intro Nat.dist_eq_intro

theorem dist.triangle_inequality (n m k : ℕ) : dist n k ≤ dist n m + dist m k := by
  have : dist n m + dist m k = n - m + (m - k) + (k - m + (m - n)) := by
    simp [dist.def, add_comm, add_left_comm, add_assoc]
  rw [this, dist.def]
  exact add_le_add tsub_le_tsub_add_tsub tsub_le_tsub_add_tsub
#align nat.dist.triangle_inequality Nat.dist.triangle_inequality

theorem dist_mul_right (n k m : ℕ) : dist (n * k) (m * k) = dist n m * k := by
  rw [dist.def, dist.def, right_distrib, tsub_mul n, tsub_mul m]
#align nat.dist_mul_right Nat.dist_mul_right

theorem dist_mul_left (k n m : ℕ) : dist (k * n) (k * m) = k * dist n m := by
  rw [mul_comm k n, mul_comm k m, dist_mul_right, mul_comm]
#align nat.dist_mul_left Nat.dist_mul_left

theorem dist_eq_max_sub_min {i j : ℕ} : dist i j = (max i j) - min i j :=
  Or.elim (lt_or_ge i j)
  (by intro h; rw [max_eq_right_of_lt h, min_eq_left_of_lt h, dist_eq_sub_of_le (Nat.le_of_lt h)])
  (by intro h; rw [max_eq_left h, min_eq_right h, dist_eq_sub_of_le_right h])

theorem dist_succ_succ {i j : Nat} : dist (succ i) (succ j) = dist i j := by
  simp [dist.def, succ_sub_succ]
#align nat.dist_succ_succ Nat.dist_succ_succ

theorem dist_pos_of_ne {i j : Nat} : i ≠ j → 0 < dist i j := fun hne =>
  Nat.lt_by_cases
    (fun h : i < j => by rw [dist_eq_sub_of_le (le_of_lt h)]; apply tsub_pos_of_lt h)
    (fun h : i = j => by contradiction) fun h : i > j => by
    rw [dist_eq_sub_of_le_right (le_of_lt h)]; apply tsub_pos_of_lt h
#align nat.dist_pos_of_ne Nat.dist_pos_of_ne

end Nat


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Mario Carneiro
-/
import Std.Data.Nat.Lemmas

/-!
# Definitions and properties of `gcd`, `lcm`, and `coprime`

-/

namespace Nat

theorem gcd_rec (m n : Nat) : gcd m n = gcd (n % m) m :=
  match m with
  | 0 => by have := (mod_zero n).symm; rwa [gcd_zero_right]
  | _ + 1 => by simp [gcd_succ]

@[elab_as_elim] theorem gcd.induction {P : Nat → Nat → Prop} (m n : Nat)
    (H0 : ∀n, P 0 n) (H1 : ∀ m n, 0 < m → P (n % m) m → P m n) : P m n :=
  Nat.strongInductionOn (motive := fun m => ∀ n, P m n) m
    (fun
    | 0, _ => H0
    | _+1, IH => fun _ => H1 _ _ (succ_pos _) (IH _ (mod_lt _ (succ_pos _)) _) )
    n

/-- The least common multiple of `m` and `n`, defined using `gcd`. -/
def lcm (m n : Nat) : Nat := m * n / gcd m n

/-- `m` and `n` are coprime, or relatively prime, if their `gcd` is 1. -/
@[reducible] def coprime (m n : Nat) : Prop := gcd m n = 1

---

theorem gcd_dvd (m n : Nat) : (gcd m n ∣ m) ∧ (gcd m n ∣ n) := by
  induction m, n using gcd.induction with
  | H0 n => rw [gcd_zero_left]; exact ⟨Nat.dvd_zero n, Nat.dvd_refl n⟩
  | H1 m n _ IH => rw [← gcd_rec] at IH; exact ⟨IH.2, (dvd_mod_iff IH.2).1 IH.1⟩

theorem gcd_dvd_left (m n : Nat) : gcd m n ∣ m := (gcd_dvd m n).left

theorem gcd_dvd_right (m n : Nat) : gcd m n ∣ n := (gcd_dvd m n).right

theorem gcd_le_left (n) (h : 0 < m) : gcd m n ≤ m := le_of_dvd h <| gcd_dvd_left m n

theorem gcd_le_right (n) (h : 0 < n) : gcd m n ≤ n := le_of_dvd h <| gcd_dvd_right m n

theorem dvd_gcd : k ∣ m → k ∣ n → k ∣ gcd m n := by
  induction m, n using gcd.induction with intro km kn
  | H0 n => rw [gcd_zero_left]; exact kn
  | H1 n m _ IH => rw [gcd_rec]; exact IH ((dvd_mod_iff km).2 kn) km

theorem dvd_gcd_iff : k ∣ gcd m n ↔ k ∣ m ∧ k ∣ n :=
  ⟨fun h => let ⟨h₁, h₂⟩ := gcd_dvd m n; ⟨Nat.dvd_trans h h₁, Nat.dvd_trans h h₂⟩,
   fun ⟨h₁, h₂⟩ => dvd_gcd h₁ h₂⟩

theorem gcd_comm (m n : Nat) : gcd m n = gcd n m :=
  dvd_antisymm
    (dvd_gcd (gcd_dvd_right m n) (gcd_dvd_left m n))
    (dvd_gcd (gcd_dvd_right n m) (gcd_dvd_left n m))

theorem gcd_eq_left_iff_dvd : m ∣ n ↔ gcd m n = m :=
  ⟨fun h => by rw [gcd_rec, mod_eq_zero_of_dvd h, gcd_zero_left],
   fun h => h ▸ gcd_dvd_right m n⟩

theorem gcd_eq_right_iff_dvd : m ∣ n ↔ gcd n m = m := by
  rw [gcd_comm]; exact gcd_eq_left_iff_dvd

theorem gcd_assoc (m n k : Nat) : gcd (gcd m n) k = gcd m (gcd n k) :=
  dvd_antisymm
    (dvd_gcd
      (Nat.dvd_trans (gcd_dvd_left (gcd m n) k) (gcd_dvd_left m n))
      (dvd_gcd (Nat.dvd_trans (gcd_dvd_left (gcd m n) k) (gcd_dvd_right m n))
        (gcd_dvd_right (gcd m n) k)))
    (dvd_gcd
      (dvd_gcd (gcd_dvd_left m (gcd n k))
        (Nat.dvd_trans (gcd_dvd_right m (gcd n k)) (gcd_dvd_left n k)))
      (Nat.dvd_trans (gcd_dvd_right m (gcd n k)) (gcd_dvd_right n k)))

@[simp] theorem gcd_one_right (n : Nat) : gcd n 1 = 1 := (gcd_comm n 1).trans (gcd_one_left n)

theorem gcd_mul_left (m n k : Nat) : gcd (m * n) (m * k) = m * gcd n k := by
  induction n, k using gcd.induction with
  | H0 k => simp
  | H1 n k _ IH => rwa [← mul_mod_mul_left, ← gcd_rec, ← gcd_rec] at IH

theorem gcd_mul_right (m n k : Nat) : gcd (m * n) (k * n) = gcd m k * n := by
  rw [Nat.mul_comm m n, Nat.mul_comm k n, Nat.mul_comm (gcd m k) n, gcd_mul_left]

theorem gcd_pos_of_pos_left {m : Nat} (n : Nat) (mpos : 0 < m) : 0 < gcd m n :=
  pos_of_dvd_of_pos (gcd_dvd_left m n) mpos

theorem gcd_pos_of_pos_right (m : Nat) {n : Nat} (npos : 0 < n) : 0 < gcd m n :=
  pos_of_dvd_of_pos (gcd_dvd_right m n) npos

theorem div_gcd_pos_of_pos_left (b : Nat) (h : 0 < a) : 0 < a / a.gcd b :=
  (Nat.le_div_iff_mul_le <| Nat.gcd_pos_of_pos_left _ h).2 (Nat.one_mul _ ▸ Nat.gcd_le_left _ h)

theorem div_gcd_pos_of_pos_right (a : Nat) (h : 0 < b) : 0 < b / a.gcd b :=
  (Nat.le_div_iff_mul_le <| Nat.gcd_pos_of_pos_right _ h).2 (Nat.one_mul _ ▸ Nat.gcd_le_right _ h)

theorem eq_zero_of_gcd_eq_zero_left {m n : Nat} (H : gcd m n = 0) : m = 0 :=
  match eq_zero_or_pos m with
  | .inl H0 => H0
  | .inr H1 => absurd (Eq.symm H) (ne_of_lt (gcd_pos_of_pos_left _ H1))

theorem eq_zero_of_gcd_eq_zero_right {m n : Nat} (H : gcd m n = 0) : n = 0 := by
  rw [gcd_comm] at H
  exact eq_zero_of_gcd_eq_zero_left H

theorem gcd_ne_zero_left : m ≠ 0 → gcd m n ≠ 0 := mt eq_zero_of_gcd_eq_zero_left

theorem gcd_ne_zero_right : n ≠ 0 → gcd m n ≠ 0 := mt eq_zero_of_gcd_eq_zero_right

theorem gcd_div {m n k : Nat} (H1 : k ∣ m) (H2 : k ∣ n) :
    gcd (m / k) (n / k) = gcd m n / k :=
  match eq_zero_or_pos k with
  | .inl H0 => by simp [H0]
  | .inr H3 => by
    apply Nat.eq_of_mul_eq_mul_right H3
    rw [Nat.div_mul_cancel (dvd_gcd H1 H2), ← gcd_mul_right,
        Nat.div_mul_cancel H1, Nat.div_mul_cancel H2]

theorem gcd_dvd_gcd_of_dvd_left {m k : Nat} (n : Nat) (H : m ∣ k) : gcd m n ∣ gcd k n :=
  dvd_gcd (Nat.dvd_trans (gcd_dvd_left m n) H) (gcd_dvd_right m n)

theorem gcd_dvd_gcd_of_dvd_right {m k : Nat} (n : Nat) (H : m ∣ k) : gcd n m ∣ gcd n k :=
  dvd_gcd (gcd_dvd_left n m) (Nat.dvd_trans (gcd_dvd_right n m) H)

theorem gcd_dvd_gcd_mul_left (m n k : Nat) : gcd m n ∣ gcd (k * m) n :=
  gcd_dvd_gcd_of_dvd_left _ (Nat.dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right (m n k : Nat) : gcd m n ∣ gcd (m * k) n :=
  gcd_dvd_gcd_of_dvd_left _ (Nat.dvd_mul_right _ _)

theorem gcd_dvd_gcd_mul_left_right (m n k : Nat) : gcd m n ∣ gcd m (k * n) :=
  gcd_dvd_gcd_of_dvd_right _ (Nat.dvd_mul_left _ _)

theorem gcd_dvd_gcd_mul_right_right (m n k : Nat) : gcd m n ∣ gcd m (n * k) :=
  gcd_dvd_gcd_of_dvd_right _ (Nat.dvd_mul_right _ _)

theorem gcd_eq_left {m n : Nat} (H : m ∣ n) : gcd m n = m :=
  dvd_antisymm (gcd_dvd_left _ _) (dvd_gcd (Nat.dvd_refl _) H)

theorem gcd_eq_right {m n : Nat} (H : n ∣ m) : gcd m n = n := by
  rw [gcd_comm, gcd_eq_left H]

@[simp] theorem gcd_mul_left_left (m n : Nat) : gcd (m * n) n = n :=
  dvd_antisymm (gcd_dvd_right _ _) (dvd_gcd (Nat.dvd_mul_left _ _) (Nat.dvd_refl _))

@[simp] theorem gcd_mul_left_right (m n : Nat) : gcd n (m * n) = n := by
  rw [gcd_comm, gcd_mul_left_left]

@[simp] theorem gcd_mul_right_left (m n : Nat) : gcd (n * m) n = n := by
  rw [Nat.mul_comm, gcd_mul_left_left]

@[simp] theorem gcd_mul_right_right (m n : Nat) : gcd n (n * m) = n := by
  rw [gcd_comm, gcd_mul_right_left]

@[simp] theorem gcd_gcd_self_right_left (m n : Nat) : gcd m (gcd m n) = gcd m n :=
  dvd_antisymm (gcd_dvd_right _ _) (dvd_gcd (gcd_dvd_left _ _) (Nat.dvd_refl _))

@[simp] theorem gcd_gcd_self_right_right (m n : Nat) : gcd m (gcd n m) = gcd n m := by
  rw [gcd_comm n m, gcd_gcd_self_right_left]

@[simp] theorem gcd_gcd_self_left_right (m n : Nat) : gcd (gcd n m) m = gcd n m := by
  rw [gcd_comm, gcd_gcd_self_right_right]

@[simp] theorem gcd_gcd_self_left_left (m n : Nat) : gcd (gcd m n) m = gcd m n := by
  rw [gcd_comm m n, gcd_gcd_self_left_right]

theorem gcd_add_mul_self (m n k : Nat) : gcd m (n + k * m) = gcd m n := by
  simp [gcd_rec m (n + k * m), gcd_rec m n]

theorem gcd_eq_zero_iff {i j : Nat} : gcd i j = 0 ↔ i = 0 ∧ j = 0 :=
  ⟨fun h => ⟨eq_zero_of_gcd_eq_zero_left h, eq_zero_of_gcd_eq_zero_right h⟩,
   fun h => by simp [h]⟩

/-! ### `lcm` -/

theorem lcm_comm (m n : Nat) : lcm m n = lcm n m := by
  rw [lcm, lcm, Nat.mul_comm n m, gcd_comm n m]

@[simp] theorem lcm_zero_left (m : Nat) : lcm 0 m = 0 := by simp [lcm]

@[simp] theorem lcm_zero_right (m : Nat) : lcm m 0 = 0 := by simp [lcm]

@[simp] theorem lcm_one_left (m : Nat) : lcm 1 m = m := by simp [lcm]

@[simp] theorem lcm_one_right (m : Nat) : lcm m 1 = m := by simp [lcm]

@[simp] theorem lcm_self (m : Nat) : lcm m m = m := by
  match eq_zero_or_pos m with
  | .inl h => rw [h, lcm_zero_left]
  | .inr h => simp [lcm, Nat.mul_div_cancel _ h]

theorem dvd_lcm_left (m n : Nat) : m ∣ lcm m n :=
  ⟨n / gcd m n, by rw [← Nat.mul_div_assoc m (Nat.gcd_dvd_right m n)]; rfl⟩

theorem dvd_lcm_right (m n : Nat) : n ∣ lcm m n := lcm_comm n m ▸ dvd_lcm_left n m

theorem gcd_mul_lcm (m n : Nat) : gcd m n * lcm m n = m * n := by
  rw [lcm, Nat.mul_div_cancel' (Nat.dvd_trans (gcd_dvd_left m n) (Nat.dvd_mul_right m n))]

theorem lcm_dvd {m n k : Nat} (H1 : m ∣ k) (H2 : n ∣ k) : lcm m n ∣ k := by
  match eq_zero_or_pos k with
  | .inl h => rw [h]; exact Nat.dvd_zero _
  | .inr kpos =>
    apply Nat.dvd_of_mul_dvd_mul_left (gcd_pos_of_pos_left n (pos_of_dvd_of_pos H1 kpos))
    rw [gcd_mul_lcm, ← gcd_mul_right, Nat.mul_comm n k]
    exact dvd_gcd (Nat.mul_dvd_mul_left _ H2) (Nat.mul_dvd_mul_right H1 _)

theorem lcm_assoc (m n k : Nat) : lcm (lcm m n) k = lcm m (lcm n k) :=
dvd_antisymm
  (lcm_dvd
    (lcm_dvd (dvd_lcm_left m (lcm n k)) (Nat.dvd_trans (dvd_lcm_left n k) (dvd_lcm_right m (lcm n k))))
    (Nat.dvd_trans (dvd_lcm_right n k) (dvd_lcm_right m (lcm n k))))
  (lcm_dvd
    (Nat.dvd_trans (dvd_lcm_left m n) (dvd_lcm_left (lcm m n) k))
    (lcm_dvd (Nat.dvd_trans (dvd_lcm_right m n) (dvd_lcm_left (lcm m n) k))
      (dvd_lcm_right (lcm m n) k)))

theorem lcm_ne_zero (hm : m ≠ 0) (hn : n ≠ 0) : lcm m n ≠ 0 := by
  intro h
  have h1 := gcd_mul_lcm m n
  rw [h, Nat.mul_zero] at h1
  match mul_eq_zero.1 h1.symm with
  | .inl hm1 => exact hm hm1
  | .inr hn1 => exact hn hn1

/-!
### `coprime`

See also `nat.coprime_of_dvd` and `nat.coprime_of_dvd'` to prove `nat.coprime m n`.
-/

instance (m n : Nat) : Decidable (coprime m n) := inferInstanceAs (Decidable (_ = 1))

theorem coprime_iff_gcd_eq_one : coprime m n ↔ gcd m n = 1 := .rfl

theorem coprime.gcd_eq_one : coprime m n → gcd m n = 1 := id

theorem coprime.symm : coprime n m → coprime m n := (gcd_comm m n).trans

theorem coprime_comm : coprime n m ↔ coprime m n := ⟨coprime.symm, coprime.symm⟩

theorem coprime.dvd_of_dvd_mul_right (H1 : coprime k n) (H2 : k ∣ m * n) : k ∣ m := by
  let t := dvd_gcd (Nat.dvd_mul_left k m) H2
  rwa [gcd_mul_left, H1.gcd_eq_one, Nat.mul_one] at t

theorem coprime.dvd_of_dvd_mul_left (H1 : coprime k m) (H2 : k ∣ m * n) : k ∣ n :=
  H1.dvd_of_dvd_mul_right (by rwa [Nat.mul_comm])

theorem coprime.gcd_mul_left_cancel (m : Nat) (H : coprime k n) : gcd (k * m) n = gcd m n :=
  have H1 : coprime (gcd (k * m) n) k := by
    rw [coprime, Nat.gcd_assoc, H.symm.gcd_eq_one, gcd_one_right]
  dvd_antisymm
    (dvd_gcd (H1.dvd_of_dvd_mul_left (gcd_dvd_left _ _)) (gcd_dvd_right _ _))
    (gcd_dvd_gcd_mul_left _ _ _)

theorem coprime.gcd_mul_right_cancel (m : Nat) (H : coprime k n) : gcd (m * k) n = gcd m n := by
  rw [Nat.mul_comm m k, H.gcd_mul_left_cancel m]

theorem coprime.gcd_mul_left_cancel_right (n : Nat)
    (H : coprime k m) : gcd m (k * n) = gcd m n := by
  rw [gcd_comm m n, gcd_comm m (k * n), H.gcd_mul_left_cancel n]

theorem coprime.gcd_mul_right_cancel_right (n : Nat)
    (H : coprime k m) : gcd m (n * k) = gcd m n := by
  rw [Nat.mul_comm n k, H.gcd_mul_left_cancel_right n]

theorem coprime_div_gcd_div_gcd
    (H : 0 < gcd m n) : coprime (m / gcd m n) (n / gcd m n) := by
  rw [coprime_iff_gcd_eq_one, gcd_div (gcd_dvd_left m n) (gcd_dvd_right m n), Nat.div_self H]

theorem not_coprime_of_dvd_of_dvd (dgt1 : 1 < d) (Hm : d ∣ m) (Hn : d ∣ n) : ¬ coprime m n :=
  fun co => Nat.not_le_of_gt dgt1 <| Nat.le_of_dvd Nat.zero_lt_one <| by
    rw [← co.gcd_eq_one]; exact dvd_gcd Hm Hn

theorem exists_coprime (H : 0 < gcd m n) :
    ∃ m' n', coprime m' n' ∧ m = m' * gcd m n ∧ n = n' * gcd m n :=
  ⟨_, _, coprime_div_gcd_div_gcd H,
    (Nat.div_mul_cancel (gcd_dvd_left m n)).symm,
    (Nat.div_mul_cancel (gcd_dvd_right m n)).symm⟩

theorem exists_coprime' (H : 0 < gcd m n) :
    ∃ g m' n', 0 < g ∧ coprime m' n' ∧ m = m' * g ∧ n = n' * g :=
  let ⟨m', n', h⟩ := exists_coprime H; ⟨_, m', n', H, h⟩

theorem coprime.mul (H1 : coprime m k) (H2 : coprime n k) : coprime (m * n) k :=
  (H1.gcd_mul_left_cancel n).trans H2

theorem coprime.mul_right (H1 : coprime k m) (H2 : coprime k n) : coprime k (m * n) :=
  (H1.symm.mul H2.symm).symm

theorem coprime.coprime_dvd_left (H1 : m ∣ k) (H2 : coprime k n) : coprime m n := by
  apply eq_one_of_dvd_one
  rw [coprime] at H2
  have := Nat.gcd_dvd_gcd_of_dvd_left n H1
  rwa [← H2]

theorem coprime.coprime_dvd_right (H1 : n ∣ m) (H2 : coprime k m) : coprime k n :=
  (H2.symm.coprime_dvd_left H1).symm

theorem coprime.coprime_mul_left (H : coprime (k * m) n) : coprime m n :=
  H.coprime_dvd_left (Nat.dvd_mul_left _ _)

theorem coprime.coprime_mul_right (H : coprime (m * k) n) : coprime m n :=
  H.coprime_dvd_left (Nat.dvd_mul_right _ _)

theorem coprime.coprime_mul_left_right (H : coprime m (k * n)) : coprime m n :=
  H.coprime_dvd_right (Nat.dvd_mul_left _ _)

theorem coprime.coprime_mul_right_right (H : coprime m (n * k)) : coprime m n :=
  H.coprime_dvd_right (Nat.dvd_mul_right _ _)

theorem coprime.coprime_div_left (cmn : coprime m n) (dvd : a ∣ m) : coprime (m / a) n := by
  match eq_zero_or_pos a with
  | .inl h0 =>
    rw [h0] at dvd
    rw [Nat.eq_zero_of_zero_dvd dvd] at cmn ⊢
    simp; assumption
  | .inr hpos =>
    let ⟨k, hk⟩ := dvd
    rw [hk, Nat.mul_div_cancel_left _ hpos]
    rw [hk] at cmn
    exact cmn.coprime_mul_left

theorem coprime.coprime_div_right (cmn : coprime m n) (dvd : a ∣ n) : coprime m (n / a) :=
  (cmn.symm.coprime_div_left dvd).symm

theorem coprime_mul_iff_left : coprime (m * n) k ↔ coprime m k ∧ coprime n k :=
  ⟨fun h => ⟨h.coprime_mul_right, h.coprime_mul_left⟩,
   fun ⟨h, _⟩ => by rwa [coprime_iff_gcd_eq_one, h.gcd_mul_left_cancel n]⟩

theorem coprime_mul_iff_right : coprime k (m * n) ↔ coprime k m ∧ coprime k n := by
  rw [@coprime_comm k, @coprime_comm k, @coprime_comm k, coprime_mul_iff_left]

theorem coprime.gcd_left (k : Nat) (hmn : coprime m n) : coprime (gcd k m) n :=
  hmn.coprime_dvd_left <| gcd_dvd_right k m

theorem coprime.gcd_right (k : Nat) (hmn : coprime m n) : coprime m (gcd k n) :=
  hmn.coprime_dvd_right <| gcd_dvd_right k n

theorem coprime.gcd_both (k l : Nat) (hmn : coprime m n) : coprime (gcd k m) (gcd l n) :=
  (hmn.gcd_left k).gcd_right l

theorem coprime.mul_dvd_of_dvd_of_dvd (hmn : coprime m n) (hm : m ∣ a) (hn : n ∣ a) : m * n ∣ a :=
  let ⟨_, hk⟩ := hm
  hk.symm ▸ Nat.mul_dvd_mul_left _ (hmn.symm.dvd_of_dvd_mul_left (hk ▸ hn))

@[simp]

@[simp] theorem coprime_zero_right (n : Nat) : coprime n 0 ↔ n = 1 := by simp [coprime]

theorem coprime_one_left : ∀ n, coprime 1 n := gcd_one_left

theorem coprime_one_right : ∀ n, coprime n 1 := gcd_one_right

@[simp] theorem coprime_one_left_eq_true (n) : coprime 1 n = True := eq_true (coprime_one_left _)

@[simp] theorem coprime_one_right_eq_true (n) : coprime n 1 = True := eq_true (coprime_one_right _)

@[simp] theorem coprime_self (n : Nat) : coprime n n ↔ n = 1 := by simp [coprime]

theorem coprime.pow_left (n : Nat) (H1 : coprime m k) : coprime (m ^ n) k := by
  induction n with
  | zero => exact coprime_one_left _
  | succ n ih => have hm := H1.mul ih; rwa [Nat.pow_succ, Nat.mul_comm]

theorem coprime.pow_right (n : Nat) (H1 : coprime k m) : coprime k (m ^ n) :=
  (H1.symm.pow_left n).symm

theorem coprime.pow {k l : Nat} (m n : Nat) (H1 : coprime k l) : coprime (k ^ m) (l ^ n) :=
  (H1.pow_left _).pow_right _

theorem coprime.eq_one_of_dvd {k m : Nat} (H : coprime k m) (d : k ∣ m) : k = 1 := by
  rw [← H.gcd_eq_one, gcd_eq_left d]

/-- Represent a divisor of `m * n` as a product of a divisor of `m` and a divisor of `n`. -/
def prod_dvd_and_dvd_of_dvd_prod {k m n : Nat} (H : k ∣ m * n) :
    {d : {m' // m' ∣ m} × {n' // n' ∣ n} // k = d.1.val * d.2.val} :=
  if h0 : gcd k m = 0 then
    ⟨⟨⟨0, eq_zero_of_gcd_eq_zero_right h0 ▸ Nat.dvd_refl 0⟩,
      ⟨n, Nat.dvd_refl n⟩⟩,
      eq_zero_of_gcd_eq_zero_left h0 ▸ (Nat.zero_mul n).symm⟩
  else by
    have hd : gcd k m * (k / gcd k m) = k := Nat.mul_div_cancel' (gcd_dvd_left k m)
    refine ⟨⟨⟨gcd k m, gcd_dvd_right k m⟩, ⟨k / gcd k m, ?_⟩⟩, hd.symm⟩
    apply Nat.dvd_of_mul_dvd_mul_left (Nat.pos_of_ne_zero h0)
    rw [hd, ← gcd_mul_right]
    exact Nat.dvd_gcd (Nat.dvd_mul_right _ _) H

theorem gcd_mul_dvd_mul_gcd (k m n : Nat) : gcd k (m * n) ∣ gcd k m * gcd k n := by
  let ⟨⟨⟨m', hm'⟩, ⟨n', hn'⟩⟩, (h : gcd k (m * n) = m' * n')⟩ :=
    prod_dvd_and_dvd_of_dvd_prod <| gcd_dvd_right k (m * n)
  rw [h]
  have h' : m' * n' ∣ k := h ▸ gcd_dvd_left ..
  exact Nat.mul_dvd_mul
    (dvd_gcd (Nat.dvd_trans (Nat.dvd_mul_right m' n') h') hm')
    (dvd_gcd (Nat.dvd_trans (Nat.dvd_mul_left n' m') h') hn')

theorem coprime.gcd_mul (k : Nat) (h : coprime m n) : gcd k (m * n) = gcd k m * gcd k n :=
  dvd_antisymm
    (gcd_mul_dvd_mul_gcd k m n)
    ((h.gcd_both k k).mul_dvd_of_dvd_of_dvd
      (gcd_dvd_gcd_mul_right_right ..)
      (gcd_dvd_gcd_mul_left_right ..))

theorem gcd_mul_gcd_of_coprime_of_mul_eq_mul
    (cop : coprime c d) (h : a * b = c * d) : a.gcd c * b.gcd c = c := by
  apply dvd_antisymm
  · apply ((cop.gcd_left _).mul (cop.gcd_left _)).dvd_of_dvd_mul_right
    rw [← h]
    apply Nat.mul_dvd_mul (gcd_dvd ..).1 (gcd_dvd ..).1
  · rw [gcd_comm a, gcd_comm b]
    refine Nat.dvd_trans ?_ (gcd_mul_dvd_mul_gcd ..)
    rw [h, gcd_mul_right_right d c]; apply Nat.dvd_refl


/- Axiom : not_iff_imp_false (P : Prop) :
¬ P ↔ P → false
-/
lemma not_iff_imp_false (P : Prop) : ¬ P ↔ P → false := iff.rfl -- hide
/-
# Proposition world.

## Level 8 : `(P → Q) → (¬ Q → ¬ P)`

There is a false proposition `false`, with no proof. It is
easy to check that $\lnot Q$ is equivalent to $Q\implies {\tt false}$,
and in the natural number game we call this

`not_iff_imp_false (P : Prop) : ¬ P ↔ (P → false)`

So you can start the proof of the contrapositive below with

`repeat {rw not_iff_imp_false},`

to get rid of the two occurences of `¬`, and I'm sure you can
take it from there (note that we just added `not_iff_imp_false` to the
theorem statements in the menu on the left). At some point your goal might be to prove `false`.
At that point I guess you must be proving something by contradiction.
Or are you?
-/

/- Lemma : no-side-bar
If $P$ and $Q$ are propositions, and $P\implies Q$, then
$\lnot Q\implies \lnot P$.
-/
lemma contrapositive (P Q : Prop) : (P → Q) → (¬ Q → ¬ P) :=
begin
  repeat {rw not_iff_imp_false},
  intro f,
  intro h,
  intro p,
  apply h,
  apply f,
  exact p,

end

/-
## Technical note

All of that rewriting you did with `rw` in addition world
was rewriting hypothesis of the form `h : X = Y`, but
you can also `rw h` if `h : P ↔ Q` (because propositional
extensionality says that if $P\iff Q$ then $P=Q$, and
mathematicians use this whether or not they notice.)
-/