/-

# Advanced proposition world.

## Level 5: `iff_trans` easter eggs.

Let's try `iff_trans` again. Try proving it in other ways.

### A trick.

Instead of using `cases` on `h : P ↔ Q` you can just access the proofs of `P → Q` and `Q → P`
directly with `h.1` and `h.2`. So you can solve this level with

```
intros hpq hqr,
split,
intro p,
apply hqr.1,
...
```

### Another trick

Instead of using `cases` on `h : P ↔ Q`, you can just `rw h`, and this will change all `P`s to `Q`s
in the goal. You can use this to create a much shorter proof. Note that
this is an argument for *not* running the `cases` tactic on an iff statement;
you cannot rewrite one-way implications, but you can rewrite two-way implications.

### Another trick

`cc` works on this sort of goal too.
-/

/- Lemma : no-side-bar
If $P$, $Q$ and $R$ are true/false statements, then `P ↔ Q` and `Q ↔ R` together imply `P ↔ R`.
-/
lemma iff_trans (P Q R : Prop) : (P ↔ Q) → (Q ↔ R) → (P ↔ R) :=
begin
  intros hpq hqr,
  split,
  intro p,
  apply hqr.1,
  apply hpq.1,
  assumption,
  intro r,
  apply hpq.2,
  apply hqr.2,
  assumption,

end


/-
Copyright (c) 2021 Sara Díaz Real. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sara Díaz Real
-/
import data.int.basic
import algebra.associated
import tactic.linarith
import tactic.ring

/-!
# IMO 2001 Q6
Let $a$, $b$, $c$, $d$ be integers with $a > b > c > d > 0$. Suppose that

$$ a*c + b*d = (a + b - c + d) * (-a + b + c + d). $$

Prove that $a*b + c*d$ is not prime.

-/

variables {a b c d : ℤ}

theorem imo2001_q6 (hd : 0 < d) (hdc : d < c) (hcb : c < b) (hba : b < a)
  (h : a*c + b*d = (a + b - c + d) * (-a + b + c + d)) :
  ¬ prime (a*b + c*d) :=
begin
  assume h0 : prime (a*b + c*d),
  have ha : 0 < a, { linarith },
  have hb : 0 < b, { linarith },
  have hc : 0 < c, { linarith },
  -- the key step is to show that `a*c + b*d` divides the product `(a*b + c*d) * (a*d + b*c)`
  have dvd_mul : a*c + b*d ∣ (a*b + c*d) * (a*d + b*c),
  { use b^2 + b*d + d^2,
    have equivalent_sums : a^2 - a*c + c^2 = b^2 + b*d + d^2,
    { ring_nf at h, nlinarith only [h], },
    calc  (a * b + c * d) * (a * d + b * c)
        = a*c * (b^2 + b*d + d^2) + b*d * (a^2 - a*c + c^2) : by ring
    ... = a*c * (b^2 + b*d + d^2) + b*d * (b^2 + b*d + d^2) : by rw equivalent_sums
    ... = (a * c + b * d) * (b ^ 2 + b * d + d ^ 2)         : by ring, },
  -- since `a*b + c*d` is prime (by assumption), it must divide `a*c + b*d` or `a*d + b*c`
  obtain (h1 : a*b + c*d ∣ a*c + b*d) | (h2 : a*c + b*d ∣ a*d + b*c) :=
    h0.left_dvd_or_dvd_right_of_dvd_mul dvd_mul,
  -- in both cases, we derive a contradiction
  { have aux : 0 < a*c + b*d,         { nlinarith only [ha, hb, hc, hd] },
    have : a*b + c*d ≤ a*c + b*d,     { from int.le_of_dvd aux h1 },
    have : ¬ (a*b + c*d ≤ a*c + b*d), { nlinarith only [hba, hcb, hdc, h] },
    contradiction, },
  { have aux : 0 < a*d + b*c,         { nlinarith only [ha, hb, hc, hd] },
    have : a*c + b*d ≤ a*d + b*c,     { from int.le_of_dvd aux h2 },
    have : ¬ (a*c + b*d ≤ a*d + b*c), { nlinarith only [hba, hdc, h] },
    contradiction, },
end


section

-- Questao 62 Logica proposicional : LCP-62 ¬A → B, ¬B ⊢ A

variables A B: Prop

open classical

example (h1: ¬ A → B) (h2: ¬ B) : A :=
    by_contradiction(
        assume h3: ¬ A,
        have h4: B,
            from h1 h3,
        show false,
            from h2 h4)

end

section

-- Questao 65 Logica proposicional : LCP-65  ¬(A∧B), B ⊢ ¬A

variables A B: Prop

example (h1: ¬ (A ∧ B)) (h2:  B) : ¬ A :=
        assume h3: A,
        have h4: A ∧ B,
            from and.intro h3 h2,
        show false,
            from h1 h4

end

section

-- Questao 35 Logica proposicional : LCP35: (A→B) ⊢ ((C∨A)→(B∨C))

variables A B C : Prop

example (h1: A → B) : ((C∨A)→(B∨C)) :=
    assume h2: (C∨A),
    show (B∨C), from or.elim h2
        (assume h3: C,
        show B ∨ C, from or.inr h3)
        (assume h4: A,
        show B ∨ C, from or.inl (h1 h4))

end

section

-- Questao 99 Logica proposicional : LCP-99 ¬(¬A∧B∧¬C), B ⊢ (A∨C)

open classical
variables {A B C : Prop}

lemma step1 (h1 : ¬ (A ∨ B)) (h2 : ¬ A) : ¬ A ∧ ¬ B :=
have h5: ¬ B, from (
assume h3: B,
have h4: A ∨ B,
from or.inr h3,
show false,
from h1 h4),
show ¬ A ∧ ¬ B, from and.intro h2 h5

lemma step2 (h₁ : ¬ (A ∨ B)) (h2 : ¬ (¬ A ∧ ¬ B)) : false :=
have h3: A, from
  (by_contradiction (assume h5: ¬ A,
    have h6: ¬ A ∧ ¬ B, from step1 h₁ h5,
    show false, from h2 h6)),
have h7: A ∨ B, from or.inl h3,
show false, from h₁ h7

theorem step3 (h : ¬ (A ∨ B)) : ¬ A ∧ ¬ B :=
by_contradiction
  (assume h' : ¬ (¬ A ∧ ¬ B),
    show false, from step2 h h')

example (h1: ¬(¬A ∧ B ∧ ¬C)) (h2:  B) : A ∨ C :=
    by_contradiction(
        assume h3: ¬ (A ∨ C),
        have h5: ¬ A ∧ ¬ C ,
        from step3 h3,
        have h7: ¬ A, from and.left h5,
        have h8: ¬ C, from and.right h5,
        have h11: B ∧ ¬ C , from and.intro h2 h8,
        have h4: ¬ A ∧ B ∧ ¬ C , from and.intro h7 h11,
        show false,
        from h1 h4
    )

end

section

-- Questao 28 Logica de primeira ordem  :  ∀x,(R(x)↔S(x)) ⊢ ∃y,R(y)↔∃z,S(z)

variable U : Type
variable R : U → Prop
variable S : U → Prop

example (h1: ∀ x, (R x  ↔ S x)) : (∃y,R y) ↔ (∃z,S z) :=

show (∃y,R y) ↔ (∃z, S z), from iff.intro
  (assume h : ∃y,R y,
    show ∃z ,S z, from exists.elim h (
    assume (x : U) (hy: R x),
    have h3: (R x  ↔ S x), from h1 x,
    have h4: S x, from iff.elim_left h3 hy,
    show ∃z ,S z, from exists.intro x h4))
  (assume h : ∃z,S z ,
    show ∃y,R y , from exists.elim h (
    assume (x : U) (hy: S x),
    have h3: (R x  ↔ S x), from h1 x,
    have h4: R x, from iff.elim_right h3 hy,
    show ∃z ,R z, from exists.intro x h4))

end

section

-- Questao 47 Logica de primeira ordem  :  ∀x.(∃y.P(y)→P(x)) ⊢ ∀x.∀y.(P(y)→P(x))

    variable U : Type
    variable P : U → Prop

    example (h1 : ∀ x,((∃ y, P y) → P x)) : ∀ x, ∀ y,( P y → P x) :=
    assume a,
    assume b,
    assume h2: P b,
    have h3: (∃ b, P b) → P a, from h1 a ,
    have h4: ∃ b, P b, from exists.intro b h2,
    have h5: P a, from h3 h4 ,
    show P a, from h5

end

section

-- Questao 13 Logica de primeira ordem  :   ∀x.(P(x)↔Q) ⊢ (∀x.P(x))↔Q

variables U Q: Prop
variable P : U → Prop
variable x: U

example (h1: (∀ x,(P x ↔ Q))) : (∀ x,P x )↔ Q :=
show (∀ x,P x ) ↔ Q, from iff.intro
  (assume h : ∀ x,P x ,
    have h3: P x ↔ Q, from h1 x,
    have h4 : P x, from h x,
    show Q, from iff.elim_left h3 h4)
  (assume h : Q ,
    assume y,
    have h3: P x ↔ Q, from h1 x,
    have h4: P x, from iff.elim_right h3 h,
    show P y  , from h4)

end

section

-- Questao 13 Logica de primeira ordem : LCPO31   P(i) ⊢ ¬∀x.¬P(x)

variable U: Prop
variable P : U → Prop
variables i x: U

-- BEGIN

  example  h1: P i  : ¬∀ x,¬ P x :=
  assume h : ∀ x,¬ P x ,
  show false, from (
    have hi : ¬ P i, from h i,
    show false, from hi h1)

-- END

end

section

-- Questao 773 dos desafios : ⊢ (((A → B) → ((⊥ → C) → D)) → ((D → A) → (E → (F → A))))

open classical

variables A B C D E F: Prop

example: ((A → B) → ((false → C) → D)) → ((D → A) → (E → (F → A)))  :=
    show (((A → B) → ((false → C) → D)) → ((D → A) → (E → (F → A)))), from
    (
        assume h1: (A → B) → ((false → C) → D),
        show (D → A) → (E → (F → A)), from (
            assume h2: (D → A), show  (E → (F → A)), from (
                assume h3: E, show (F → A), from (
                    assume h4: F,

                    have hfalcd : (false → C) → D, from (
                        have hab : A → B, from (
                            assume ha : A,
                            show B, from sorry
                        ),
                        show (false → C) → D, from h1 hab
                    ),

                    have hfalc : false → C, from (
                        assume hfal : false,
                        show C, from by_contradiction (
                            assume hnc : ¬ C,
                            show false, from hfal
                        )
                    ),

                    have hd : D, from hfalcd hfalc,

                    show A, from h2 hd
                )
            )
        )
    )
end

section

-- Questao 386 dos desafios : ((A → B) ∧ (C → D)), ((B ∨ D) → E), (¬E) ⊢ ¬(A ∨ C)

variables A B C D E: Prop

example (h1: (A → B) ∧ (C → D)) (h2: (B ∨ D) → E) (h4: ¬E ): ¬(A ∨ C) :=
    show ¬(A ∨ C), from
    (assume h: A ∨ C, show false, from or.elim h
        (assume p1 : A, show false, from
            (have p2 : A → B, from and.left h1,
             have p3 : B, from p2 p1,
             have p4 : B ∨ D, from or.inl p3,
             have p5 : E, from h2 p4,
             show false, from h4 p5
            )
        )
        (assume p1 : C, show false, from
            (have p2 : C → D, from and.right h1,
             have p3 : D, from p2 p1,
             have p4 : B ∨ D, from or.inr p3,
             have p5 : E, from h2 p4,
             show false, from h4 p5
            )
        )
    )

end


/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.multiset.powerset
import Mathlib.PostPort

universes u_1 u_2

namespace Mathlib

/-!
# The antidiagonal on a multiset.

The antidiagonal of a multiset `s` consists of all pairs `(t₁, t₂)`
such that `t₁ + t₂ = s`. These pairs are counted with multiplicities.
-/

namespace multiset

/-- The antidiagonal of a multiset `s` consists of all pairs `(t₁, t₂)`
    such that `t₁ + t₂ = s`. These pairs are counted with multiplicities. -/
def antidiagonal {α : Type u_1} (s : multiset α) : multiset (multiset α × multiset α) :=
  quot.lift_on s (fun (l : List α) => ↑(list.revzip (powerset_aux l))) sorry

theorem antidiagonal_coe {α : Type u_1} (l : List α) : antidiagonal ↑l = ↑(list.revzip (powerset_aux l)) :=
  rfl

@[simp] theorem antidiagonal_coe' {α : Type u_1} (l : List α) : antidiagonal ↑l = ↑(list.revzip (powerset_aux' l)) :=
  quot.sound revzip_powerset_aux_perm_aux'

/-- A pair `(t₁, t₂)` of multisets is contained in `antidiagonal s`
    if and only if `t₁ + t₂ = s`. -/
@[simp] theorem mem_antidiagonal {α : Type u_1} {s : multiset α} {x : multiset α × multiset α} : x ∈ antidiagonal s ↔ prod.fst x + prod.snd x = s := sorry

@[simp] theorem antidiagonal_map_fst {α : Type u_1} (s : multiset α) : map prod.fst (antidiagonal s) = powerset s := sorry

@[simp] theorem antidiagonal_map_snd {α : Type u_1} (s : multiset α) : map prod.snd (antidiagonal s) = powerset s := sorry

@[simp] theorem antidiagonal_zero {α : Type u_1} : antidiagonal 0 = (0, 0) ::ₘ 0 :=
  rfl

@[simp] theorem antidiagonal_cons {α : Type u_1} (a : α) (s : multiset α) : antidiagonal (a ::ₘ s) = map (prod.map id (cons a)) (antidiagonal s) + map (prod.map (cons a) id) (antidiagonal s) := sorry

@[simp] theorem card_antidiagonal {α : Type u_1} (s : multiset α) : coe_fn card (antidiagonal s) = bit0 1 ^ coe_fn card s := sorry

theorem prod_map_add {α : Type u_1} {β : Type u_2} [comm_semiring β] {s : multiset α} {f : α → β} {g : α → β} : prod (map (fun (a : α) => f a + g a) s) =
  sum
    (map (fun (p : multiset α × multiset α) => prod (map f (prod.fst p)) * prod (map g (prod.snd p))) (antidiagonal s)) := sorry


-- Interseccion_de_los_primos_y_los_mayores_que_dos.lean
-- Intersección de los primos y los mayores que dos
-- José A. Alonso Jiménez
-- Sevilla, 1 de junio de 2021
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Los números primos, los mayores que 2 y los impares se definen por
--    def primos      : set ℕ := {n | prime n}
--    def mayoresQue2 : set ℕ := {n | n > 2}
--    def impares     : set ℕ := {n | ¬ even n}
--
-- Demostrar que
--    primos ∩ mayoresQue2 ⊆ impares
-- ----------------------------------------------------------------------

import data.nat.parity
import data.nat.prime
import tactic

open nat

def primos      : set ℕ := {n | nat.prime n}
def mayoresQue2 : set ℕ := {n | n > 2}
def impares     : set ℕ := {n | ¬ even n}

example : primos ∩ mayoresQue2 ⊆ impares :=
begin
  unfold primos mayoresQue2 impares,
  intro n,
  simp,
  intro hn,
  cases prime.eq_two_or_odd hn with h h,
  { rw h,
    intro,
    linarith, },
  { rw even_iff,
    rw h,
    norm_num },
end


/-
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, Floris van Doorn, Amelia Livingston, Yury Kudryashov,
Neil Strickland, Aaron Anderson
-/

import algebra.group_with_zero.basic

/-!
# Divisibility

This file defines the basics of the divisibility relation in the context of `(comm_)` `monoid`s
`(_with_zero)`.

## Main definitions

 * `monoid.has_dvd`

## Implementation notes

The divisibility relation is defined for all monoids, and as such, depends on the order of
  multiplication if the monoid is not commutative. There are two possible conventions for
  divisibility in the noncommutative context, and this relation follows the convention for ordinals,
  so `a | b` is defined as `∃ c, b = a * c`.

## Tags

divisibility, divides
-/

variables {α : Type*}

section monoid

variables [monoid α] {a b c : α}

/-- There are two possible conventions for divisibility, which coincide in a `comm_monoid`.
    This matches the convention for ordinals. -/
@[priority 100]
instance monoid_has_dvd : has_dvd α :=
has_dvd.mk (λ a b, ∃ c, b = a * c)

-- TODO: this used to not have `c` explicit, but that seems to be important
--       for use with tactics, similar to `exists.intro`
theorem dvd.intro (c : α) (h : a * c = b) : a ∣ b :=
exists.intro c h^.symm

alias dvd.intro ← dvd_of_mul_right_eq

theorem exists_eq_mul_right_of_dvd (h : a ∣ b) : ∃ c, b = a * c := h

theorem dvd.elim {P : Prop} {a b : α} (H₁ : a ∣ b) (H₂ : ∀ c, b = a * c → P) : P :=
exists.elim H₁ H₂

@[refl, simp] theorem dvd_refl (a : α) : a ∣ a :=
dvd.intro 1 (mul_one _)

lemma dvd_rfl {a : α} : a ∣ a :=
dvd_refl a

local attribute [simp] mul_assoc mul_comm mul_left_comm

@[trans] theorem dvd_trans (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
match h₁, h₂ with
| ⟨d, (h₃ : b = a * d)⟩, ⟨e, (h₄ : c = b * e)⟩ :=
  ⟨d * e, show c = a * (d * e), by simp [h₃, h₄]⟩
end

alias dvd_trans ← has_dvd.dvd.trans

theorem one_dvd (a : α) : 1 ∣ a := dvd.intro a (one_mul _)

@[simp] theorem dvd_mul_right (a b : α) : a ∣ a * b := dvd.intro b rfl

theorem dvd_mul_of_dvd_left (h : a ∣ b) (c : α) : a ∣ b * c :=
h.trans (dvd_mul_right b c)

alias dvd_mul_of_dvd_left ← has_dvd.dvd.mul_right

theorem dvd_of_mul_right_dvd (h : a * b ∣ c) : a ∣ c :=
(dvd_mul_right a b).trans h

section map_dvd

variables {M N : Type*}

lemma mul_hom.map_dvd [monoid M] [monoid N] (f : mul_hom M N) {a b} : a ∣ b → f a ∣ f b
| ⟨c, h⟩ := ⟨f c, h.symm ▸ f.map_mul a c⟩

lemma monoid_hom.map_dvd [monoid M] [monoid N] (f : M →* N) {a b} : a ∣ b → f a ∣ f b :=
f.to_mul_hom.map_dvd

end map_dvd

end monoid

section comm_monoid

variables [comm_monoid α] {a b c : α}

theorem dvd.intro_left (c : α) (h : c * a = b) : a ∣ b :=
dvd.intro _ (begin rewrite mul_comm at h, apply h end)

alias dvd.intro_left ← dvd_of_mul_left_eq

theorem exists_eq_mul_left_of_dvd (h : a ∣ b) : ∃ c, b = c * a :=
dvd.elim h (assume c, assume H1 : b = a * c, exists.intro c (eq.trans H1 (mul_comm a c)))

lemma dvd_iff_exists_eq_mul_left : a ∣ b ↔ ∃ c, b = c * a :=
⟨exists_eq_mul_left_of_dvd, by { rintro ⟨c, rfl⟩, exact ⟨c, mul_comm _ _⟩, }⟩

theorem dvd.elim_left {P : Prop} (h₁ : a ∣ b) (h₂ : ∀ c, b = c * a → P) : P :=
exists.elim (exists_eq_mul_left_of_dvd h₁) (assume c, assume h₃ : b = c * a, h₂ c h₃)

@[simp] theorem dvd_mul_left (a b : α) : a ∣ b * a := dvd.intro b (mul_comm a b)

theorem dvd_mul_of_dvd_right (h : a ∣ b) (c : α) : a ∣ c * b :=
begin rw mul_comm, exact h.mul_right _ end

alias dvd_mul_of_dvd_right ← has_dvd.dvd.mul_left

local attribute [simp] mul_assoc mul_comm mul_left_comm

theorem mul_dvd_mul : ∀ {a b c d : α}, a ∣ b → c ∣ d → a * c ∣ b * d
| a ._ c ._ ⟨e, rfl⟩ ⟨f, rfl⟩ := ⟨e * f, by simp⟩

theorem mul_dvd_mul_left (a : α) {b c : α} (h : b ∣ c) : a * b ∣ a * c :=
mul_dvd_mul (dvd_refl a) h

theorem mul_dvd_mul_right (h : a ∣ b) (c : α) : a * c ∣ b * c :=
mul_dvd_mul h (dvd_refl c)

theorem dvd_of_mul_left_dvd (h : a * b ∣ c) : b ∣ c :=
dvd.elim h (λ d ceq, dvd.intro (a * d) (by simp [ceq]))

end comm_monoid

section monoid_with_zero

variables [monoid_with_zero α] {a : α}

theorem eq_zero_of_zero_dvd (h : 0 ∣ a) : a = 0 :=
dvd.elim h (assume c, assume H' : a = 0 * c, eq.trans H' (zero_mul c))

/-- Given an element `a` of a commutative monoid with zero, there exists another element whose
    product with zero equals `a` iff `a` equals zero. -/
@[simp] lemma zero_dvd_iff : 0 ∣ a ↔ a = 0 :=
⟨eq_zero_of_zero_dvd, λ h, by rw h⟩

@[simp] theorem dvd_zero (a : α) : a ∣ 0 := dvd.intro 0 (by simp)

end monoid_with_zero

/-- Given two elements `b`, `c` of a `cancel_monoid_with_zero` and a nonzero element `a`,
 `a*b` divides `a*c` iff `b` divides `c`. -/
theorem mul_dvd_mul_iff_left [cancel_monoid_with_zero α] {a b c : α}
  (ha : a ≠ 0) : a * b ∣ a * c ↔ b ∣ c :=
exists_congr $ λ d, by rw [mul_assoc, mul_right_inj' ha]

/-- Given two elements `a`, `b` of a commutative `cancel_monoid_with_zero` and a nonzero
  element `c`, `a*c` divides `b*c` iff `a` divides `b`. -/
theorem mul_dvd_mul_iff_right [comm_cancel_monoid_with_zero α] {a b c : α} (hc : c ≠ 0) :
  a * c ∣ b * c ↔ a ∣ b :=
exists_congr $ λ d, by rw [mul_right_comm, mul_left_inj' hc]

/-!
### Units in various monoids
-/

namespace units

section monoid
variables [monoid α] {a b : α} {u : units α}

/-- Elements of the unit group of a monoid represented as elements of the monoid
    divide any element of the monoid. -/
lemma coe_dvd : ↑u ∣ a := ⟨↑u⁻¹ * a, by simp⟩

/-- In a monoid, an element `a` divides an element `b` iff `a` divides all
    associates of `b`. -/
lemma dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
iff.intro
  (assume ⟨c, eq⟩, ⟨c * ↑u⁻¹, by rw [← mul_assoc, ← eq, units.mul_inv_cancel_right]⟩)
  (assume ⟨c, eq⟩, eq.symm ▸ (dvd_mul_right _ _).mul_right _)

/-- In a monoid, an element `a` divides an element `b` iff all associates of `a` divide `b`. -/
lemma mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
iff.intro
  (λ ⟨c, eq⟩, ⟨↑u * c, eq.trans (mul_assoc _ _ _)⟩)
  (λ h, dvd_trans (dvd.intro ↑u⁻¹ (by rw [mul_assoc, u.mul_inv, mul_one])) h)

end monoid

section comm_monoid
variables [comm_monoid α] {a b : α} {u : units α}

/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
lemma dvd_mul_left : a ∣ u * b ↔ a ∣ b := by { rw mul_comm, apply dvd_mul_right }

/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
lemma mul_left_dvd : ↑u * a ∣ b ↔ a ∣ b :=
by { rw mul_comm, apply mul_right_dvd }

end comm_monoid

end units

namespace is_unit

section monoid

variables [monoid α] {a b u : α} (hu : is_unit u)
include hu

/-- Units of a monoid divide any element of the monoid. -/
@[simp] lemma dvd : u ∣ a := by { rcases hu with ⟨u, rfl⟩, apply units.coe_dvd, }

@[simp] lemma dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.dvd_mul_right, }

/-- In a monoid, an element a divides an element b iff all associates of `a` divide `b`.-/
@[simp] lemma mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.mul_right_dvd, }

end monoid

section comm_monoid
variables [comm_monoid α] (a b u : α) (hu : is_unit u)
include hu

/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
@[simp] lemma dvd_mul_left : a ∣ u * b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.dvd_mul_left, }

/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
@[simp] lemma mul_left_dvd : u * a ∣ b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.mul_left_dvd, }

end comm_monoid

end is_unit

section comm_monoid_with_zero

variable [comm_monoid_with_zero α]

/-- `dvd_not_unit a b` expresses that `a` divides `b` "strictly", i.e. that `b` divided by `a`
is not a unit. -/
def dvd_not_unit (a b : α) : Prop := a ≠ 0 ∧ ∃ x, ¬is_unit x ∧ b = a * x

lemma dvd_not_unit_of_dvd_of_not_dvd {a b : α} (hd : a ∣ b) (hnd : ¬ b ∣ a) :
  dvd_not_unit a b :=
begin
  split,
  { rintro rfl, exact hnd (dvd_zero _) },
  { rcases hd with ⟨c, rfl⟩,
    refine ⟨c, _, rfl⟩,
    rintro ⟨u, rfl⟩,
    simpa using hnd }
end

end comm_monoid_with_zero


import data.set classical algebra.order data.nat
open set eq.ops

namespace relations

variable {T : Type}

/- [KS]
Given a set S, a partition of S is a set X ⊆ ℘(S) (that is, a set of subsets of S) with the following properties:
1. The union of all sets in X is equal to S.
2. For any S1, S2 ∈ X with S1 ≠ S2, we have that S1 ∩ S2 = Ø (S1 and S2 are disjoint)
3. Ø ∉ X.
-/

definition pairwise_disjoint (Xs : set (set T)) := ∀ ⦃ X1 X2 : set T ⦄, X1 ∈ Xs → X2 ∈ Xs → X1 ≠ X2 → X1 ∩ X2 = ∅

definition big_union (Xs : set (set T)) : set T := λ x, ∃ xs, xs ∈ Xs ∧ x ∈ xs
definition powerset (xs : set T) : set (set T) := λ ys, ys ⊆ xs

structure partition (xs : set T) (Xs : set (set T)) :=
  mk :: (in_powerset : Xs ⊆ powerset xs)
        (spans : big_union Xs = xs)
        (pdisjoint : pairwise_disjoint Xs)
        (ncempty : ¬ ∅ ∈ Xs)

open partition

-- Choice functions (not the end of the world)
-- (if we go down this road, we would hide the definitions from students, and just give them (1), (3) and (4))

definition cover {xs : set T} {Xs : set (set T)} (p : partition xs Xs) (x : T) : set T := epsilon (λ ys, ys ∈ Xs ∧ x ∈ ys)

definition cover_exists {xs : set T} {Xs : set (set T)} (p : partition xs Xs) (x : T) (x_in_xs : x ∈ xs) : ∃ ys, ys ∈ Xs ∧ x ∈ ys :=
  (spans p)⁻¹ ▸ x_in_xs

lemma x_in_cover_x {xs : set T} {Xs : set (set T)} (p : partition xs Xs) (x : T) (x_in_xs : x ∈ xs) : x ∈ cover p x :=
  and.right (epsilon_spec (cover_exists p x x_in_xs))

lemma cover_x_in_Xs {xs : set T} {Xs : set (set T)} (p : partition xs Xs) (x : T) (x_in_xs : x ∈ xs) : cover p x ∈ Xs :=
  and.left (epsilon_spec (cover_exists p x x_in_xs))

/- [KS]
Lemma: Let S be a set and X a partition of S. Then every element u ∈ S belongs to exactly one set
Y ∈ X
Proof: Let S be a set and X a partition of S. We will show that every element u ∈ S belongs to at least
one set Y ∈ X and to at most one set Y ∈ X.
To see that every element u ∈ S belongs to at least one set Y ∈ X, note that since X is a partition of S,
the union of all the sets in S must be equal to S. Consequently, there must be at least one set Y ∈ X
such that u ∈ Y, since otherwise the union of all sets contained in X would not be equal to S.
To see that every element u ∈ S belongs to at most one set Y ∈ X, suppose for the sake of contradiction
that u belongs to two sets Y1, Y2 ∈ X with Y1 ≠ Y2. But then x ∈ Y1 ∩ Y2, meaning that
Y1 ∩ Y2 ≠ Ø, a contradiction. We have reached a contradiction, so our assumption must have been
wrong.
Thus every element u ∈ S belongs to at most one set Y ∈ X. ■
-/

definition exactly_one [reducible] {T : Type} (P : T → Prop) := (∃ (u : T), P u) ∧ (∀ u v : T, P u → P v → u = v)

-- Lemma: Let S be a set and X a partition of S. Then every element u ∈ S belongs to exactly one set Y ∈ X.
definition unique_set : ∀ {S : set T} {X : set (set T)}, partition S X → ∀ (u : T), u ∈ S → exactly_one (λ (Y : set T), Y ∈ X ∧ u ∈ Y) :=
  -- Proof: Let S be a set and X a partition of S.
  assume (S : set T) (X : set (set T)) (p : partition S X) (u : T) (u_in_S : u ∈ S),
  -- We will show that every element u ∈ S belongs to at least one set Y ∈ X and to at most one set Y ∈ X.
  show (∃ (Y : set T), Y ∈ X ∧ u ∈ Y) ∧ (∀ (Y1 Y2 : set T), (Y1 ∈ X ∧ u ∈ Y1) → (Y2 ∈ X ∧ u ∈ Y2) → Y1 = Y2), from
  -- To see that every element u ∈ S belongs to at least one set Y ∈ X,
  have exists_Y : ∃ (Y : set T), Y ∈ X ∧ u ∈ Y, from
    -- note that since X is a partition of S, the union of all the sets in S must be equal to S.
    -- Consequently, there must be at least one set Y ∈ X such that u ∈ Y,
    -- since otherwise the union of all sets contained in X would not be equal to S.
    have u_in_bigX : u ∈ big_union X, from (spans p)⁻¹ ▸ u_in_S,
    u_in_bigX,
  -- To see that every element u ∈ S belongs to at most one set Y ∈ X,
  have unique : (∀ (Y1 Y2 : set T), (Y1 ∈ X ∧ u ∈ Y1) → (Y2 ∈ X ∧ u ∈ Y2) → Y1 = Y2), from
    -- suppose for the sake of contradiction that u belongs to two sets Y1, Y2 ∈ X with Y1 ≠ Y2.
    assume (Y1 Y2 : set T) (HY1 : Y1 ∈ X ∧ u ∈ Y1) (HY2 : Y2 ∈ X ∧ u ∈ Y2),
    by_contradiction
      (assume Y1_neq_Y2 : Y1 ≠ Y2,
       show false, from
       -- But then u ∈ Y1 ∩ Y2,
       have u_in_Y1capY2 : u ∈ Y1 ∩ Y2, from and.intro (and.right HY1) (and.right HY2),
       -- meaning that Y1 ∩ Y2 ≠ Ø, a contradiction.
       have Y1capY2_nempty : Y1 ∩ Y2 ≠ ∅, from
         assume Y1capY2_empty : Y1 ∩ Y2 = ∅,
         have u_in_emptyset : u ∈ ∅, from Y1capY2_empty ▸ u_in_Y1capY2,
         show false, from u_in_emptyset,
       -- We have reached a contradiction, so our assumption must have been wrong.
       absurd (pdisjoint p (and.left HY1) (and.left HY2) Y1_neq_Y2) Y1capY2_nempty),
  -- Thus every element u ∈ S belongs to at most one set Y ∈ X.
  show exactly_one (λ (Y : set T), Y ∈ X ∧ u ∈ Y), from and.intro exists_Y unique
  -- ■

definition set_of_sets (f : T → set T) : set (set T) := λ (s : set T), ∃ (u : T), s = f u
definition bigcup (Xs : set (set T)) : set T := λ (v: T), ∃ (X : set T), X ∈ Xs ∧ v ∈ X

lemma equiv_spans : ∀ {R : T → T → Prop}, equivalence R → ∀ v, v ∈ bigcup (set_of_sets R) :=
  assume (R : T → T → Prop) (R_equiv : equivalence R) (v : T),
  show ∃ (X : set T), X ∈ (set_of_sets R) ∧ v ∈ X, from
  let X := R v in
  have X_in_ssR : X ∈ set_of_sets R, from exists.intro v rfl,
  have v_in_X : v ∈ X, from and.left R_equiv v,
  exists.intro X (and.intro X_in_ssR v_in_X)

end relations


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

! This file was ported from Lean 3 source module algebra.invertible
! leanprover-community/mathlib commit 10b4e499f43088dd3bb7b5796184ad5216648ab1
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Group.Units
import Mathlib.Algebra.GroupWithZero.Units.Lemmas
import Mathlib.Algebra.Ring.Defs
/-!
# Invertible elements

This file defines a typeclass `Invertible a` for elements `a` with a two-sided
multiplicative inverse.

The intent of the typeclass is to provide a way to write e.g. `⅟2` in a ring
like `ℤ[1/2]` where some inverses exist but there is no general `⁻¹` operator;
or to specify that a field has characteristic `≠ 2`.
It is the `Type`-valued analogue to the `Prop`-valued `IsUnit`.

For constructions of the invertible element given a characteristic, see
`Algebra/Char_P/Invertible` and other lemmas in that file.

## Notation

 * `⅟a` is `Invertible.invOf a`, the inverse of `a`

## Implementation notes

The `Invertible` class lives in `Type`, not `Prop`, to make computation easier.
If multiplication is associative, `Invertible` is a subsingleton anyway.

The `simp` normal form tries to normalize `⅟a` to `a ⁻¹`. Otherwise, it pushes
`⅟` inside the expression as much as possible.

Since `Invertible a` is not a `Prop` (but it is a `Subsingleton`), we have to be careful about
coherence issues: we should avoid having multiple non-defeq instances for `Invertible a` in the
same context.  This file plays it safe and uses `def` rather than `instance` for most definitions,
users can choose which instances to use at the point of use.

For example, here's how you can use an `Invertible 1` instance:
```lean
variables {α : Type _} [monoid α]

def something_that_needs_inverses (x : α) [Invertible x] := sorry

section
local attribute [instance] invertibleOne
def something_one := something_that_needs_inverses 1
end
```

## Tags

invertible, inverse element, invOf, a half, one half, a third, one third, ½, ⅓

-/

universe u

variable {α : Type u}

/-- `Invertible a` gives a two-sided multiplicative inverse of `a`. -/
class Invertible [Mul α] [One α] (a : α) : Type u where
  /-- The inverse of an `Invertible` element -/
  invOf : α
  /-- `invOf a` is a left inverse of `a` -/
  invOf_mul_self : invOf * a = 1
  /-- `invOf a` is a right inverse of `a` -/
  mul_invOf_self : a * invOf = 1
#align invertible Invertible

/-- The inverse of an `Invertible` element -/
prefix:max
  "⅟" =>-- This notation has the same precedence as `Inv.inv`.
  Invertible.invOf

@[simp]
theorem invOf_mul_self [Mul α] [One α] (a : α) [Invertible a] : ⅟ a * a = 1 :=
  Invertible.invOf_mul_self
#align inv_of_mul_self invOf_mul_self

@[simp]
theorem mul_invOf_self [Mul α] [One α] (a : α) [Invertible a] : a * ⅟ a = 1 :=
  Invertible.mul_invOf_self
#align mul_inv_of_self mul_invOf_self

@[simp]
theorem invOf_mul_self_assoc [Monoid α] (a b : α) [Invertible a] : ⅟ a * (a * b) = b := by
  rw [← mul_assoc, invOf_mul_self, one_mul]
#align inv_of_mul_self_assoc invOf_mul_self_assoc

@[simp]
theorem mul_invOf_self_assoc [Monoid α] (a b : α) [Invertible a] : a * (⅟ a * b) = b := by
  rw [← mul_assoc, mul_invOf_self, one_mul]
#align mul_inv_of_self_assoc mul_invOf_self_assoc

@[simp]
theorem mul_invOf_mul_self_cancel [Monoid α] (a b : α) [Invertible b] : a * ⅟ b * b = a := by
  simp [mul_assoc]
#align mul_inv_of_mul_self_cancel mul_invOf_mul_self_cancel

@[simp]
theorem mul_mul_invOf_self_cancel [Monoid α] (a b : α) [Invertible b] : a * b * ⅟ b = a := by
  simp [mul_assoc]
#align mul_mul_inv_of_self_cancel mul_mul_invOf_self_cancel

theorem invOf_eq_right_inv [Monoid α] {a b : α} [Invertible a] (hac : a * b = 1) : ⅟ a = b :=
  left_inv_eq_right_inv (invOf_mul_self _) hac
#align inv_of_eq_right_inv invOf_eq_right_inv

theorem invOf_eq_left_inv [Monoid α] {a b : α} [Invertible a] (hac : b * a = 1) : ⅟ a = b :=
  (left_inv_eq_right_inv hac (mul_invOf_self _)).symm
#align inv_of_eq_left_inv invOf_eq_left_inv

theorem invertible_unique {α : Type u} [Monoid α] (a b : α) [Invertible a] [Invertible b]
    (h : a = b) : ⅟ a = ⅟ b := by
  apply invOf_eq_right_inv
  rw [h, mul_invOf_self]
#align invertible_unique invertible_unique

instance [Monoid α] (a : α) : Subsingleton (Invertible a) :=
  ⟨fun ⟨b, hba, hab⟩ ⟨c, _, hac⟩ => by
    congr
    exact left_inv_eq_right_inv hba hac⟩

/-- If `r` is invertible and `s = r`, then `s` is invertible. -/
def Invertible.copy [MulOneClass α] {r : α} (hr : Invertible r) (s : α) (hs : s = r) :
    Invertible s where
  invOf := ⅟ r
  invOf_mul_self := by rw [hs, invOf_mul_self]
  mul_invOf_self := by rw [hs, mul_invOf_self]
#align invertible.copy Invertible.copy

/-- If `a` is invertible and `a = b`, then `⅟a = ⅟b`. -/
@[congr]
theorem Invertible.congr [Ring α] (a b : α) [Invertible a] [Invertible b] (h : a = b) :
  ⅟a = ⅟b := by subst h; congr; apply Subsingleton.allEq

/-- An `invertible` element is a unit. -/
@[simps]
def unitOfInvertible [Monoid α] (a : α) [Invertible a] :
    αˣ where
  val := a
  inv := ⅟ a
  val_inv := by simp
  inv_val := by simp
#align unit_of_invertible unitOfInvertible

theorem isUnit_of_invertible [Monoid α] (a : α) [Invertible a] : IsUnit a :=
  ⟨unitOfInvertible a, rfl⟩
#align is_unit_of_invertible isUnit_of_invertible

/-- Units are invertible in their associated monoid. -/
def Units.invertible [Monoid α] (u : αˣ) :
    Invertible (u : α) where
  invOf := ↑u⁻¹
  invOf_mul_self := u.inv_mul
  mul_invOf_self := u.mul_inv
#align units.invertible Units.invertible

@[simp]
theorem invOf_units [Monoid α] (u : αˣ) [Invertible (u : α)] : ⅟ (u : α) = ↑u⁻¹ :=
  invOf_eq_right_inv u.mul_inv
#align inv_of_units invOf_units

theorem IsUnit.nonempty_invertible [Monoid α] {a : α} (h : IsUnit a) : Nonempty (Invertible a) :=
  let ⟨x, hx⟩ := h
  ⟨x.invertible.copy _ hx.symm⟩
#align is_unit.nonempty_invertible IsUnit.nonempty_invertible

/-- Convert `IsUnit` to `Invertible` using `Classical.choice`.

Prefer `casesI h.nonempty_invertible` over `letI := h.invertible` if you want to avoid choice. -/
noncomputable def IsUnit.invertible [Monoid α] {a : α} (h : IsUnit a) : Invertible a :=
  Classical.choice h.nonempty_invertible
#align is_unit.invertible IsUnit.invertible

@[simp]
theorem nonempty_invertible_iff_isUnit [Monoid α] (a : α) : Nonempty (Invertible a) ↔ IsUnit a :=
  ⟨Nonempty.rec <| @isUnit_of_invertible _ _ _, IsUnit.nonempty_invertible⟩
#align nonempty_invertible_iff_is_unit nonempty_invertible_iff_isUnit

/-- Each element of a group is invertible. -/
def invertibleOfGroup [Group α] (a : α) : Invertible a :=
  ⟨a⁻¹, inv_mul_self a, mul_inv_self a⟩
#align invertible_of_group invertibleOfGroup

@[simp]
theorem invOf_eq_group_inv [Group α] (a : α) [Invertible a] : ⅟ a = a⁻¹ :=
  invOf_eq_right_inv (mul_inv_self a)
#align inv_of_eq_group_inv invOf_eq_group_inv

/-- `1` is the inverse of itself -/
def invertibleOne [Monoid α] : Invertible (1 : α) :=
  ⟨1, mul_one _, one_mul _⟩
#align invertible_one invertibleOne

@[simp]
theorem invOf_one' [Monoid α] {_ : Invertible (1 : α)} : ⅟ (1 : α) = 1 :=
  invOf_eq_right_inv (mul_one _)

theorem invOf_one [Monoid α] [Invertible (1 : α)] : ⅟ (1 : α) = 1 :=
  invOf_eq_right_inv (mul_one _)
#align inv_of_one invOf_one

/-- `-⅟a` is the inverse of `-a` -/
def invertibleNeg [Mul α] [One α] [HasDistribNeg α] (a : α) [Invertible a] : Invertible (-a) :=
  ⟨-⅟ a, by simp, by simp⟩
#align invertible_neg invertibleNeg

@[simp]
theorem invOf_neg [Monoid α] [HasDistribNeg α] (a : α) [Invertible a] [Invertible (-a)] :
    ⅟ (-a) = -⅟ a :=
  invOf_eq_right_inv (by simp)
#align inv_of_neg invOf_neg

@[simp]
theorem one_sub_invOf_two [Ring α] [Invertible (2 : α)] : 1 - (⅟ 2 : α) = ⅟ 2 :=
  (isUnit_of_invertible (2 : α)).mul_right_inj.1 <| by
    rw [mul_sub, mul_invOf_self, mul_one, ← one_add_one_eq_two, add_sub_cancel]
#align one_sub_inv_of_two one_sub_invOf_two

@[simp]
theorem invOf_two_add_invOf_two [NonAssocSemiring α] [Invertible (2 : α)] :
    (⅟ 2 : α) + (⅟ 2 : α) = 1 := by rw [← two_mul, mul_invOf_self]
#align inv_of_two_add_inv_of_two invOf_two_add_invOf_two

/-- `a` is the inverse of `⅟a`. -/
instance invertibleInvOf [One α] [Mul α] {a : α} [Invertible a] : Invertible (⅟ a) :=
  ⟨a, mul_invOf_self a, invOf_mul_self a⟩
#align invertible_inv_of invertibleInvOf

@[simp]
theorem invOf_invOf [Monoid α] (a : α) [Invertible a] [Invertible (⅟ a)] : ⅟ (⅟ a) = a :=
  invOf_eq_right_inv (invOf_mul_self _)
#align inv_of_inv_of invOf_invOf

@[simp]
theorem invOf_inj [Monoid α] {a b : α} [Invertible a] [Invertible b] : ⅟ a = ⅟ b ↔ a = b :=
  ⟨invertible_unique _ _, invertible_unique _ _⟩
#align inv_of_inj invOf_inj

/-- `⅟b * ⅟a` is the inverse of `a * b` -/
def invertibleMul [Monoid α] (a b : α) [Invertible a] [Invertible b] : Invertible (a * b) :=
  ⟨⅟ b * ⅟ a, by simp [← mul_assoc], by simp [← mul_assoc]⟩
#align invertible_mul invertibleMul

@[simp]
theorem invOf_mul [Monoid α] (a b : α) [Invertible a] [Invertible b] [Invertible (a * b)] :
    ⅟ (a * b) = ⅟ b * ⅟ a :=
  invOf_eq_right_inv (by simp [← mul_assoc])
#align inv_of_mul invOf_mul

theorem mul_right_inj_of_invertible [Monoid α] (c : α) [Invertible c] :
    a * c = b * c ↔ a = b :=
  ⟨fun h => by simpa using congr_arg (· * ⅟c) h, congr_arg (· * _)⟩

theorem mul_left_inj_of_invertible [Monoid α] (c : α) [Invertible c] :
    c * a = c * b ↔ a = b :=
  ⟨fun h => by simpa using congr_arg (⅟c * ·) h, congr_arg (_ * ·)⟩

theorem invOf_mul_eq_iff_eq_mul_left [Monoid α] [Invertible (c : α)] :
    ⅟c * a = b ↔ a = c * b := by
  rw [← mul_left_inj_of_invertible (c := c), mul_invOf_self_assoc]

theorem mul_left_eq_iff_eq_invOf_mul [Monoid α] [Invertible (c : α)] :
    c * a = b ↔ a = ⅟c * b := by
  rw [← mul_left_inj_of_invertible (c := ⅟c), invOf_mul_self_assoc]

theorem mul_invOf_eq_iff_eq_mul_right [Monoid α] [Invertible (c : α)] :
    a * ⅟c = b ↔ a = b * c := by
  rw [← mul_right_inj_of_invertible (c := c), mul_invOf_mul_self_cancel]

theorem mul_right_eq_iff_eq_mul_invOf [Monoid α] [Invertible (c : α)] :
    a * c = b ↔ a = b * ⅟c := by
  rw [← mul_right_inj_of_invertible (c := ⅟c), mul_mul_invOf_self_cancel]

theorem Commute.invOf_right [Monoid α] {a b : α} [Invertible b] (h : Commute a b) :
    Commute a (⅟ b) :=
  calc
    a * ⅟ b = ⅟ b * (b * a * ⅟ b) := by simp [mul_assoc]
    _ = ⅟ b * (a * b * ⅟ b) := by rw [h.eq]
    _ = ⅟ b * a := by simp [mul_assoc]

#align commute.inv_of_right Commute.invOf_right

theorem Commute.invOf_left [Monoid α] {a b : α} [Invertible b] (h : Commute b a) :
    Commute (⅟ b) a :=
  calc
    ⅟ b * a = ⅟ b * (a * b * ⅟ b) := by simp [mul_assoc]
    _ = ⅟ b * (b * a * ⅟ b) := by rw [h.eq]
    _ = a * ⅟ b := by simp [mul_assoc]

#align commute.inv_of_left Commute.invOf_left

theorem commute_invOf {M : Type _} [One M] [Mul M] (m : M) [Invertible m] : Commute m (⅟ m) :=
  calc
    m * ⅟ m = 1 := mul_invOf_self m
    _ = ⅟ m * m := (invOf_mul_self m).symm

#align commute_inv_of commute_invOf

theorem nonzero_of_invertible [MulZeroOneClass α] (a : α) [Nontrivial α] [Invertible a] : a ≠ 0 :=
  fun ha =>
  zero_ne_one <|
    calc
      0 = ⅟ a * a := by simp [ha]
      _ = 1 := invOf_mul_self a

#align nonzero_of_invertible nonzero_of_invertible

theorem pos_of_invertible_cast [Semiring α] [Nontrivial α] (n : ℕ) [Invertible (n : α)] : 0 < n :=
  Nat.zero_lt_of_ne_zero fun h => nonzero_of_invertible (n : α) (h ▸ Nat.cast_zero)

instance (priority := 100) Invertible.ne_zero [MulZeroOneClass α] [Nontrivial α] (a : α)
    [Invertible a] : NeZero a :=
  ⟨nonzero_of_invertible a⟩
#align invertible.ne_zero Invertible.ne_zero

section MonoidWithZero

variable [MonoidWithZero α]

/-- A variant of `Ring.inverse_unit`. -/
@[simp]
theorem Ring.inverse_invertible (x : α) [Invertible x] : Ring.inverse x = ⅟ x :=
  Ring.inverse_unit (unitOfInvertible _)
#align ring.inverse_invertible Ring.inverse_invertible

end MonoidWithZero

section GroupWithZero

variable [GroupWithZero α]

/-- `a⁻¹` is an inverse of `a` if `a ≠ 0` -/
def invertibleOfNonzero {a : α} (h : a ≠ 0) : Invertible a :=
  ⟨a⁻¹, inv_mul_cancel h, mul_inv_cancel h⟩
#align invertible_of_nonzero invertibleOfNonzero

@[simp]
theorem invOf_eq_inv (a : α) [Invertible a] : ⅟ a = a⁻¹ :=
  invOf_eq_right_inv (mul_inv_cancel (nonzero_of_invertible a))
#align inv_of_eq_inv invOf_eq_inv

@[simp]
theorem inv_mul_cancel_of_invertible (a : α) [Invertible a] : a⁻¹ * a = 1 :=
  inv_mul_cancel (nonzero_of_invertible a)
#align inv_mul_cancel_of_invertible inv_mul_cancel_of_invertible

@[simp]
theorem mul_inv_cancel_of_invertible (a : α) [Invertible a] : a * a⁻¹ = 1 :=
  mul_inv_cancel (nonzero_of_invertible a)
#align mul_inv_cancel_of_invertible mul_inv_cancel_of_invertible

@[simp]
theorem div_mul_cancel_of_invertible (a b : α) [Invertible b] : a / b * b = a :=
  div_mul_cancel a (nonzero_of_invertible b)
#align div_mul_cancel_of_invertible div_mul_cancel_of_invertible

@[simp]
theorem mul_div_cancel_of_invertible (a b : α) [Invertible b] : a * b / b = a :=
  mul_div_cancel a (nonzero_of_invertible b)
#align mul_div_cancel_of_invertible mul_div_cancel_of_invertible

@[simp]
theorem div_self_of_invertible (a : α) [Invertible a] : a / a = 1 :=
  div_self (nonzero_of_invertible a)
#align div_self_of_invertible div_self_of_invertible

/-- `b / a` is the inverse of `a / b` -/
def invertibleDiv (a b : α) [Invertible a] [Invertible b] : Invertible (a / b) :=
  ⟨b / a, by simp [← mul_div_assoc], by simp [← mul_div_assoc]⟩
#align invertible_div invertibleDiv

-- Porting note: removed `simp` attibute as `simp` can prove it
theorem invOf_div (a b : α) [Invertible a] [Invertible b] [Invertible (a / b)] :
    ⅟ (a / b) = b / a :=
  invOf_eq_right_inv (by simp [← mul_div_assoc])
#align inv_of_div invOf_div

/-- `a` is the inverse of `a⁻¹` -/
def invertibleInv {a : α} [Invertible a] : Invertible a⁻¹ :=
  ⟨a, by simp, by simp⟩
#align invertible_inv invertibleInv

end GroupWithZero

/-- Monoid homs preserve invertibility. -/
def Invertible.map {R : Type _} {S : Type _} {F : Type _} [MulOneClass R] [MulOneClass S]
    [MonoidHomClass F R S] (f : F) (r : R) [Invertible r] :
    Invertible (f r) where
  invOf := f (⅟ r)
  invOf_mul_self := by rw [← map_mul, invOf_mul_self, map_one]
  mul_invOf_self := by rw [← map_mul, mul_invOf_self, map_one]
#align invertible.map Invertible.map

/-- Note that the `invertible (f r)` argument can be satisfied by using `letI := invertible.map f r`
before applying this lemma. -/
theorem map_invOf {R : Type _} {S : Type _} {F : Type _} [MulOneClass R] [Monoid S]
    [MonoidHomClass F R S] (f : F) (r : R) [Invertible r] [ifr : Invertible (f r)] :
    f (⅟ r) = ⅟ (f r) :=
  have h : ifr = Invertible.map f r := Subsingleton.elim _ _
  by subst h ; rfl

#align map_inv_of map_invOf

/-- If a function `f : R → S` has a left-inverse that is a monoid hom,
  then `r : R` is invertible if `f r` is.

The inverse is computed as `g (⅟(f r))` -/
@[simps! (config := .lemmasOnly)]
def Invertible.ofLeftInverse {R : Type _} {S : Type _} {G : Type _} [MulOneClass R] [MulOneClass S]
    [MonoidHomClass G S R] (f : R → S) (g : G) (r : R) (h : Function.LeftInverse g f)
    [Invertible (f r)] : Invertible r :=
  (Invertible.map g (f r)).copy _ (h r).symm
#align invertible.of_left_inverse Invertible.ofLeftInverse
#align invertible.of_left_inverse_inv_of Invertible.ofLeftInverse_invOf

/-- Invertibility on either side of a monoid hom with a left-inverse is equivalent. -/
@[simps]
def invertibleEquivOfLeftInverse {R : Type _} {S : Type _} {F G : Type _} [Monoid R] [Monoid S]
    [MonoidHomClass F R S] [MonoidHomClass G S R] (f : F) (g : G) (r : R)
    (h : Function.LeftInverse g f) :
    Invertible (f r) ≃
      Invertible r where
  toFun _ := Invertible.ofLeftInverse f _ _ h
  invFun _ := Invertible.map f _
  left_inv _ := Subsingleton.elim _ _
  right_inv _ := Subsingleton.elim _ _
#align invertible_equiv_of_left_inverse invertibleEquivOfLeftInverse
#align invertible_equiv_of_left_inverse_symm_apply invertibleEquivOfLeftInverse_symm_apply
#align invertible_equiv_of_left_inverse_apply invertibleEquivOfLeftInverse_apply


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson
-/
import analysis.special_functions.trigonometric.complex
import analysis.special_functions.trigonometric.deriv

/-!
# Complex trigonometric functions

Basic facts and derivatives for the complex trigonometric functions.
-/

noncomputable theory

namespace complex

open set filter
open_locale real

lemma has_strict_deriv_at_tan {x : ℂ} (h : cos x ≠ 0) :
  has_strict_deriv_at tan (1 / (cos x)^2) x :=
begin
  convert (has_strict_deriv_at_sin x).div (has_strict_deriv_at_cos x) h,
  rw ← sin_sq_add_cos_sq x,
  ring,
end

lemma has_deriv_at_tan {x : ℂ} (h : cos x ≠ 0) :
  has_deriv_at tan (1 / (cos x)^2) x :=
(has_strict_deriv_at_tan h).has_deriv_at

open_locale topology

lemma tendsto_abs_tan_of_cos_eq_zero {x : ℂ} (hx : cos x = 0) :
  tendsto (λ x, abs (tan x)) (𝓝[≠] x) at_top :=
begin
  simp only [tan_eq_sin_div_cos, ← norm_eq_abs, norm_div],
  have A : sin x ≠ 0 := λ h, by simpa [*, sq] using sin_sq_add_cos_sq x,
  have B : tendsto cos (𝓝[≠] (x)) (𝓝[≠] 0),
    from hx ▸ (has_deriv_at_cos x).tendsto_punctured_nhds (neg_ne_zero.2 A),
  exact continuous_sin.continuous_within_at.norm.mul_at_top (norm_pos_iff.2 A)
    (tendsto_norm_nhds_within_zero.comp B).inv_tendsto_zero,
end

lemma tendsto_abs_tan_at_top (k : ℤ) :
  tendsto (λ x, abs (tan x)) (𝓝[≠] ((2 * k + 1) * π / 2)) at_top :=
tendsto_abs_tan_of_cos_eq_zero $ cos_eq_zero_iff.2 ⟨k, rfl⟩

@[simp] lemma continuous_at_tan {x : ℂ} : continuous_at tan x ↔ cos x ≠ 0 :=
begin
  refine ⟨λ hc h₀, _, λ h, (has_deriv_at_tan h).continuous_at⟩,
  exact not_tendsto_nhds_of_tendsto_at_top (tendsto_abs_tan_of_cos_eq_zero h₀) _
    (hc.norm.tendsto.mono_left inf_le_left)
end

@[simp] lemma differentiable_at_tan {x : ℂ} : differentiable_at ℂ tan x ↔ cos x ≠ 0 :=
⟨λ h, continuous_at_tan.1 h.continuous_at, λ h, (has_deriv_at_tan h).differentiable_at⟩

@[simp] lemma deriv_tan (x : ℂ) : deriv tan x = 1 / (cos x)^2 :=
if h : cos x = 0 then
  have ¬differentiable_at ℂ tan x := mt differentiable_at_tan.1 (not_not.2 h),
  by simp [deriv_zero_of_not_differentiable_at this, h, sq]
else (has_deriv_at_tan h).deriv

@[simp] lemma cont_diff_at_tan {x : ℂ} {n : ℕ∞} :
  cont_diff_at ℂ n tan x ↔ cos x ≠ 0 :=
⟨λ h, continuous_at_tan.1 h.continuous_at,
  cont_diff_sin.cont_diff_at.div cont_diff_cos.cont_diff_at⟩

end complex


import algebra.order.positive.field number_theory.padics.padic_val data.nat.factorization.basic

/-!
# Correspondence between `ℚ+` and `nat.primes → ℤ`

We construct an explicit homomorphism `(additive) ℚ+ →+ nat.primes → ℤ` and prove injectivity.

TODO:
1. Construct a `finsupp` version and prove bijectivity.
2. See if we can add MUCH more results!
No priority on either as of writing.
-/

namespace IMOSL
namespace extra

def pos_rat_factor_hom : additive {x : ℚ // 0 < x} →+ nat.primes → ℤ :=
{ to_fun := λ q, (λ p, padic_val_rat p q.1),
  map_zero' := funext (λ p, padic_val_rat.one),
  map_add' := λ q r, funext (λ p, by haveI : fact (p : ℕ).prime := ⟨p.2⟩;
    exact padic_val_rat.mul (ne_of_gt q.2) (ne_of_gt r.2)) }

namespace pos_rat_factor_hom

lemma apply (q : {x : ℚ // 0 < x}) (p : nat.primes) :
  pos_rat_factor_hom q p = padic_val_rat p q.1 := rfl

lemma apply' {q : ℚ} (h : 0 < q) (p : nat.primes) :
  pos_rat_factor_hom ⟨q, h⟩ p = padic_val_rat p q := rfl

theorem inj : function.injective pos_rat_factor_hom :=
begin
  ---- Setup
  rw injective_iff_map_eq_zero,
  rintros ⟨q, h⟩ h0,
  suffices : q = 1,
    simp_rw this; refl,
  simp_rw [function.funext_iff, pi.zero_apply, apply] at h0,
  replace h0 : ∀ p : ℕ, p.prime → padic_val_rat p q = 0 := λ p hp, h0 ⟨p, hp⟩,

  ---- Now prove that `ν_p(q) = 0` for all `p` prime iff `q = 1`, assuming `q > 0`
  rcases q with ⟨n, d, h1, h2⟩,
  rw ← rat.num_pos_iff_pos at h,
  lift n to ℕ using le_of_lt h; rw nat.cast_pos at h,
  simp_rw [padic_val_rat, sub_eq_zero, nat.cast_inj, padic_val_int.of_nat,
    ← nat.eq_iff_prime_padic_val_nat_eq n d (ne_of_gt h) (ne_of_gt h1)] at h0,
  simp_rw [rat.eq_iff_mul_eq_mul, h0, rat.num_one, rat.denom_one, nat.cast_one],
  exact mul_comm _ 1
end

end pos_rat_factor_hom

end extra
end IMOSL


/-
Copyright (c) 2020 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa
-/
import data.polynomial.erase_lead
import data.polynomial.eval

/-!
# Denominators of evaluation of polynomials at ratios

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

Let `i : R → K` be a homomorphism of semirings.  Assume that `K` is commutative.  If `a` and
`b` are elements of `R` such that `i b ∈ K` is invertible, then for any polynomial
`f ∈ R[X]` the "mathematical" expression `b ^ f.nat_degree * f (a / b) ∈ K` is in
the image of the homomorphism `i`.
-/

open polynomial finset
open_locale polynomial

section denoms_clearable

variables {R K : Type*} [semiring R] [comm_semiring K] {i : R →+* K}
variables {a b : R} {bi : K}
-- TODO: use hypothesis (ub : is_unit (i b)) to work with localizations.

/-- `denoms_clearable` formalizes the property that `b ^ N * f (a / b)`
does not have denominators, if the inequality `f.nat_degree ≤ N` holds.

The definition asserts the existence of an element `D` of `R` and an
element `bi = 1 / i b` of `K` such that clearing the denominators of
the fraction equals `i D`.
-/
def denoms_clearable (a b : R) (N : ℕ) (f : R[X]) (i : R →+* K) : Prop :=
  ∃ (D : R) (bi : K), bi * i b = 1 ∧ i D = i b ^ N * eval (i a * bi) (f.map i)

lemma denoms_clearable_zero (N : ℕ) (a : R) (bu : bi * i b = 1) :
  denoms_clearable a b N 0 i :=
⟨0, bi, bu, by simp only [eval_zero, ring_hom.map_zero, mul_zero, polynomial.map_zero]⟩

lemma denoms_clearable_C_mul_X_pow {N : ℕ} (a : R) (bu : bi * i b = 1) {n : ℕ} (r : R)
  (nN : n ≤ N) : denoms_clearable a b N (C r * X ^ n) i :=
begin
  refine ⟨r * a ^ n * b ^ (N - n), bi, bu, _⟩,
  rw [C_mul_X_pow_eq_monomial, map_monomial, ← C_mul_X_pow_eq_monomial, eval_mul, eval_pow, eval_C],
  rw [ring_hom.map_mul, ring_hom.map_mul, ring_hom.map_pow, ring_hom.map_pow, eval_X, mul_comm],
  rw [← tsub_add_cancel_of_le nN] {occs := occurrences.pos [2]},
  rw [pow_add, mul_assoc, mul_comm (i b ^ n), mul_pow, mul_assoc, mul_assoc (i a ^ n), ← mul_pow],
  rw [bu, one_pow, mul_one],
end

lemma denoms_clearable.add {N : ℕ} {f g : R[X]} :
  denoms_clearable a b N f i → denoms_clearable a b N g i → denoms_clearable a b N (f + g) i :=
λ ⟨Df, bf, bfu, Hf⟩ ⟨Dg, bg, bgu, Hg⟩, ⟨Df + Dg, bf, bfu,
  begin
    rw [ring_hom.map_add, polynomial.map_add, eval_add, mul_add, Hf, Hg],
    congr,
    refine @inv_unique K _ (i b) bg bf _ _;
    rwa mul_comm,
  end ⟩

lemma denoms_clearable_of_nat_degree_le (N : ℕ) (a : R) (bu : bi * i b = 1) :
  ∀ (f : R[X]), f.nat_degree ≤ N → denoms_clearable a b N f i :=
induction_with_nat_degree_le _ N
  (denoms_clearable_zero N a bu)
  (λ N_1 r r0, denoms_clearable_C_mul_X_pow a bu r)
  (λ f g fg gN df dg, df.add dg)

/-- If `i : R → K` is a ring homomorphism, `f` is a polynomial with coefficients in `R`,
`a, b` are elements of `R`, with `i b` invertible, then there is a `D ∈ R` such that
`b ^ f.nat_degree * f (a / b)` equals `i D`. -/
theorem denoms_clearable_nat_degree
  (i : R →+* K) (f : R[X]) (a : R) (bu : bi * i b = 1) :
  denoms_clearable a b f.nat_degree f i :=
denoms_clearable_of_nat_degree_le f.nat_degree a bu f le_rfl

end denoms_clearable

open ring_hom

/--  Evaluating a polynomial with integer coefficients at a rational number and clearing
denominators, yields a number greater than or equal to one.  The target can be any
`linear_ordered_field K`.
The assumption on `K` could be weakened to `linear_ordered_comm_ring` assuming that the
image of the denominator is invertible in `K`. -/
lemma one_le_pow_mul_abs_eval_div {K : Type*} [linear_ordered_field K] {f : ℤ[X]}
  {a b : ℤ} (b0 : 0 < b) (fab : eval ((a : K) / b) (f.map (algebra_map ℤ K)) ≠ 0) :
  (1 : K) ≤ b ^ f.nat_degree * |eval ((a : K) / b) (f.map (algebra_map ℤ K))| :=
begin
  obtain ⟨ev, bi, bu, hF⟩ := @denoms_clearable_nat_degree _ _ _ _ b _ (algebra_map ℤ K)
    f a (by { rw [eq_int_cast, one_div_mul_cancel], rw [int.cast_ne_zero], exact (b0.ne.symm) }),
  obtain Fa := congr_arg abs hF,
  rw [eq_one_div_of_mul_eq_one_left bu, eq_int_cast, eq_int_cast, abs_mul] at Fa,
  rw [abs_of_pos (pow_pos (int.cast_pos.mpr b0) _ : 0 < (b : K) ^ _), one_div, eq_int_cast] at Fa,
  rw [div_eq_mul_inv, ← Fa, ← int.cast_abs, ← int.cast_one, int.cast_le],
  refine int.le_of_lt_add_one ((lt_add_iff_pos_left 1).mpr (abs_pos.mpr (λ F0, fab _))),
  rw [eq_one_div_of_mul_eq_one_left bu, F0, one_div, eq_int_cast, int.cast_zero, zero_eq_mul] at hF,
  cases hF with hF hF,
  { exact (not_le.mpr b0 (le_of_eq (int.cast_eq_zero.mp (pow_eq_zero hF)))).elim },
  { rwa div_eq_mul_inv }
end


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jeremy Avigad, Mario Carneiro
-/

import algebra.associated
import algebra.parity
import data.int.dvd.basic
import data.int.units
import data.nat.factorial.basic
import data.nat.gcd.basic
import data.nat.sqrt
import order.bounds.basic
import tactic.by_contra

/-!
# Prime numbers

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

This file deals with prime numbers: natural numbers `p ≥ 2` whose only divisors are `p` and `1`.

## Important declarations

- `nat.prime`: the predicate that expresses that a natural number `p` is prime
- `nat.primes`: the subtype of natural numbers that are prime
- `nat.min_fac n`: the minimal prime factor of a natural number `n ≠ 1`
- `nat.exists_infinite_primes`: Euclid's theorem that there exist infinitely many prime numbers.
  This also appears as `nat.not_bdd_above_set_of_prime` and `nat.infinite_set_of_prime` (the latter
  in `data.nat.prime_fin`).
- `nat.prime_iff`: `nat.prime` coincides with the general definition of `prime`
- `nat.irreducible_iff_prime`: a non-unit natural number is only divisible by `1` iff it is prime

-/

open bool subtype
open_locale nat

namespace nat

/-- `nat.prime p` means that `p` is a prime number, that is, a natural number
  at least 2 whose only divisors are `p` and `1`. -/
@[pp_nodot]
def prime (p : ℕ) := _root_.irreducible p

theorem _root_.irreducible_iff_nat_prime (a : ℕ) : irreducible a ↔ nat.prime a := iff.rfl

theorem not_prime_zero : ¬ prime 0
| h := h.ne_zero rfl

theorem not_prime_one : ¬ prime 1
| h := h.ne_one rfl

theorem prime.ne_zero {n : ℕ} (h : prime n) : n ≠ 0 := irreducible.ne_zero h

theorem prime.pos {p : ℕ} (pp : prime p) : 0 < p := nat.pos_of_ne_zero pp.ne_zero

theorem prime.two_le : ∀ {p : ℕ}, prime p → 2 ≤ p
| 0 h := (not_prime_zero h).elim
| 1 h := (not_prime_one h).elim
| (n+2) _ := le_add_self

theorem prime.one_lt {p : ℕ} : prime p → 1 < p := prime.two_le

instance prime.one_lt' (p : ℕ) [hp : _root_.fact p.prime] : _root_.fact (1 < p) := ⟨hp.1.one_lt⟩

lemma prime.ne_one {p : ℕ} (hp : p.prime) : p ≠ 1 :=
hp.one_lt.ne'

lemma prime.eq_one_or_self_of_dvd {p : ℕ} (pp : p.prime) (m : ℕ) (hm : m ∣ p) : m = 1 ∨ m = p :=
begin
  obtain ⟨n, hn⟩ := hm,
  have := pp.is_unit_or_is_unit hn,
  rw [nat.is_unit_iff, nat.is_unit_iff] at this,
  apply or.imp_right _ this,
  rintro rfl,
  rw [hn, mul_one]
end

theorem prime_def_lt'' {p : ℕ} : prime p ↔ 2 ≤ p ∧ ∀ m ∣ p, m = 1 ∨ m = p :=
begin
  refine ⟨λ h, ⟨h.two_le, h.eq_one_or_self_of_dvd⟩, λ h, _⟩,
  have h1 := one_lt_two.trans_le h.1,
  refine ⟨mt nat.is_unit_iff.mp h1.ne', λ a b hab, _⟩,
  simp only [nat.is_unit_iff],
  apply or.imp_right _ (h.2 a _),
  { rintro rfl,
    rw [← mul_right_inj' (pos_of_gt h1).ne', ←hab, mul_one] },
  { rw hab,
    exact dvd_mul_right _ _ }
end

theorem prime_def_lt {p : ℕ} : prime p ↔ 2 ≤ p ∧ ∀ m < p, m ∣ p → m = 1 :=
prime_def_lt''.trans $
and_congr_right $ λ p2, forall_congr $ λ m,
⟨λ h l d, (h d).resolve_right (ne_of_lt l),
 λ h d, (le_of_dvd (le_of_succ_le p2) d).lt_or_eq_dec.imp_left (λ l, h l d)⟩

theorem prime_def_lt' {p : ℕ} : prime p ↔ 2 ≤ p ∧ ∀ m, 2 ≤ m → m < p → ¬ m ∣ p :=
prime_def_lt.trans $ and_congr_right $ λ p2, forall_congr $ λ m,
⟨λ h m2 l d, not_lt_of_ge m2 ((h l d).symm ▸ dec_trivial),
λ h l d, begin
  rcases m with _|_|m,
  { rw eq_zero_of_zero_dvd d at p2, revert p2, exact dec_trivial },
  { refl },
  { exact (h dec_trivial l).elim d }
end⟩

theorem prime_def_le_sqrt {p : ℕ} : prime p ↔ 2 ≤ p ∧
  ∀ m, 2 ≤ m → m ≤ sqrt p → ¬ m ∣ p :=
prime_def_lt'.trans $ and_congr_right $ λ p2,
⟨λ a m m2 l, a m m2 $ lt_of_le_of_lt l $ sqrt_lt_self p2,
 λ a, have ∀ {m k}, m ≤ k → 1 < m → p ≠ m * k, from
  λ m k mk m1 e, a m m1
    (le_sqrt.2 (e.symm ▸ nat.mul_le_mul_left m mk)) ⟨k, e⟩,
  λ m m2 l ⟨k, e⟩, begin
    cases (le_total m k) with mk km,
    { exact this mk m2 e },
    { rw [mul_comm] at e,
      refine this km (lt_of_mul_lt_mul_right _ (zero_le m)) e,
      rwa [one_mul, ← e] }
  end⟩

theorem prime_of_coprime (n : ℕ) (h1 : 1 < n) (h : ∀ m < n, m ≠ 0 → n.coprime m) : prime n :=
begin
  refine prime_def_lt.mpr ⟨h1, λ m mlt mdvd, _⟩,
  have hm : m ≠ 0,
  { rintro rfl,
    rw zero_dvd_iff at mdvd,
    exact mlt.ne' mdvd },
  exact (h m mlt hm).symm.eq_one_of_dvd mdvd,
end

section

/--
  This instance is slower than the instance `decidable_prime` defined below,
  but has the advantage that it works in the kernel for small values.

  If you need to prove that a particular number is prime, in any case
  you should not use `dec_trivial`, but rather `by norm_num`, which is
  much faster.
  -/
local attribute [instance]
def decidable_prime_1 (p : ℕ) : decidable (prime p) :=
decidable_of_iff' _ prime_def_lt'

theorem prime_two : prime 2 := dec_trivial
theorem prime_three : prime 3 := dec_trivial

lemma prime.five_le_of_ne_two_of_ne_three {p : ℕ} (hp : p.prime) (h_two : p ≠ 2) (h_three : p ≠ 3) :
  5 ≤ p :=
begin
  by_contra' h,
  revert h_two h_three hp,
  dec_trivial!
end

end

theorem prime.pred_pos {p : ℕ} (pp : prime p) : 0 < pred p :=
lt_pred_iff.2 pp.one_lt

theorem succ_pred_prime {p : ℕ} (pp : prime p) : succ (pred p) = p :=
succ_pred_eq_of_pos pp.pos

theorem dvd_prime {p m : ℕ} (pp : prime p) : m ∣ p ↔ m = 1 ∨ m = p :=
⟨λ d, pp.eq_one_or_self_of_dvd m d, λ h, h.elim (λ e, e.symm ▸ one_dvd _) (λ e, e.symm ▸ dvd_rfl)⟩

theorem dvd_prime_two_le {p m : ℕ} (pp : prime p) (H : 2 ≤ m) : m ∣ p ↔ m = p :=
(dvd_prime pp).trans $ or_iff_right_of_imp $ not.elim $ ne_of_gt H

theorem prime_dvd_prime_iff_eq {p q : ℕ} (pp : p.prime) (qp : q.prime) : p ∣ q ↔ p = q :=
dvd_prime_two_le qp (prime.two_le pp)

theorem prime.not_dvd_one {p : ℕ} (pp : prime p) : ¬ p ∣ 1 :=
pp.not_dvd_one

theorem not_prime_mul {a b : ℕ} (a1 : 1 < a) (b1 : 1 < b) : ¬ prime (a * b) :=
λ h, ne_of_lt (nat.mul_lt_mul_of_pos_left b1 (lt_of_succ_lt a1)) $
by simpa using (dvd_prime_two_le h a1).1 (dvd_mul_right _ _)

lemma not_prime_mul' {a b n : ℕ} (h : a * b = n) (h₁ : 1 < a) (h₂ : 1 < b) : ¬ prime n :=
by { rw ← h, exact not_prime_mul h₁ h₂ }

lemma prime_mul_iff {a b : ℕ} :
  nat.prime (a * b) ↔ (a.prime ∧ b = 1) ∨ (b.prime ∧ a = 1) :=
by simp only [iff_self, irreducible_mul_iff, ←irreducible_iff_nat_prime, nat.is_unit_iff]

lemma prime.dvd_iff_eq {p a : ℕ} (hp : p.prime) (a1 : a ≠ 1) : a ∣ p ↔ p = a :=
begin
  refine ⟨_, by { rintro rfl, refl }⟩,
  -- rintro ⟨j, rfl⟩ does not work, due to `nat.prime` depending on the class `irreducible`
  rintro ⟨j, hj⟩,
  rw hj at hp ⊢,
  rcases prime_mul_iff.mp hp with ⟨h, rfl⟩ | ⟨h, rfl⟩,
  { exact mul_one _ },
  { exact (a1 rfl).elim }
end

section min_fac

lemma min_fac_lemma (n k : ℕ) (h : ¬ n < k * k) :
  sqrt n - k < sqrt n + 2 - k :=
(tsub_lt_tsub_iff_right $ le_sqrt.2 $ le_of_not_gt h).2 $
nat.lt_add_of_pos_right dec_trivial

/-- If `n < k * k`, then `min_fac_aux n k = n`, if `k | n`, then `min_fac_aux n k = k`.
  Otherwise, `min_fac_aux n k = min_fac_aux n (k+2)` using well-founded recursion.
  If `n` is odd and `1 < n`, then then `min_fac_aux n 3` is the smallest prime factor of `n`. -/
def min_fac_aux (n : ℕ) : ℕ → ℕ
| k :=
  if h : n < k * k then n else
  if k ∣ n then k else
  have _, from min_fac_lemma n k h,
  min_fac_aux (k + 2)
using_well_founded {rel_tac :=
  λ _ _, `[exact ⟨_, measure_wf (λ k, sqrt n + 2 - k)⟩]}

/-- Returns the smallest prime factor of `n ≠ 1`. -/
def min_fac : ℕ → ℕ
| 0 := 2
| 1 := 1
| (n+2) := if 2 ∣ n then 2 else min_fac_aux (n + 2) 3

@[simp] theorem min_fac_zero : min_fac 0 = 2 := rfl
@[simp] theorem min_fac_one : min_fac 1 = 1 := rfl

theorem min_fac_eq : ∀ n, min_fac n = if 2 ∣ n then 2 else min_fac_aux n 3
| 0     := by simp
| 1     := by simp [show 2≠1, from dec_trivial]; rw min_fac_aux; refl
| (n+2) :=
  have 2 ∣ n + 2 ↔ 2 ∣ n, from
    (nat.dvd_add_iff_left (by refl)).symm,
  by simp [min_fac, this]; congr

private def min_fac_prop (n k : ℕ) :=
  2 ≤ k ∧ k ∣ n ∧ ∀ m, 2 ≤ m → m ∣ n → k ≤ m

theorem min_fac_aux_has_prop {n : ℕ} (n2 : 2 ≤ n) :
  ∀ k i, k = 2*i+3 → (∀ m, 2 ≤ m → m ∣ n → k ≤ m) → min_fac_prop n (min_fac_aux n k)
| k := λ i e a, begin
  rw min_fac_aux,
  by_cases h : n < k*k; simp [h],
  { have pp : prime n :=
      prime_def_le_sqrt.2 ⟨n2, λ m m2 l d,
        not_lt_of_ge l $ lt_of_lt_of_le (sqrt_lt.2 h) (a m m2 d)⟩,
    from ⟨n2, dvd_rfl, λ m m2 d, le_of_eq
      ((dvd_prime_two_le pp m2).1 d).symm⟩ },
  have k2 : 2 ≤ k, { subst e, exact dec_trivial },
  by_cases dk : k ∣ n; simp [dk],
  { exact ⟨k2, dk, a⟩ },
  { refine have _, from min_fac_lemma n k h,
      min_fac_aux_has_prop (k+2) (i+1)
        (by simp [e, left_distrib]) (λ m m2 d, _),
    cases nat.eq_or_lt_of_le (a m m2 d) with me ml,
    { subst me, contradiction },
    apply (nat.eq_or_lt_of_le ml).resolve_left, intro me,
    rw [← me, e] at d, change 2 * (i + 2) ∣ n at d,
    have := a _ le_rfl (dvd_of_mul_right_dvd d),
    rw e at this, exact absurd this dec_trivial }
end
using_well_founded {rel_tac :=
  λ _ _, `[exact ⟨_, measure_wf (λ k, sqrt n + 2 - k)⟩]}

theorem min_fac_has_prop {n : ℕ} (n1 : n ≠ 1) :
  min_fac_prop n (min_fac n) :=
begin
  by_cases n0 : n = 0, {simp [n0, min_fac_prop, ge]},
  have n2 : 2 ≤ n, { revert n0 n1, rcases n with _|_|_; exact dec_trivial },
  simp [min_fac_eq],
  by_cases d2 : 2 ∣ n; simp [d2],
  { exact ⟨le_rfl, d2, λ k k2 d, k2⟩ },
  { refine min_fac_aux_has_prop n2 3 0 rfl
      (λ m m2 d, (nat.eq_or_lt_of_le m2).resolve_left (mt _ d2)),
    exact λ e, e.symm ▸ d }
end

theorem min_fac_dvd (n : ℕ) : min_fac n ∣ n :=
if n1 : n = 1 then by simp [n1] else (min_fac_has_prop n1).2.1

theorem min_fac_prime {n : ℕ} (n1 : n ≠ 1) : prime (min_fac n) :=
let ⟨f2, fd, a⟩ := min_fac_has_prop n1 in
prime_def_lt'.2 ⟨f2, λ m m2 l d, not_le_of_gt l (a m m2 (d.trans fd))⟩

theorem min_fac_le_of_dvd {n : ℕ} : ∀ {m : ℕ}, 2 ≤ m → m ∣ n → min_fac n ≤ m :=
by by_cases n1 : n = 1;
  [exact λ m m2 d, n1.symm ▸ le_trans dec_trivial m2,
    exact (min_fac_has_prop n1).2.2]

theorem min_fac_pos (n : ℕ) : 0 < min_fac n :=
by by_cases n1 : n = 1;
    [exact n1.symm ▸ dec_trivial, exact (min_fac_prime n1).pos]

theorem min_fac_le {n : ℕ} (H : 0 < n) : min_fac n ≤ n :=
le_of_dvd H (min_fac_dvd n)

theorem le_min_fac {m n : ℕ} : n = 1 ∨ m ≤ min_fac n ↔ ∀ p, prime p → p ∣ n → m ≤ p :=
⟨λ h p pp d, h.elim
  (by rintro rfl; cases pp.not_dvd_one d)
  (λ h, le_trans h $ min_fac_le_of_dvd pp.two_le d),
  λ H, or_iff_not_imp_left.2 $ λ n1, H _ (min_fac_prime n1) (min_fac_dvd _)⟩

theorem le_min_fac' {m n : ℕ} : n = 1 ∨ m ≤ min_fac n ↔ ∀ p, 2 ≤ p → p ∣ n → m ≤ p :=
⟨λ h p (pp:1<p) d, h.elim
  (by rintro rfl; cases not_le_of_lt pp (le_of_dvd dec_trivial d))
  (λ h, le_trans h $ min_fac_le_of_dvd pp d),
  λ H, le_min_fac.2 (λ p pp d, H p pp.two_le d)⟩

theorem prime_def_min_fac {p : ℕ} : prime p ↔ 2 ≤ p ∧ min_fac p = p :=
⟨λ pp, ⟨pp.two_le,
  let ⟨f2, fd, a⟩ := min_fac_has_prop $ ne_of_gt pp.one_lt in
  ((dvd_prime pp).1 fd).resolve_left (ne_of_gt f2)⟩,
  λ ⟨p2, e⟩, e ▸ min_fac_prime (ne_of_gt p2)⟩

@[simp] lemma prime.min_fac_eq {p : ℕ} (hp : prime p) : min_fac p = p :=
(prime_def_min_fac.1 hp).2

/--
This instance is faster in the virtual machine than `decidable_prime_1`,
but slower in the kernel.

If you need to prove that a particular number is prime, in any case
you should not use `dec_trivial`, but rather `by norm_num`, which is
much faster.
-/
instance decidable_prime (p : ℕ) : decidable (prime p) :=
decidable_of_iff' _ prime_def_min_fac

theorem not_prime_iff_min_fac_lt {n : ℕ} (n2 : 2 ≤ n) : ¬ prime n ↔ min_fac n < n :=
(not_congr $ prime_def_min_fac.trans $ and_iff_right n2).trans $
  (lt_iff_le_and_ne.trans $ and_iff_right $ min_fac_le $ le_of_succ_le n2).symm

lemma min_fac_le_div {n : ℕ} (pos : 0 < n) (np : ¬ prime n) : min_fac n ≤ n / min_fac n :=
match min_fac_dvd n with
| ⟨0, h0⟩     := absurd pos $ by rw [h0, mul_zero]; exact dec_trivial
| ⟨1, h1⟩     :=
  begin
    rw mul_one at h1,
    rw [prime_def_min_fac, not_and_distrib, ← h1, eq_self_iff_true, not_true, or_false,
      not_le] at np,
    rw [le_antisymm (le_of_lt_succ np) (succ_le_of_lt pos), min_fac_one, nat.div_one]
  end
| ⟨(x+2), hx⟩ :=
  begin
    conv_rhs { congr, rw hx },
    rw [nat.mul_div_cancel_left _ (min_fac_pos _)],
    exact min_fac_le_of_dvd dec_trivial ⟨min_fac n, by rwa mul_comm⟩
  end
end

/--
The square of the smallest prime factor of a composite number `n` is at most `n`.
-/
lemma min_fac_sq_le_self {n : ℕ} (w : 0 < n) (h : ¬ prime n) : (min_fac n)^2 ≤ n :=
have t : (min_fac n) ≤ (n/min_fac n) := min_fac_le_div w h,
calc
(min_fac n)^2 = (min_fac n) * (min_fac n)   : sq (min_fac n)
          ... ≤ (n/min_fac n) * (min_fac n) : nat.mul_le_mul_right (min_fac n) t
          ... ≤ n                           : div_mul_le_self n (min_fac n)

@[simp]
lemma min_fac_eq_one_iff {n : ℕ} : min_fac n = 1 ↔ n = 1 :=
begin
  split,
  { intro h,
    by_contradiction hn,
    have := min_fac_prime hn,
    rw h at this,
    exact not_prime_one this, },
  { rintro rfl, refl, }
end

@[simp]
lemma min_fac_eq_two_iff (n : ℕ) : min_fac n = 2 ↔ 2 ∣ n :=
begin
  split,
  { intro h,
    convert min_fac_dvd _,
    rw h, },
  { intro h,
    have ub := min_fac_le_of_dvd (le_refl 2) h,
    have lb := min_fac_pos n,
    apply ub.eq_or_lt.resolve_right (λ h', _),
    have := le_antisymm (nat.succ_le_of_lt lb) (lt_succ_iff.mp h'),
    rw [eq_comm, nat.min_fac_eq_one_iff] at this,
    subst this,
    exact not_lt_of_le (le_of_dvd zero_lt_one h) one_lt_two }
end

end min_fac

theorem exists_dvd_of_not_prime {n : ℕ} (n2 : 2 ≤ n) (np : ¬ prime n) :
  ∃ m, m ∣ n ∧ m ≠ 1 ∧ m ≠ n :=
⟨min_fac n, min_fac_dvd _, ne_of_gt (min_fac_prime (ne_of_gt n2)).one_lt,
  ne_of_lt $ (not_prime_iff_min_fac_lt n2).1 np⟩

theorem exists_dvd_of_not_prime2 {n : ℕ} (n2 : 2 ≤ n) (np : ¬ prime n) :
  ∃ m, m ∣ n ∧ 2 ≤ m ∧ m < n :=
⟨min_fac n, min_fac_dvd _, (min_fac_prime (ne_of_gt n2)).two_le,
  (not_prime_iff_min_fac_lt n2).1 np⟩

theorem exists_prime_and_dvd {n : ℕ} (hn : n ≠ 1) : ∃ p, prime p ∧ p ∣ n :=
⟨min_fac n, min_fac_prime hn, min_fac_dvd _⟩

theorem dvd_of_forall_prime_mul_dvd {a b : ℕ}
  (hdvd : ∀ p : ℕ, p.prime → p ∣ a → p * a ∣ b) : a ∣ b :=
begin
  obtain rfl | ha := eq_or_ne a 1, { apply one_dvd },
  obtain ⟨p, hp⟩ := exists_prime_and_dvd ha,
  exact trans (dvd_mul_left a p) (hdvd p hp.1 hp.2),
end

/-- Euclid's theorem on the **infinitude of primes**.
Here given in the form: for every `n`, there exists a prime number `p ≥ n`. -/
theorem exists_infinite_primes (n : ℕ) : ∃ p, n ≤ p ∧ prime p :=
let p := min_fac (n! + 1) in
have f1 : n! + 1 ≠ 1, from ne_of_gt $ succ_lt_succ $ factorial_pos _,
have pp : prime p, from min_fac_prime f1,
have np : n ≤ p, from le_of_not_ge $ λ h,
  have h₁ : p ∣ n!, from dvd_factorial (min_fac_pos _) h,
  have h₂ : p ∣ 1, from (nat.dvd_add_iff_right h₁).2 (min_fac_dvd _),
  pp.not_dvd_one h₂,
⟨p, np, pp⟩

/-- A version of `nat.exists_infinite_primes` using the `bdd_above` predicate. -/
lemma not_bdd_above_set_of_prime : ¬ bdd_above {p | prime p} :=
begin
  rw not_bdd_above_iff,
  intro n,
  obtain ⟨p, hi, hp⟩ := exists_infinite_primes n.succ,
  exact ⟨p, hp, hi⟩,
end

lemma prime.eq_two_or_odd {p : ℕ} (hp : prime p) : p = 2 ∨ p % 2 = 1 :=
p.mod_two_eq_zero_or_one.imp_left
  (λ h, ((hp.eq_one_or_self_of_dvd 2 (dvd_of_mod_eq_zero h)).resolve_left dec_trivial).symm)

lemma prime.eq_two_or_odd' {p : ℕ} (hp : prime p) : p = 2 ∨ odd p :=
or.imp_right (λ h, ⟨p / 2, (div_add_mod p 2).symm.trans (congr_arg _ h)⟩) hp.eq_two_or_odd

lemma prime.even_iff {p : ℕ} (hp : prime p) : even p ↔ p = 2 :=
by rw [even_iff_two_dvd, prime_dvd_prime_iff_eq prime_two hp, eq_comm]

lemma prime.odd_of_ne_two {p : ℕ} (hp : p.prime) (h_two : p ≠ 2) : odd p :=
hp.eq_two_or_odd'.resolve_left h_two

lemma prime.even_sub_one {p : ℕ} (hp : p.prime) (h2 : p ≠ 2) : even (p - 1) :=
let ⟨n, hn⟩ := hp.odd_of_ne_two h2 in ⟨n, by rw [hn, nat.add_sub_cancel, two_mul]⟩

/-- A prime `p` satisfies `p % 2 = 1` if and only if `p ≠ 2`. -/
lemma prime.mod_two_eq_one_iff_ne_two {p : ℕ} [fact p.prime] : p % 2 = 1 ↔ p ≠ 2 :=
begin
  refine ⟨λ h hf, _, (nat.prime.eq_two_or_odd $ fact.out p.prime).resolve_left⟩,
  rw hf at h,
  simpa using h,
end

theorem coprime_of_dvd {m n : ℕ} (H : ∀ k, prime k → k ∣ m → ¬ k ∣ n) : coprime m n :=
begin
  rw [coprime_iff_gcd_eq_one],
  by_contra g2,
  obtain ⟨p, hp, hpdvd⟩ := exists_prime_and_dvd g2,
  apply H p hp; apply dvd_trans hpdvd,
  { exact gcd_dvd_left _ _ },
  { exact gcd_dvd_right _ _ }
end

theorem coprime_of_dvd' {m n : ℕ} (H : ∀ k, prime k → k ∣ m → k ∣ n → k ∣ 1) : coprime m n :=
coprime_of_dvd $ λk kp km kn, not_le_of_gt kp.one_lt $ le_of_dvd zero_lt_one $ H k kp km kn

theorem factors_lemma {k} : (k+2) / min_fac (k+2) < k+2 :=
div_lt_self dec_trivial (min_fac_prime dec_trivial).one_lt

theorem prime.coprime_iff_not_dvd {p n : ℕ} (pp : prime p) : coprime p n ↔ ¬ p ∣ n :=
⟨λ co d, pp.not_dvd_one $ co.dvd_of_dvd_mul_left (by simp [d]),
 λ nd, coprime_of_dvd $ λ m m2 mp, ((prime_dvd_prime_iff_eq m2 pp).1 mp).symm ▸ nd⟩

theorem prime.dvd_iff_not_coprime {p n : ℕ} (pp : prime p) : p ∣ n ↔ ¬ coprime p n :=
iff_not_comm.2 pp.coprime_iff_not_dvd

theorem prime.not_coprime_iff_dvd {m n : ℕ} :
  ¬ coprime m n ↔ ∃p, prime p ∧ p ∣ m ∧ p ∣ n :=
begin
  apply iff.intro,
  { intro h,
    exact ⟨min_fac (gcd m n), min_fac_prime h,
      ((min_fac_dvd (gcd m n)).trans (gcd_dvd_left m n)),
      ((min_fac_dvd (gcd m n)).trans (gcd_dvd_right m n))⟩ },
  { intro h,
    cases h with p hp,
    apply nat.not_coprime_of_dvd_of_dvd (prime.one_lt hp.1) hp.2.1 hp.2.2 }
end

theorem prime.dvd_mul {p m n : ℕ} (pp : prime p) : p ∣ m * n ↔ p ∣ m ∨ p ∣ n :=
⟨λ H, or_iff_not_imp_left.2 $ λ h,
  (pp.coprime_iff_not_dvd.2 h).dvd_of_dvd_mul_left H,
 or.rec (λ h : p ∣ m, h.mul_right _) (λ h : p ∣ n, h.mul_left _)⟩

theorem prime.not_dvd_mul {p m n : ℕ} (pp : prime p)
  (Hm : ¬ p ∣ m) (Hn : ¬ p ∣ n) : ¬ p ∣ m * n :=
mt pp.dvd_mul.1 $ by simp [Hm, Hn]

theorem prime_iff {p : ℕ} : p.prime ↔ _root_.prime p :=
⟨λ h, ⟨h.ne_zero, h.not_unit, λ a b, h.dvd_mul.mp⟩, prime.irreducible⟩

alias prime_iff ↔ prime.prime _root_.prime.nat_prime
attribute [protected, nolint dup_namespace] prime.prime

theorem irreducible_iff_prime {p : ℕ} : irreducible p ↔ _root_.prime p := prime_iff

theorem prime.dvd_of_dvd_pow {p m n : ℕ} (pp : prime p) (h : p ∣ m^n) : p ∣ m :=
begin
  induction n with n IH,
  { exact pp.not_dvd_one.elim h },
  { rw pow_succ at h, exact (pp.dvd_mul.1 h).elim id IH }
end

lemma prime.pow_not_prime {x n : ℕ} (hn : 2 ≤ n) : ¬ (x ^ n).prime :=
λ hp, (hp.eq_one_or_self_of_dvd x $ dvd_trans ⟨x, sq _⟩ (pow_dvd_pow _ hn)).elim
  (λ hx1, hp.ne_one $ hx1.symm ▸ one_pow _)
  (λ hxn, lt_irrefl x $ calc x = x ^ 1 : (pow_one _).symm
     ... < x ^ n : nat.pow_right_strict_mono (hxn.symm ▸ hp.two_le) hn
     ... = x : hxn.symm)

lemma prime.pow_not_prime' {x : ℕ} : ∀ {n : ℕ}, n ≠ 1 → ¬ (x ^ n).prime
| 0     := λ _, not_prime_one
| 1     := λ h, (h rfl).elim
| (n+2) := λ _, prime.pow_not_prime le_add_self

lemma prime.eq_one_of_pow {x n : ℕ} (h : (x ^ n).prime) : n = 1 :=
not_imp_not.mp prime.pow_not_prime' h

lemma prime.pow_eq_iff {p a k : ℕ} (hp : p.prime) : a ^ k = p ↔ a = p ∧ k = 1 :=
begin
  refine ⟨λ h, _, λ h, by rw [h.1, h.2, pow_one]⟩,
  rw ←h at hp,
  rw [←h, hp.eq_one_of_pow, eq_self_iff_true, and_true, pow_one],
end

lemma pow_min_fac {n k : ℕ} (hk : k ≠ 0) : (n^k).min_fac = n.min_fac :=
begin
  rcases eq_or_ne n 1 with rfl | hn,
  { simp },
  have hnk : n ^ k ≠ 1 := λ hk', hn ((pow_eq_one_iff hk).1 hk'),
  apply (min_fac_le_of_dvd (min_fac_prime hn).two_le ((min_fac_dvd n).pow hk)).antisymm,
  apply min_fac_le_of_dvd (min_fac_prime hnk).two_le
    ((min_fac_prime hnk).dvd_of_dvd_pow (min_fac_dvd _)),
end

lemma prime.pow_min_fac {p k : ℕ} (hp : p.prime) (hk : k ≠ 0) : (p^k).min_fac = p :=
by rw [pow_min_fac hk, hp.min_fac_eq]

lemma prime.mul_eq_prime_sq_iff {x y p : ℕ} (hp : p.prime) (hx : x ≠ 1) (hy : y ≠ 1) :
  x * y = p ^ 2 ↔ x = p ∧ y = p :=
⟨λ h, have pdvdxy : p ∣ x * y, by rw h; simp [sq],
begin
  -- Could be `wlog := hp.dvd_mul.1 pdvdxy using x y`, but that imports more than we want.
  suffices : ∀ (x' y' : ℕ), x' ≠ 1 → y' ≠ 1 → x' * y' = p ^ 2 → p ∣ x' → x' = p ∧ y' = p,
  { obtain hx|hy := hp.dvd_mul.1 pdvdxy;
      [skip, rw and_comm];
      [skip, rw mul_comm at h pdvdxy];
      apply this;
      assumption },
  clear_dependent x y,
  rintros x y hx hy h ⟨a, ha⟩,
  have hap : a ∣ p, from ⟨y, by rwa [ha, sq,
        mul_assoc, mul_right_inj' hp.ne_zero, eq_comm] at h⟩,
  exact ((nat.dvd_prime hp).1 hap).elim
    (λ _, by clear_aux_decl; simp [*, sq, mul_right_inj' hp.ne_zero] at *
      {contextual := tt})
    (λ _, by clear_aux_decl; simp [*, sq, mul_comm, mul_assoc,
      mul_right_inj' hp.ne_zero, nat.mul_right_eq_self_iff hp.pos] at *
      {contextual := tt})
end,
λ ⟨h₁, h₂⟩, h₁.symm ▸ h₂.symm ▸ (sq _).symm⟩

lemma prime.dvd_factorial : ∀ {n p : ℕ} (hp : prime p), p ∣ n! ↔ p ≤ n
| 0 p hp := iff_of_false hp.not_dvd_one (not_le_of_lt hp.pos)
| (n+1) p hp := begin
  rw [factorial_succ, hp.dvd_mul, prime.dvd_factorial hp],
  exact ⟨λ h, h.elim (le_of_dvd (succ_pos _)) le_succ_of_le,
    λ h, (_root_.lt_or_eq_of_le h).elim (or.inr ∘ le_of_lt_succ)
      (λ h, or.inl $ by rw h)⟩
end

theorem prime.coprime_pow_of_not_dvd {p m a : ℕ} (pp : prime p) (h : ¬ p ∣ a) : coprime a (p^m) :=
(pp.coprime_iff_not_dvd.2 h).symm.pow_right _

theorem coprime_primes {p q : ℕ} (pp : prime p) (pq : prime q) : coprime p q ↔ p ≠ q :=
pp.coprime_iff_not_dvd.trans $ not_congr $ dvd_prime_two_le pq pp.two_le

theorem coprime_pow_primes {p q : ℕ} (n m : ℕ) (pp : prime p) (pq : prime q) (h : p ≠ q) :
  coprime (p^n) (q^m) :=
((coprime_primes pp pq).2 h).pow _ _

theorem coprime_or_dvd_of_prime {p} (pp : prime p) (i : ℕ) : coprime p i ∨ p ∣ i :=
by rw [pp.dvd_iff_not_coprime]; apply em

lemma coprime_of_lt_prime {n p} (n_pos : 0 < n) (hlt : n < p) (pp : prime p) :
  coprime p n :=
(coprime_or_dvd_of_prime pp n).resolve_right $ λ h, lt_le_antisymm hlt (le_of_dvd n_pos h)

lemma eq_or_coprime_of_le_prime {n p} (n_pos : 0 < n) (hle : n ≤ p) (pp : prime p) :
  p = n ∨ coprime p n :=
hle.eq_or_lt.imp eq.symm (λ h, coprime_of_lt_prime n_pos h pp)

theorem dvd_prime_pow {p : ℕ} (pp : prime p) {m i : ℕ} : i ∣ (p^m) ↔ ∃ k ≤ m, i = p^k :=
by simp_rw [dvd_prime_pow (prime_iff.mp pp) m, associated_eq_eq]

lemma prime.dvd_mul_of_dvd_ne {p1 p2 n : ℕ} (h_neq : p1 ≠ p2) (pp1 : prime p1) (pp2 : prime p2)
  (h1 : p1 ∣ n) (h2 : p2 ∣ n) : (p1 * p2 ∣ n) :=
coprime.mul_dvd_of_dvd_of_dvd ((coprime_primes pp1 pp2).mpr h_neq) h1 h2

/--
If `p` is prime,
and `a` doesn't divide `p^k`, but `a` does divide `p^(k+1)`
then `a = p^(k+1)`.
-/
lemma eq_prime_pow_of_dvd_least_prime_pow
  {a p k : ℕ} (pp : prime p) (h₁ : ¬(a ∣ p^k)) (h₂ : a ∣ p^(k+1)) :
  a = p^(k+1) :=
begin
  obtain ⟨l, ⟨h, rfl⟩⟩ := (dvd_prime_pow pp).1 h₂,
  congr,
  exact le_antisymm h (not_le.1 ((not_congr (pow_dvd_pow_iff_le_right (prime.one_lt pp))).1 h₁)),
end

lemma ne_one_iff_exists_prime_dvd : ∀ {n}, n ≠ 1 ↔ ∃ p : ℕ, p.prime ∧ p ∣ n
| 0 := by simpa using (Exists.intro 2 nat.prime_two)
| 1 := by simp [nat.not_prime_one]
| (n+2) :=
let a := n+2 in
let ha : a ≠ 1 := nat.succ_succ_ne_one n in
begin
  simp only [true_iff, ne.def, not_false_iff, ha],
  exact ⟨a.min_fac, nat.min_fac_prime ha, a.min_fac_dvd⟩,
end

lemma eq_one_iff_not_exists_prime_dvd {n : ℕ} : n = 1 ↔ ∀ p : ℕ, p.prime → ¬p ∣ n :=
by simpa using not_iff_not.mpr ne_one_iff_exists_prime_dvd

lemma succ_dvd_or_succ_dvd_of_succ_sum_dvd_mul {p : ℕ} (p_prime : prime p) {m n k l : ℕ}
      (hpm : p ^ k ∣ m) (hpn : p ^ l ∣ n) (hpmn : p ^ (k+l+1) ∣ m*n) :
      p ^ (k+1) ∣ m ∨ p ^ (l+1) ∣ n :=
have hpd : p^(k+l)*p ∣ m*n, by rwa pow_succ' at hpmn,
have hpd2 : p ∣ (m*n) / p ^ (k+l), from dvd_div_of_mul_dvd hpd,
have hpd3 : p ∣ (m*n) / (p^k * p^l), by simpa [pow_add] using hpd2,
have hpd4 : p ∣ (m / p^k) * (n / p^l), by simpa [nat.div_mul_div_comm hpm hpn] using hpd3,
have hpd5 : p ∣ (m / p^k) ∨ p ∣ (n / p^l), from (prime.dvd_mul p_prime).1 hpd4,
suffices p^k*p ∣ m ∨ p^l*p ∣ n, by rwa [pow_succ', pow_succ'],
  hpd5.elim
    (assume : p ∣ m / p ^ k, or.inl $ mul_dvd_of_dvd_div hpm this)
    (assume : p ∣ n / p ^ l, or.inr $ mul_dvd_of_dvd_div hpn this)

lemma prime_iff_prime_int {p : ℕ} : p.prime ↔ _root_.prime (p : ℤ) :=
⟨λ hp, ⟨int.coe_nat_ne_zero_iff_pos.2 hp.pos, mt int.is_unit_iff_nat_abs_eq.1 hp.ne_one,
  λ a b h, by rw [← int.dvd_nat_abs, int.coe_nat_dvd, int.nat_abs_mul, hp.dvd_mul] at h;
    rwa [← int.dvd_nat_abs, int.coe_nat_dvd, ← int.dvd_nat_abs, int.coe_nat_dvd]⟩,
  λ hp, nat.prime_iff.2 ⟨int.coe_nat_ne_zero.1 hp.1,
      mt nat.is_unit_iff.1 $ λ h, by simpa [h, not_prime_one] using hp,
    λ a b, by simpa only [int.coe_nat_dvd, (int.coe_nat_mul _ _).symm] using hp.2.2 a b⟩⟩

/-- The type of prime numbers -/
def primes := {p : ℕ // p.prime}

namespace primes

instance : has_repr nat.primes := ⟨λ p, repr p.val⟩
instance inhabited_primes : inhabited primes := ⟨⟨2, prime_two⟩⟩

instance coe_nat : has_coe nat.primes ℕ := ⟨subtype.val⟩

theorem coe_nat_injective : function.injective (coe : nat.primes → ℕ) :=
subtype.coe_injective

theorem coe_nat_inj (p q : nat.primes) : (p : ℕ) = (q : ℕ) ↔ p = q :=
subtype.ext_iff.symm

end primes

instance monoid.prime_pow {α : Type*} [monoid α] : has_pow α primes := ⟨λ x p, x^(p : ℕ)⟩

end nat

namespace nat

instance fact_prime_two : fact (prime 2) := ⟨prime_two⟩

instance fact_prime_three : fact (prime 3) := ⟨prime_three⟩

end nat

namespace int
lemma prime_two : prime (2 : ℤ) := nat.prime_iff_prime_int.mp nat.prime_two
lemma prime_three : prime (3 : ℤ) := nat.prime_iff_prime_int.mp nat.prime_three
end int

assert_not_exists multiset


/- In this file we demonstrate examples and constructions of various types. Key
definitions can be found in the model.lean file.-/

import model

/-- We now define some example languages. We start with the simplest
possible language, the language of pure sets. This language has no
functions, relations or constants.-/
def set_lang: lang := {F := function.const ℕ+ empty,
                       R := function.const ℕ+ empty,
                       C := empty}

/-- The language of ordered sets is the language or sets with a binary
  ordering relation {<}.-/
def ordered_set_lang: lang := {R := λ n : ℕ+, if n=2 then unit else empty,
                               ..set_lang}

/-- A magma is a {×}-structure. So this has 1 function, 0 relations and
0 constants.-/
def magma_lang : lang := {F := λ n : ℕ+, if n=2 then unit else empty,
                          ..set_lang}

/-- A semigroup is a {×}-structure which satisfies the identity
  u × (v × w) = (u × v) × w.  Note that identities are not relations!-/
def semigroup_lang : lang := magma_lang

/-- A monoid is a {×, 1}-structure which satisfies the identities
   1. u × (v × w) = (u × v) × w
   2. u × 1 = u
   3. 1 × u = u. -/
def monoid_lang : lang := {F := λ n : ℕ+,
                                if n=2 then unit else empty, -- one binary op.,
                           C := unit,                        -- one constant
                           R := function.const ℕ+ empty}

/-- A group is a {×, ⁻¹, 1}-structure which satisfies the identities
 1. u × (v × w) = (u × v) × w
 2. u × 1 = u
 3. 1 × u = u
 4. u × u−1 = 1
 5. u−1 × u = 1 -/
def group_lang : lang := {F := λ n : ℕ+,
                               if n=1 then unit else        -- one unary op.
                               if n=2 then unit else empty, -- one binary op.
                          C := unit,                        -- one constant
                          ..set_lang}

/-- A semiring is a {×, +, 0, 1}-structure which satisfies the identities
  1. u + (v + w) = (u + v) + w
  2. u + v = v + u
  3. u + 0 = u
  5. u × (v × w) = (u × v) × w
  6. u × 1 = u, 1 × u = u
  7. u × (v + w) = (u × v) + (u × w)
  8. (v + w) × u = (v × u) + (w × u)-/
def semiring_lang : lang := {F := λ n : ℕ+,
                                  if n = 2 then fin 2 else empty, -- two binary ops.
                             C := fin 2,                     -- two constants
                             ..magma_lang}

/-- A ring is a {×,+,−,0,1}-structure which satisfies the identities
   1. u + (v + w) = (u + v) + w
   2. u + v = v + u
   3. u + 0 = u
   4. u + (−u) = 0
   5. u × (v × w) = (u × v) × w
   6. u × 1 = u, 1 × u = u
   7. u × (v + w) = (u × v) + (u × w)
   8. (v + w) × u = (v × u) + (w × u)-/
def ring_lang : lang := {F := λ n : ℕ+,
                              if n = 1 then fin 1 else        -- one unary op.
                              if n = 2 then fin 2 else empty, -- two binary ops.
                         C := fin 2,                          -- two constants
                         ..magma_lang}

/-- An ordered ring is a ring along with a binary ordering relation {<}.-/
def ordered_ring_lang : lang := {R := λ n : ℕ+,
                                if n = 2 then unit else empty,  -- one binary rel.
                                ..ring_lang}

/-- An inhabited type is a structure of the set language-/
def type_is_struc_of_set_lang {A : Type} [inhabited A] : struc (set_lang) :=
 {univ := A,
  F := λ _, empty.elim,
  R := λ _, empty.elim,
  C := empty.elim}

/-- Type is a structure of the ordered set language-/
def type_is_struc_of_ordered_set_lang {A : Type} [has_lt A] [inhabited A]:
  struc (ordered_set_lang) :=
  {univ := A,
   F := λ _, empty.elim,
   R := λ n r v, by {repeat {cases n <|> unfold_coes at v <|> linarith
                             <|> cases r <|> cases n_val},
                     exact (v.nth 0 < v.nth 1)},
   C := empty.elim}

/-- We need to define a magma, because it looks like it is not defined
  in Mathlib.-/
class magma (α : Type) :=
(mul : α → α → α)

def free_magma_is_struc_of_magma_lang {A : Type} [magma A] [inhabited A] :
  struc (magma_lang) :=
  {univ := A,
   F := λ n f, by {repeat {cases n with n_val _ <|> unfold_coes <|> cases f
                    <|> linarith <|> cases n_val},
                   exact magma.mul}, -- if n=2
   R := λ _, empty.elim,
   C := empty.elim}

def semigroup_is_struc_of_semigroup_lang {A : Type} [semigroup A] [inhabited A] :
  struc (semigroup_lang) :=
  {univ := A,
   F := λ n f, by {repeat {cases n <|> linarith <|> cases f <|> cases n_val},
                   exact semigroup.mul},
   R := λ _, empty.elim,
   C := empty.elim}

/-- Monoid is a structure of the language of monoids-/
def monoid_is_struc_of_monoid_lang {A : Type} [monoid A] :
  struc (monoid_lang) :=
  {univ := A,
   F := λ n f, by {repeat {cases n <|> cases f <|> linarith <|> cases n_val},
                   exact monoid.mul},
   R := λ _, empty.elim,
   C := 1,
   univ_inhabited := ⟨1⟩}

/-- Group is a structure of the group language-/
def group_is_struc_of_group_lang {A : Type} [group A] :
  struc (group_lang) :=
  {univ := A,
   F := λ n f, by {repeat {cases n <|> linarith <|> cases f <|> cases n_val},
                   exact group.inv,
                   exact group.mul},
   ..monoid_is_struc_of_monoid_lang}

/-- Semiring is a structure of the language of semirings-/
def semiring_is_struc_of_semiring_lang {A : Type} [semiring A] :
  struc (semiring_lang) :=
  {univ := A,
   F := λ n f, by {cases n, cases n_val,
                     {linarith},
                   cases n_val, cases f,
                   cases n_val, cases f,
                     {cases f_val,
                      exact (+),
                      exact (*)},
                   cases f},
   R := λ _, empty.elim,
   C := λ c, by {cases c, cases c_val, exact 0, exact 1},
   univ_inhabited := ⟨0⟩}

/-- Ring is a structure of the language of rings-/
def ring_is_struc_of_ring_lang {A : Type} [ring A] :
  struc (ring_lang) :=
  {univ := A,
   F := λ n f, by {cases n, cases n_val,
                     {linarith},
                   cases n_val,
                     {exact ring.neg},
                   cases n_val, cases f,
                     {cases f_val,
                      exact (+),
                      exact (*)},
                   cases f},
   ..semiring_is_struc_of_semiring_lang}

/-- Ordered ring is a structure of the language of ordered rings-/
def ordered_ring_is_struc_of_ordered_ring_lang {A : Type} [ordered_ring A]
  : struc(ordered_ring_lang) :=
  {univ := A,
   R := λ n r v, by {cases n, cases n_val,
                       {linarith},
                     cases n_val, cases r,
                       {unfold_coes at v,
                        exact v.nth 0 < v.nth 1}},
   ..ring_is_struc_of_ring_lang}

/-- A type with linear order is a structure on dense-linear-order language.-/
def LO_is_struc_of_DLO_lang {A : Type} [linear_order A] [inhabited A]
 : struc (lang.DLO_lang) :=
  {univ := A,
   R := λ n r v, by {cases n, cases n_val,
                       {linarith},
                     cases n_val, cases r,
                       {unfold_coes at v,
                        exact (v.nth 0 < v.nth 1)}},
   .. type_is_struc_of_set_lang}

/- TODO: Fix this proof.
lemma ordered_ring_is_struc_of_ordered_ring_lang {A : Type} [ordered_ring A] :
  struc (ordered_ring_lang) :=
begin
  fconstructor,
  { exact A},
  {
    intros n f,
    cases n,
     cases f,                                              -- n=0: f n = empty
    cases n,
     exact ordered_ring.neg (v.nth 0),                     -- n=1: f n = {-}
    cases n,
     cases f,                                              -- n=2: f n = {×, +}
     { exact ordered_ring.mul (v.nth 0) (v.nth 1)},        -- ×
     { exact ordered_ring.add (v.nth 0) (v.nth 1)},        -- +
     cases f,                                              -- n>2: f n = empty
  },
  {
    intros n r v,
    iterate 2 {cases n, cases r},                          -- n<2: r n = empty
    cases n,
     exact ordered_ring.lt (v.nth 0) (v.nth 1),            -- n=2: r n = {<}
     cases r,                                              -- n>2: r n = empty
  },
  {
    intro c,
    cases c,     --C = {0, 1}
    { exact 0},
    { exact 1}
  }
end-/

/-! 4.1 Examples of Terms
    ---------------------
The following example is taken from [Marker2002]. -/

namespace example_terms
  /-- The language L has:
  - one unary function f,
  - one binary function g,
  - and one constant symbol c.-/

  def L1 : lang := {F := λ (n : ℕ+),
                         if n=1 then unit else        -- one unary op.
                         if n=2 then unit else empty, -- one binrary op.
                    R := function.const ℕ+ empty,
                    C := unit}
  /-- f is a unary operation in L1. -/
  def f : L1.F 1 := unit.star
  /-- g is a binary operation in L1. -/
  def g : L1.F 2 := unit.star
  /-- c is a constant in L1. -/
  def c : L1.C := unit.star

  /-- M1 is a structure on L1. -/
  def M1 : struc L1 :=
  {univ := ℕ,
   F := λ n f, by {cases n, cases n_val,
                     { linarith},
                   cases n_val,
                     { exact λ x : ℕ, 100*x}, -- if n=1
                   cases n_val,
                     { exact (+)},             -- if n=2
                   cases f},                   -- if n>2
   R := λ _, empty.elim,
   C := λ c, 1,
   }

  open term

  /-- t = f(g(c, f(v₅))) is a term on language L1.-/
  def t₁ : term L1 0 := app (func f) (var 5)            -- f(c)
  def t₂ : term L1 0 := app (app (func g) (con c)) t₁   -- g(c)(t₁)
  def t₃ : term L1 0 := app (func f) t₂                 -- f(t₂)
  def t : term L1 0 := app (func f)
                           $ app (app (func g) (con c))
                                 $ app (func f) (con c)
  def va : ℕ → M1.univ := function.const ℕ (M1.C c)

  #reduce term_interpretation va (func f)  -- f is interpreted as x ↦ 100x
  #reduce term_interpretation va (func g)  -- g is interpreted (x, y) ↦ x+y
  #reduce term_interpretation va (con c)   -- c is interpreted as (1 : ℕ)
  #reduce term_interpretation va t₁          -- f(c) is interpreted as 100
  #reduce term_interpretation va t₂          -- g(c, t₁) is interpreted as 101
  #reduce term_interpretation va t₃          -- f(g(c, f(c))) is interpreted as 10100
  #reduce term_interpretation va t           -- same as t₃

  def t₄ : term L1 0 := app (func f) (var 5) -- f(v₅)
  def t₅ : term L1 0 := app (app (func g) (con c)) (var 4) -- g(c, v₄)
  #reduce term_sub t₅ 0 t₄ -- f(g(c, v₄))
  #reduce term_sub (var 3) 0 t₄ -- f(v₃)
  #reduce term_sub_for_var (var 3) 4 0 t₄ -- f(v₅)
  #reduce term_sub_for_var (var 3) 5 0 t₄ -- f(v₃)

  open example_terms
  def ψ₁ : formula L1 := t₁ =' (var 5) -- f(c) = v₅
  def ψ₂ : formula L1 := ¬' (var 4 =' t₃ ) -- g(c, t₁) =/= v₄
  def ψ₃ : formula L1 := ∃' 3 ψ₁ -- ∃v₃  f(v₅) = v₅
  def ψ₄ : formula L1 := ∀' 4 (∀' 5 ψ₂) -- ∀v₄∀v₅ g(c, f(v₄)) =/= v₅

  example : ¬ (formula.var_occurs_freely 5 ψ₄) :=
  begin
    rw ψ₄,
    unfold formula.var_occurs_freely,
    rw ψ₂,
    simp,
  end

  def phi : formula (lang.DLO_lang) :=
  ¬'(∀' 2 ⊤') ∧' ((var 1) =' (var 4)) ∧' (∃' 3 (var 2 =' var 3))

  example :   formula.var_occurs_freely 1 phi :=
    by norm_num [phi, formula.var_occurs_freely]
  example :   formula.var_occurs_freely 2 phi :=
    by norm_num [phi, formula.var_occurs_freely]
  example : ¬ formula.var_occurs_freely 3 phi :=
    by norm_num [phi, formula.var_occurs_freely]
  example :   formula.var_occurs_freely 4 phi :=
    by norm_num [phi, formula.var_occurs_freely]
  example : ¬ formula.var_occurs_freely 5 phi :=
    by norm_num [phi, formula.var_occurs_freely]

end example_terms

namespace DLO_Model

@[reducible] def Q_struc : struc lang.DLO_lang :=
 { univ := ℚ,
   R := λ n f, by { cases n, cases n_val,
                      {linarith},
                    cases n_val,
                      {exact ∅},
                    cases n_val,
                      {exact {v : vector ℚ 2 | v.nth 0 < v.nth 1}},
                    exact ∅},
  F := λ _, empty.elim,
  C := empty.elim
 }
notation `<'` : 110 := @formula.rel lang.DLO_lang 2 ()

/- A dense linear ordering without endpoints is a language containg a
    single binary relation symbol < satisfying the following sentences:
-- 1. ∀x x < x;
-- 2. ∀x ∀y ∀z (x < y → (y < z → x < z));
-- 3. ∀x ∀y (x < y ∨ x = y ∨ y < x);
-- 4. ∀x ∃y x < y;
-- 5. ∀x ∃y y < x;
-- 6. ∀x ∀y (x < y → ∃z (x < z ∧ z < y)). -/

open term
@[reducible] def mk_vec (v₁ v₂ : ℕ) : vector (term lang.DLO_lang 0) 2 := ⟨[var v₁, var v₂], rfl⟩
@[reducible] def φ₁ : formula lang.DLO_lang := <' $ mk_vec 1 2 -- x < y
@[reducible] def φ₂ : formula lang.DLO_lang := <' $ mk_vec 2 1 -- y < x
@[reducible] def φ₃ : formula lang.DLO_lang := <' $ mk_vec 2 3 -- y < z
@[reducible] def φ₄ : formula lang.DLO_lang := <' $ mk_vec 3 2 -- z < y
@[reducible] def φ₅ : formula lang.DLO_lang := <' $ mk_vec 1 3 -- x < z
@[reducible] def φ₆ : formula lang.DLO_lang := <' $ mk_vec 1 1 -- x < x

def DLO_axioms : set (formula lang.DLO_lang) :=
 { ∀'1 (¬' φ₆),
   ∀'1 (∀'2 (∀'3 (φ₁ →' (φ₃ →' φ₅)))),
   ∀'1 (∀'2 ((φ₁ ∨' φ₂) ∨' (var 1 =' var 2))),
   ∀'1 (∃'2 (φ₁)),
   ∀'1 (∃'2 (φ₂)),
   ∀'1 (∀'2 (φ₁ →' ∃'3(φ₅ ∧' φ₄)))}

def DLO_theory : set (sentence lang.DLO_lang) :=
 { ⟨∀'1 (¬' φ₆), by {finish [formula.var_occurs_freely]}⟩,
   ⟨∀'1 (∀'2 (∀'3 (φ₁ →' (φ₃ →' φ₅)))),
     by {push_neg,
         rintros _ _ _ _ (⟨_ | _ | _⟩ | ⟨_ | _ | _⟩ | _ | _ | _);
         tauto}⟩,
   ⟨∀'1 (∀'2 (∀' 3 ((φ₁ ∨' φ₂) ∨' (var 1 =' var 2)))),
     by {push_neg,
         rintro (_ | _ | _ | _ | _ | _) _ _ _;
         exact dec_trivial <|> tauto}⟩,
   ⟨∀'1 (∃'2 (φ₁)),
     by {push_neg,
         rintros _ _ _ (_ | _ | _);
         tauto}⟩,
   ⟨∀'1 (∃'2 (φ₂)),
        by {push_neg,
            rintros _ _ _ (_ | _ | _);
            tauto}⟩,
   ⟨∀'1 (∀'2 (φ₁ →' ∃'3(φ₅ ∧' φ₄))),
        by {push_neg,
            rintros _ _ _ (⟨_ | _ | _⟩ | ⟨_, ⟨_ | _ | _⟩ | _ | _ | _⟩);
            tauto}⟩,
  }

def Q_Model_DLO : Model (DLO_theory) :=
 { M := Q_struc,
   satis :=
   begin
     rintros σ (⟨_, _⟩ | x | _ | ⟨_, _⟩ | ⟨_, _⟩ | H);
     use function.const ℕ (42 : ℚ), -- 42 is just an arbitrary value
     { rintros _ _ ⟨_, _⟩, tauto},
     { sorry },
     { sorry },
     { intros x,
       use x+1,
       split;
         simp only [vector.map, list.map, vector.nth, vector.head,
                    fin.val_one, term_interpretation, rat.le,
                    function.update_same, list.nth_le];
         norm_num;
         try {ring};
         dec_trivial},
     { intros x,
       use x-1,
       split;
         simp only [vector.map, list.map, vector.nth, vector.head,
                    fin.val_one, term_interpretation, rat.le,
                    function.update_same, list.nth_le];
         norm_num;
         try {ring};
         dec_trivial},
     repeat {sorry},
end

#exit

/-- DLO is complete by using Vaught's test. This will include the
    back-and-forth argument (Lou) which includes construct a sequence of
    partial isomorphisms and then stitch it together to create a big
    isomoprhism by zig-zagging back and forth over countable models of
    DLO.-/
theorem DLO_is_complete : complete_theory DLO_theory := sorry

end DLO_Model


/-
Lean's type hierarchy
-/

namespace hidden1

structure box (α : Type) : Type :=
(val : α)

def b3 : box nat := box.mk 3

/-
Every term has a type.
Types are terms, too.

The type of 3 is ℕ.
ℕ is a type, so it has a type.
The type of ℕ (#check) is Type.

So here's a picture so far.

     Type (aka Type 0)
      /  |  \ ..... \
  bool  nat string (box nat) ...
  / \    |    |        |
  tt ff  3   "Hi!" (box.mk 3)

We can pass 3 to box'.mk because
its type, nat, "belongs to" Type.

Can we pass nat to box'.mk?
-/

def bn := box.mk nat    -- No!

/-
A picture tells us what went wrong.

         ???
          |
        Type 0        (α := Type : ???)
        /  |  \
    bool nat string   (a := nat : Type)
    / \   |    |
    tt ff  3   "Hi!"

Above Type 0 is an infinite stack of
higher type universes, Type 1, Type 2,
etc.
-/

#check Type 0
#check Type 1
#check Type 2
-- etc

/-
Here's the key idea. If a term
contains, or is parameterized by,
a value in Type u, where u is any
natural number, then it must live
(at least) in Type (u+1).

In the our example, (a := nat : Type),
so (α := Type : Type 1)
-/

end hidden1

/-
We've defined our identity functions
to take (1) any type, α, of type Type,
and (2) any value, a, of the type give
as the value of α, and to return that
same value, a.

Knowing that types are terms, too,
the clever student will try to apply
our id functions to type-valued terms,
such as nat or bool. Alas, that won't
work.
-/

#eval id6 nat       -- Nope!

/-
Why? Let's analyze it. That's easier
if we make the implicit type argument
explicit using an _. We'll use our id2
version, which requires that the first,
type, argument (a value of type, Type)
be given explicitly. Let's start with
an example that works.
-/

#eval id2 nat 5

/-
Here 3 is value of type nat, and
nat is a value of type Type. The
type argument to box has to be a
value of type, Type (a shorthand
for Type 0), so nat will do fine,
as would bool, string, box nat, etc.

        Type 0 (Type)
        /  |  \
    bool nat string  (nat : Type)
    / \   |    |
    tt ff  3   "Hi!"  (3 : nat)

What does the picture look like with a = nat?

          ???
          |
        Type 0        (α := Type : ???)
        /  |  \
    bool nat string  (a := nat : Type)
    / \   |    |
    tt ff  3   "Hi!"
-/

/-
              ad inf
                |
              Type 2
                |
              Type 1
                |
              Type 0       (α := Type : ???)
             /  |  \
          bool nat string  (a := nat : Type)
          / \   |    |
         tt ff  3   "Hi!"
-/
-- A "sad" solution is to change to (α : Type 1)

def id7 {α : Type 1} (a : α) : α := a

-- Okay, it works when applied to types in Type 0
#reduce id7 nat   -- careful: nat is second argument!
#eval id bool
#eval id string

-- But now it's broken for *values* of these types

#eval id7 nat 5     -- hope for 5

-- And for values in the next "type universe" up

#eval id7 Type      -- hope for Type

/-
Here's the general picture.

               ... ad inf.      -- Type 2 is type of Type 1, etc
                |
             Type 1             -- Type of Type 0
                |
              Type 0            -- Type of usual types
             /  |  \
      ... bool nat string ...   -- usual types
          / \   |    |
         tt ff  5   "Hi!"  ...  -- values of these types
-/

/-
Solution is to make definition "polymorphic" in
type universes.
-/

universe u   -- let u be an arbitrary universe level (0, 1, 2, etc)

-- And let Lean infer u from context

def id {α : Type u} (a : α) : α := a

#reduce id 5      -- 5    belongs to nat,     nat     belongs to Type
#reduce id nat    -- nat  belongs to Type,    Type    belongs to Type 1
#reduce id Type   -- Type belongs to Type 1,  Type 1  belongs to Type 2
#reduce id (Type 10)  -- etc, ad infinitum

-- Yay!

/-
Why such complexity? It avoids certain inconsistencies.

Russell's Paradox.

Consider the set of all sets that do not contain themselves.

Does it contain itself?
If it doesn't contain itself then it must contain itself.
If it does contain itself then it mustn't contain itself.
Either assumption leads to a contradiction, and inconsistency.
Russell introduced stratified types to avoid this problem in set theory.
The hierarchy prevents any type from containing itself as a value.

The very concept of sets containing or not containing themselves
has to be excluded from consideration. Types that contain only
"lower" types and never themselves solves the problem. Bertrand
Russell is thus a great-great-granddaddy of this aspect of type
theory.

What belongs in higher type universes in practice? It's pretty
straightforward. If an object *contains a value* of type, Type u,
then that object lives in (at least) Type u+1. Details later on.
-/

/-
This material about type universes is quite abstract, so please
don't worry if it's going over your head at this point. Graduate
students should work hard to grasp it. For the most part I will
avoid defining types in full generality w.r.t. universe levels.
-/

end hidden


import data.nat.gcd

variables x y z : ℕ

#check dvd_mul_left
#check dvd_mul_right
#check @dvd_mul_of_dvd_left
#check @dvd_mul_of_dvd_right

example (h₀ : x ∣ y) (h₁ : y ∣ z) : x ∣ z :=
dvd_trans h₀ h₁

example : x ∣ y * x * z :=
begin
  apply dvd_mul_of_dvd_left,
  apply dvd_mul_left,
end

example : x ∣ x^2 :=
by apply dvd_mul_right


/-
Copyright (c) 2019 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Patrick Massot, Casper Putz, Anne Baanen
-/
import data.matrix.basic
import linear_algebra.finite_dimensional

/-!
# The finite-dimensional space of matrices

This file shows that `m` by `n` matrices form a finite-dimensional space,
and proves the `finrank` of that space is equal to `card m * card n`.

## Main definitions

 * `matrix.finite_dimensional`: matrices form a finite dimensional vector space over a field `K`
 * `matrix.finrank_matrix`: the `finrank` of `matrix m n R` is `card m * card n`

## Tags

matrix, finite dimensional, findim, finrank

-/

universes u v

namespace matrix

section finite_dimensional

variables {m n : Type*} {R : Type v} [field R]

instance [finite m] [finite n] : finite_dimensional R (matrix m n R) :=
linear_equiv.finite_dimensional (linear_equiv.curry R m n)

/--
The dimension of the space of finite dimensional matrices
is the product of the number of rows and columns.
-/
@[simp] lemma finrank_matrix [fintype m] [fintype n] :
  finite_dimensional.finrank R (matrix m n R) = fintype.card m * fintype.card n :=
by rw [@linear_equiv.finrank_eq R (matrix m n R) _ _ _ _ _ _ (linear_equiv.curry R m n).symm,
       finite_dimensional.finrank_fintype_fun_eq_card, fintype.card_prod]

end finite_dimensional

end matrix