--lecture on propositional logic
variables P Q R : Prop

--proposition :definitive statement which we may be able to prove
--propositional connectives

#check P ∧ Q --and, conjunction
#check P ∨ Q -- or, disconjunction
#check P → Q -- if-then, implication
#check ¬ P --not, negation
-- ¬ P = P → false
#check P ↔ Q --if and only if, equivalence
#check false
#check true

--P → (Q→ R)

--tautologies: If we are proving a statement containing propositional variables then this means that the statement is true for all replacements of the variables with actual propositions. We say it is a tautology.

theorem I: P → P :=
begin
  assume h,
  exact h,
end

theorem C : (P → Q) → (Q → R) → (P → R) :=
begin
  assume p2q,
  assume q2r,
  assume p,
  apply q2r, --q->r, then r can be replaced by q, goal is q
  apply p2q, --goal is p
  exact p,
end

theorem swap : (P → Q → R) → (Q → P → R) :=
begin
  assume left,
  assume lq,
  assume lp,
  apply left, -- two goals
  exact lp,
  exact lq,
end

#print I
#print C
#print swap

/--
  ASSUME => to prove = right after |-
  APPLY => to use
  EXACT
--/

example : P → Q → P ∧ Q :=
begin
  assume p q, -- p → q → r
  constructor, -- turns goal into two goals
  exact p,
  exact q,
end

theorem comAnd : P ∧ Q → Q ∧ P :=
begin
  assume pq,
  cases pq with p q, -- p ∧ q becomes p and q
  constructor,
  exact q,
  exact p,
end

theorem curry: (P → Q → R) ↔ (P ∧ Q → R) :=
begin
  constructor, -- left to right, and right to left
  assume lhs,
  assume pq,
  cases pq with p q,
  apply lhs, -- two goals become three goals necuase p q r in consequence
  exact p,
  exact q,

  assume rhs,
  assume p q,
  apply rhs, -- two goals become three goals necuase p q r in consequence
  constructor,
  exact p,
  exact q,
end

/--
  constuctor => to prove conjunction
  cases h with x y => to use conjunction
--/

--disconjunction, or, ∨
example: P → P ∨ Q :=
begin
  assume p,
  left, --- either prove P or we can prove Q
  exact p,
end

example: Q → P ∨ Q :=
begin
  assume q,
  right, --- either prove P or we can prove Q
  exact q,
end

theorem case_lem : (P → R) → (Q → R) → P ∨ Q → R :=
begin
  assume pr qr pq,
  cases pq with p q, --seperate two cases, either p or q
  apply pr,
  exact p,

  apply qr,
  exact q,
end

/--
  to prove disconjunction -> left, right
  to use an assumption of disconjunction -> cases h with x y
--/

example: P ∨ Q → Q ∨ P :=
begin
  assume pq,
  cases pq with p q,
  right,
  exact p,
  left,
  exact q,
end

example : true :=
begin
  trivial, --/constructor -- can only prove
end

--efq, Ex falso quod libet
--from false follows everything.
-- If pigs can fly then I am the president of America
theorem efq: false → P :=
begin
  assume f,
  cases f, -- can only use
end

-- ¬P = P → false
theorem contr: ¬ (P ∧ ¬ P) :=
begin
  assume pnp, --goal turns to false
  cases pnp with p np,
  apply np, -- because ¬P = P → false
  exact p,
end

/--
  to prove true : trival
  to use false: cases (no with)
--/

--The truth based logic is called classical logic
--evidence based one is called intuitionistic logic

--de morgen law
/--
  ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q
  ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q
--/
theorem dm1 : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
-- p ↔ q = p → q ∧ q → p
begin
  constructor, --to prove ∧ use constructor
  assume npq,
  constructor,
  assume p, --not p = p -> false
  apply npq,
  left,
  exact p,

  assume q,
  apply npq,
  right,
  exact q,

  assume npnq,
  assume pq,
  cases npnq with np nq, --to use and , cases with x y
  cases pq with p q, --to use or , cases with x y
  apply np,
  exact p,

  apply nq,
  exact q,
end

--law of exclude middle : P ∨ ¬ P
--em = excluded middle
--the third is not given
open classical
#check em P

--It is not the case that I have a cat and that I have a dog
--I don’t have a cat or I don’t have a dog ?
--NO
theorem dm2 : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q := -----em
begin
  constructor, -- to prove and
  assume npq,
  cases (em P) with p np, -- add one more case
  right,
  assume q,
  apply npq,
  constructor,
  apply p,

  apply q,

  left,
  apply np,

  assume npnq pq,
  cases npnq with np nq,
  apply np,
  cases pq with p q,
  exact p,

  apply nq,
  cases pq with p q,
  exact q,
end
-- raa equivalent to the principle of excluded middle
--indirect proof
theorem raa : ¬ ¬ P → P :=
begin
  assume nnp,
  cases (em P) with p np,
  exact p,
  apply efq, -- false -> P
  apply nnp,
  exact np
end

theorem nn_em : ¬ ¬ (P ∨ ¬ P) :=
begin
  assume npnp,
  apply npnp,
  right,
  assume p,
  apply npnp,
  left,
  exact p
end

--em and raa are equivalent

--predicate logic
--Predicate logic extends propositional logic, we can use it to talk about objects and their properties.
--types (sets)
#check ℕ -- \nat--\bn
#check list ℕ
#check bool
variables A B C : Type

--predicates
--Prime: ℕ → Prop
--Prime 3 : Prop -- 3 is a prime number

--A = type of students
--isClever : A → Prop

--relations: leq : ℕ -> (ℕ → Prop)
--we write x ≤ y for leq x y
-- 3 ≤ 4 : Prop
--leq 3 4 : Prop

variables PP QQ : A → Prop --two predicates PP QQ
--PP x means x is clever
--QQ x means x is funny

--quantifiers
-- ∀ : for all, universal quantifier
-- ∃ : exists, existential quantifier
-- (∀ x : A, PP x) all students are clever
-- (∃ x : A, PP x) there is a clever student

--equality
-- a b : A , we can form a = b: Prop, means a is equal to b

--proofs
--∀ x : A, PP x
--how to prove: assume h
--how ro use: apply h --h : ∀ x : A, PP x, and we want to prove PP a

--If all students are clever
--then if all clever students are funny
--then all students are funny.
example : (∀ x : A, PP x) → (∀ y : A, PP y → QQ y) → ∀ z : A , QQ z :=
begin
  assume h g,
  assume george, --using george as an example of z
  apply g, --pp y -> qq y => pp george
  apply h, --pp x
end

--all students are clever and funny is the same as saying that all students are clever and all students are funny.
example : (∀ x : A, PP x ∧ QQ x) ↔ (∀ x : A , PP x) ∧ (∀ x : A, QQ x) :=
begin
  constructor,
  assume h,
  constructor,
  assume y,
  have pq : PP y ∧ QQ y, --change the goal? add one more assumption?
  apply h, --one goal eliminated
  cases pq with p q,
  exact p, --exact the same, use exact

  assume y,
  have pq2 : PP y ∧ QQ y, --change the goal?
  apply h, --one goal eliminated
  cases pq2 with p q,
  exact q,

  assume g,
  cases g with pp qq,
  assume z,
  constructor,
  apply pp, --x and z are same format but not same element, use apply
  apply qq,
end

-- ∃ x : A, PP x
--how to prove: existsi a, to show PP a
-- h : ∃ x : A, PP x
--how to use: cases h with a p, given a : A, p : PP a

--If there is a clever student and all clever students are funny then there is a funny student.
example : (∃ x : A, PP x)
  → (∀ y : A, PP y → QQ y)
  → ∃ z : A , QQ z :=
begin
  assume g h,
  cases g with a p, -- g : ∃ (x : A), PP x => a : A, p : PP a
  existsi a,
  apply h,
  exact p,
end

--There is a student who is clever or funny is the same as saying there is a student who is funny or there is a student who is clever.
example : (∃ x : A, PP x ∨ QQ x)
              ↔ (∃ x : A , PP x) ∨ (∃ x : A, QQ x) :=
begin
  constructor,
  assume h,
  cases h with a g,
  cases g with p q,
  left,
  existsi a, -- there is already an a as variable on the top
  exact p,
  right,
  existsi a,
  exact q,

  assume h,
  cases h with p q,
  cases p with a pa, -- have to create a variable at first
  existsi a,
  left,
  exact pa,
  cases q with a q,
  existsi a,
  right,
  exact q,
end

example : (∀  x : A, PP x ∨ QQ x)
              ↔ (∀  x : A , PP x) ∨ (∀ x : A, QQ x) :=
begin
  sorry
end
/--
(∀  x : A , PP x) ∨ (∀ x : A, QQ x) -> (∀  x : A, PP x ∨ QQ x)
--/

example : (∃ x : A, PP x ∧  QQ x)
              ↔ (∃ x : A , PP x) ∧ (∃ x : A, QQ x) :=
begin
  sorry
end
/--
(∃ x : A, PP x ∧  QQ x)
              →  (∃ x : A , PP x) ∧ (∃ x : A, QQ x)
--/

--have aux: P
--first i have to prove P
--then I can use aux : P

variable People : Type

variable Loves : People → People → Prop

--everybody loves somebody
--predicate logic
#check ∀ x : People, ∃ y : People, Loves x y

--there is somebody who is loved by everyone
#check ∃ y : People, ∀ x : People, Loves x y

example: (∃ y : People, ∀ x : People, Loves x y) → (∀ x : People, ∃ y : People, Loves x y) :=
begin
  assume h,
  assume g,
  cases h with a alla, --there is no other object for exist to use, we can only use exist to move on
  existsi a,
  apply alla,
end

--everybody loves themselves
#check ∀ x : People, Loves x x

--everybody loves at most one person
#check ∀ x y z: People, Loves x y → Loves x z → y = z

--everybody only loves themselves
#check ∀ x y : People, Loves x y → x = y

example: (∀ x : People, Loves x x) → (∀ x y z: People, Loves x y → Loves x z → y = z) → (∀ x y : People, Loves x y → x = y) :=
begin
  assume h g,
  assume a b,
  assume ab,
  apply g, --question mark here
  apply h, --everybody loves themselves
  exact ab,
end

--currying equivalent
-- (P ∧ Q → R) ↔ (P→ Q → R)
--((∃ x : A, PP x) → R)  ↔ (∀ x : A , PP x → R)
theorem curry_pred : ((∃ x : A, PP x) → R)  ↔ (∀ x : A , PP x → R)  :=
begin
  constructor,
  assume h,
  assume a,
  assume g,
  apply h,
  existsi a,
  exact g,

  assume h g,
  cases g with a ppa,
  apply h,
  exact ppa, ---? question mark here
  --apply ppa,
end

--equality
example : ∀ x : A, x = x :=
begin
  assume h,
  reflexivity,
end

example:  ∀ x y : A, x = y → PP y → PP x :=
begin
  assume a b,
  assume h,
  assume g,
  rewrite h, --change the goal using equality
  exact g,
end

example:  ∀ x y : A, x=y → PP x → PP y :=
begin
  assume a b,
  assume h,
  assume g,
  rewrite ← h, -- another direction of equality, from right to left
  exact g,
end

/--
  Equality is an equivalence relation, it mens that it is

  reflexive (∀ x : A, x=x),
  symmetric (∀ x y : A, x=y → y=x)
  transitive (∀ x y z : A, x=y → y=z → x=z)
--/

theorem sym_eq : ∀ x y : A, x = y → y=x :=
begin
  assume x y p,
  rewrite p, ---automatically uses reflexivity
end

theorem trans_eq : ∀ x y z : A, x=y → y=z → x=z :=
begin
  assume x y z,
  assume xy yz,
  rewrite xy,
  exact yz,
end
/--equality
  to prove: reflexivity
  to use: rewrite h && rewrite ← h
--/
example : ∀ x y : A, x=y → y=x :=
begin
  assume x y p,
  symmetry,
  exact p,
end

example : ∀ x y z : A, x=y → y=z → x=z :=
begin
  assume x y z xy yz,
  transitivity, --x = ? = z
  exact xy,
  exact yz,
end

example : ∀ x y z : A, x=y → y=z → x=z :=
begin
  assume x y z xy yz,
  calc
    x = y   : by exact xy
    ... = z : by exact yz, -- last expression of the previous line, in this case y.
end

--de morgan for propositional logic
-- ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q
-- ¬ (∃ x : A, PP x) ↔ ∀ x : A, ¬ PP x
-- ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q
-- ¬ (∀ x : A, PP x) ↔ ∃ x : A, ¬ PP x
example : ¬ P ∨ ¬ Q → ¬ (P ∧ Q):=
begin
  assume h,
  assume pq,
  cases pq with p q,
  cases h with np nq,
  apply np,
  exact p,

  apply nq,
  exact q,
end

theorem dm1_pred : ¬ (∃ x : A, PP x) ↔ ∀ x : A, ¬ PP x :=
begin
  constructor,
  assume h,
  assume a,
  assume ppa,
  apply h,
  existsi a,
  exact ppa,

  assume h,
  assume ppx,
  cases ppx with a ppa,
  apply h,
  exact ppa,
end

--classical logic needed
theorem dm2_pred : ¬ (∀ x : A, PP x) ↔ ∃ x : A, ¬ PP x :=
begin
  constructor,
  assume h,
  apply raa, --!!q = q, add !! to goal
  assume npp,

  apply h,
  assume a,
  apply raa,---double time
  assume nppa,
  apply npp,
  existsi a,
  exact nppa,

  assume h,
  assume nppx,
  cases h with a nppa,
  apply nppa,
  apply nppx,
end
/--
  in every non-empty pub, there is one person such that if this person drinks then everybody is drinking. true
  A = prople in the pub
  PP x = x is drinking
--/

theorem aux_thm: (¬ ∀ (x : A), PP x) →   ∃ x : A, ¬ PP x :=
begin
    assume h,
    apply raa,
    assume ng,
    apply h,
    assume x,
    apply raa,
    assume np,
    apply ng,
    existsi x,
    exact np,
end

theorem drinker: (∃ x : A, true) → ∃ x : A, PP x → ∀ x : A, PP x :=
--(∃ x : A,  true) → (∃ x:A, (PP x → ∀ x : A,PP x)) :=
begin
  ---b) provable classically
  assume atr,
  cases atr with a tr,
  cases em (∀ x : A, PP x) with app napp,  --create two assumptions, P and ¬ P
  existsi a,
  assume ppa,
  exact app,

  have h :(∃ x: A, ¬ PP x),  --one changes goal, one adds as assumption
  apply aux_thm, --we know napp and current goal are equal
  exact napp,

  cases h with a1 npp,
  existsi a1,
  assume ppa1,
  have f: false,
  apply npp,
  exact ppa1,

  cases f, --to use false
end

theorem ex09 : (∃ x : A, true) → (∃ x:A, PP x) → ∀ x : A,PP x :=
begin ---c) not provable classically
  sorry
end


-- gcd and div and mod and lt and le are all in core
-- but most of the theorems about them are here

import data.int.basic
import data.int.order
import data.nat.gcd

-- a congruent to b modulo m
def cong (a:int) (b: int) (m: int): Prop := m ∣ a - b

-- p is prime
def is_prime (p:nat): Prop := ∀ x y: int, ↑p ∣ (x*y) → ↑p ∣ x ∨ ↑p ∣ y

-- Solution to linear diophantine equation with constants a b k
def LDE (a:int) (b:int) (k:int): Prop := ∃ x y: int, a*x + b*y = k

theorem WOP {k: nat} (p:nat → Prop) (H: p k): ∃ n: nat, p n ∧ (∀ y:nat, p y → y ≥ n) :=
begin
    revert H,
    apply @nat.strong_induction_on (λ h, p h → (∃ (n : ℕ), p n ∧ ∀ (y : ℕ), p y → y ≥ n)) k,
    intros x Hx Hpx,
    induction x,
    apply exists.intro 0,
    split, exact Hpx,
    intros, exact nat.zero_le y,
    cases classical.em (∃ y, y < nat.succ a ∧ p y),
    cases a_1,
    apply Hx, apply a_2.1, apply a_2.2,
    apply exists.intro (nat.succ a),
    split, assumption,
    intros,
    by_contradiction,
    apply a_1,
    apply exists.intro y,
    exact ⟨lt_of_not_ge a_3,a_2⟩
end

--

lemma SwapSums (a b x : int) : (a-b) + (-x + x) = (a-x) - (b-x) :=
begin
    simp, rw [add_comm x], simp
end

lemma NegCommViaMul (a:int) (b:int) : (-1)*(a - b) = b - a := by simp

lemma simplifyTransum (a: int) (b: int) (c: int) : (a-b) + (b-c) = a - c := by simp [add_assoc]

theorem Mreflex (a:int) (m: int): cong a a m :=
begin
    unfold cong,
    apply exists.intro (0:ℤ),
    simp
end

theorem Msymmetric {a b : int} {m: int} (H1: cong a b m): cong b a m :=
begin
    cases H1,
    apply exists.intro (-a_1),
    simp,
    rw ←a_2,
    simp
end

theorem Mtrans {a b c: int} (m: nat) (H1: cong a b m) (H2: cong b c m): cong a c m :=
begin
    cases H1,
    cases H2,
    apply exists.intro (a_1 + a_3),
    rw [mul_add,←a_2,←a_4],
    simp,
    rw [←add_assoc b],
    simp
end

theorem Mmul {x a b: int} (n:int) (H1: cong a b n) : cong (a*x) (b*x) n:=
begin
    cases H1,
    apply exists.intro (a_1*x),
    rw [←mul_assoc,←a_2,sub_mul]
end

theorem Msub {a b: int} {n:nat} (x:int) (H1: cong a b n) : cong (a-x) (b-x) n:=
begin
    unfold cong at *,
    simp at *,
    rw [add_comm x,add_assoc],
    simp,
    exact H1
end

theorem MinsertLeft {a b c: int} {n:int} (H1: cong a b n) (H2: a = c): cong c b n := begin
    rw H2 at H1,
    exact H1
end

theorem MinsertRight {a b c: int} {n:int} (H1: cong a b n) (H2: b = c): cong a c n:=
begin
    rw H2 at H1,
    exact H1
end

theorem Madd {a b: int} {n:nat} (x: int) (H1: cong a b n) : cong (a+x) (b+x) n:=
begin
    unfold cong at *,
    simp at *,
    rw [add_comm x,add_assoc],
    simp,
    exact H1
end

theorem Mcancel {p:nat} {a b x} (H1: is_prime p) (H2: cong (x*a) (x*b) p): (cong a b p) ∨ ↑p ∣ x  :=
begin
    unfold is_prime at H1,
    unfold cong at *,
    rw ←mul_sub at H2,
    simp at *,
    cases H2,
    cases (H1 x (a-b) _),
    left, exact a_3,
    right, exact a_3,
    exact ⟨a_1,a_2⟩
end

theorem MDsum {a b c d n: int} (H1: cong a b n) (H2: cong c d n): cong (a+c) (b+d) n :=
begin
    unfold cong at *,
    simp [add_assoc],
    rw [add_comm c,add_assoc,add_comm (-d),←add_assoc],
    cases H1,
    cases H2,
    simp at a_2,
    simp at a_4,
    rw [a_2,a_4,←mul_add],
    apply exists.intro (a_1+a_3),
    refl
end

theorem basicInequality {a b : int} (H1: b ∣ a) (HA: a > 0) : a ≥ b :=
begin
    cases b,
    cases a_1,
    apply le_of_lt HA,
    cases H1,
    simp at *,
    rw nat.succ_eq_add_one,
    change a ≥ a_1+1,
    apply int.add_one_le_of_lt,
    cases a_2,
    cases a_2,
    change a = 0*(a_1+1) at a_3,
    simp at a_3,
    apply false.elim (ne_of_lt HA a_3.symm),
    rw a_3,
    change (a_1:ℤ) < ↑((nat.succ a_2) * (nat.succ a_1)),
    suffices : 1 * a_1 < (nat.succ a_2) * (nat.succ a_1),
        simp at *,
        apply int.coe_nat_lt_coe_nat_of_lt,
        exact this,
    apply mul_lt_mul',
        apply nat.le_add_left,
        apply nat.lt_succ_self,
        apply nat.zero_le,
        apply nat.zero_lt_succ,
    have HA1: a ≤ 0,
        apply int.le_of_lt,
        apply int.neg_of_sign_eq_neg_one,
        rw a_3,
        apply int.sign_mul,
    apply false.elim (((lt_iff_not_ge _ _).1 HA) HA1),
    apply le_of_lt,
    apply lt_of_lt_of_le,
    apply int.neg_of_sign_eq_neg_one,
    simp [int.sign],
    apply le_of_lt HA
end

-- Division algorithm
-- depedencies on data.int.basic and data.int.order
theorem DivAlgo (a : int) (b : int) (Hb : b>0): ∃ q r : int, a = b*q + r ∧ 0 ≤ r ∧ b > r :=
begin
    apply exists.intro (a/b),
    apply exists.intro (a%b),
    split,
    rw add_comm,
    rw int.mod_add_div,
    split,
    apply int.mod_nonneg a (int.ne_of_lt Hb).symm,
    apply int.mod_lt_of_pos, exact Hb
end

--Lemma for proving that Z is a principal ideal domain later on

lemma LDEsimp {a b p :int} (W1: (LDE a b p ∧ p > 0) ∧ (∀ (q: int), LDE a b q ∧ q > 0 → q ≥ p)) : p ∣ a :=
begin
    cases W1.1.1 with x W2,
    cases W2 with y W3,
    have App: ∃ m n: int, a = p*m + n ∧ 0 ≤ n ∧ p > n, from DivAlgo a p W1.1.2,
    cases App with m D1,
    cases D1 with n D,
    have D1 := D.1,
    rw ←W3 at D1,
    have A: a*(1-x*m) + b*(-(y*m)) = n,
        simp [mul_add],
        rw [←neg_add,←mul_assoc,←mul_assoc],
        apply add_neg_eq_of_eq_add,
        rw [←add_mul,add_comm],
        apply D1,
    have Z1: n = 0,
        cases lt_or_eq_of_le D.2.1,
            have C0: LDE a b n := ⟨1-x*m,⟨-(y*m),A⟩⟩,
            have C1: n ≥ p := W1.2 n ⟨C0,a_1⟩,
            apply false.elim ((not_lt_of_ge C1) D.2.2),
            simp [a_1],
    simp [W3,Z1] at D1,
    exact ⟨m,D1⟩
end

lemma LDEcomm {a b p: int} (H: LDE a b p) : LDE b a p :=
begin
    cases H,
    cases a_2,
    rw add_comm at a_3,
    apply exists.intro,
    apply exists.intro,
    exact a_3
end

lemma PisGCD {j b p: int} (W2: p > 0) (W11: LDE j b p) (y: int): y ∣ j ∧ y ∣ b → p ≥ y :=
begin
    intro P,
    have F: y ∣ p,
        cases P.1,
        cases P.2,
        simp [LDE] at *,
        cases W11,
        cases a_5,
        rw [a_1,a_3,mul_assoc,mul_assoc,←mul_add] at a_6,
        simp [has_dvd.dvd],
        exact ⟨_,a_6.symm⟩,
    exact basicInequality F W2
end

theorem mul_sign : ∀ (i : int), i * int.sign i = int.nat_abs i
| (n+1:ℕ) := by {simp [int.sign], rw ←int.abs_eq_nat_abs, refl}
| 0       := by {simp [int.sign], refl}
| -[1+ n] := by {simp [int.sign], refl}

theorem sign_mul : ∀ (i : int), int.sign i * i = int.nat_abs i
| (n+1:ℕ) := by {simp [int.sign], rw ←int.abs_eq_nat_abs, refl}
| 0       := by {simp [int.sign], refl}
| -[1+ n] := by {simp [int.sign], refl}

theorem nat_le_int {j b : ℤ} {n : ℕ} (hn : ∀ (y : ℕ), LDE j b y ∧ y > 0 → y ≥ n) {q : ℤ} (hq : LDE j b q ∧ q > 0) : q ≥ n :=
begin
    have hqq : q = ↑(int.nat_abs q),
    induction q,
    refl,
    exfalso,
    apply not_le_of_gt hq.2,
    apply le_of_lt,
    apply (int.sign_eq_neg_one_iff_neg _).1,
    simp [int.sign],
    have hqn : int.nat_abs q ≥ n,
    apply hn,
    exact ⟨hqq ▸ hq.1, (int.nat_abs_pos_of_ne_zero (int.ne_of_lt hq.2).symm)⟩,
    have hqn := int.coe_nat_le_coe_nat_of_le hqn,
    exact hqq.symm ▸ hqn,
end

theorem IntegersFormPID (j : int) (b : int): LDE j b (int.gcd j b) :=
let p := int.gcd j b in
begin
    cases classical.em (j=0),
    rw a,
    unfold LDE,
    simp [int.gcd],
    change ∃ (y : ℤ), b * y = ↑(nat.gcd 0 (int.nat_abs b)),
    rw nat.gcd_zero_left,
    apply exists.intro,
    exact mul_sign b,
    have H : ∃ n: nat, (LDE j b n ∧ n > 0) ∧ (∀ y:nat, (LDE j b y ∧ y > 0) → y ≥ n),
        apply @WOP (int.nat_abs j + int.nat_abs b) (λ h, LDE j b h ∧ h > 0),
        split,
        apply exists.intro (int.sign j),
        apply exists.intro (int.sign b),
        rw [mul_sign,mul_sign],
        refl,
        have H: int.nat_abs j > 0,
            apply nat.lt_of_le_and_ne,
            apply nat.zero_le,
            intro H, apply a, exact int.eq_zero_of_nat_abs_eq_zero H.symm,
        apply nat.add_pos_left H,
    cases H with n hn,
    have Hj : ↑n ∣ j,
        apply LDEsimp,
        split,
        split,
        exact hn.1.1,
        exact int.coe_nat_lt_coe_nat_of_lt hn.1.2,
        intros q hq,
        apply nat_le_int,
        intros y hy,
        exact hn.2 y hy,
        exact hq,
    have Hb : ↑n ∣ b,
        apply LDEsimp,
        split,
        split,
        exact LDEcomm hn.1.1,
        exact int.coe_nat_lt_coe_nat_of_lt hn.1.2,
        intros q hq,
        apply nat_le_int,
        intros y hy,
        exact hn.2 y hy,
        rw (iff.intro LDEcomm LDEcomm),
        exact hq,
    have Hp : nat.gcd (int.nat_abs j) (int.nat_abs b) = p, refl,
    cases (nat.gcd_dvd_left (int.nat_abs j) (int.nat_abs b)),
    rw Hp at a_2,
    have a_2 : int.sign j * int.nat_abs j = int.sign j * p * a_1 := by {rw [a_2,mul_assoc], refl},
    rw int.sign_mul_nat_abs at a_2,
    have Hpn : n ∣ p,
        unfold has_dvd.dvd at *,
        cases Hj,
        cases Hb,
        apply nat.dvd_gcd,
        have a_4 : int.nat_abs j = n * int.nat_abs a_3,
        rw [a_4,int.nat_abs_mul], refl,
        exact exists.intro _ a_4,
        have a_6 : int.nat_abs b = n * int.nat_abs a_5,
        rw [a_6,int.nat_abs_mul], refl,
        exact exists.intro _ a_6,
    have Hnp : p ∣ n,
        have hj : p ∣ int.nat_abs j := nat.gcd_dvd_left (int.nat_abs j) (int.nat_abs b),
        have hb : p ∣ int.nat_abs b := nat.gcd_dvd_right (int.nat_abs j) (int.nat_abs b),
        have h := hn.1.1,
        unfold LDE at h,
        cases hj,
        cases hb,
        have a_4 : j = p * (a_3 * int.sign j),
            suffices : int.sign j * int.nat_abs j = p * (a_3 * int.sign j),
            rw [int.sign_mul_nat_abs] at this, exact this,
            rw a_4,
            simp, refl,
        have a_6 : b = p * (a_5 * int.sign b),
            suffices : int.sign b * int.nat_abs b = p * (a_5 * int.sign b),
            rw [int.sign_mul_nat_abs] at this, exact this,
            rw a_6,
            simp, refl,
        cases hn.1.1,
        cases a_8,
        rw [a_4,a_6,mul_assoc,mul_assoc,mul_assoc,mul_assoc,←mul_add] at a_9,
        have a_9 := congr_arg int.nat_abs a_9,
        rw [int.nat_abs_mul] at a_9,
        change p * _ = n at a_9,
        apply exists.intro,
        apply a_9_1.symm,
    have H : n = p := nat.dvd_antisymm Hpn Hnp,
    rw H at hn,
    exact hn.1.1
end


/-
Copyright (c) 2022 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa

! This file was ported from Lean 3 source module tactic.compute_degree
! leanprover-community/mathlib commit 2d915e4ef8f55de94a850f0e5363ba8b25dc4c29
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Polynomial.Degree.Lemmas

/-! # `compute_degree_le` a tactic for computing degrees of polynomials

This file defines the tactic `compute_degree_le`.

Using `compute_degree_le` when the goal is of the form `f.nat_degree ≤ d`, tries to solve the goal.
It may leave side-goals, in case it is not entirely successful.

See the doc-string for more details.

##  Future work

* Deal with goals of the form `f.(nat_)degree = d` (PR #14040 does exactly this).
* Add better functionality to deal with exponents that are not necessarily closed natural numbers.
* Add support for proving goals of the from `f.(nat_)degree ≠ 0`.
* Make sure that `degree` and `nat_degree` are equally supported.

##  Implementation details

We start with a goal of the form `f.(nat_)degree ≤ d`.  Recurse into `f` breaking apart sums,
products and powers.  Take care of numerals, `C a, X (^ n), monomial a n` separately. -/

namespace Tactic

namespace ComputeDegree

open Expr Polynomial

-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      `guess_degree e` assumes that `e` is an expression in a polynomial ring, and makes an attempt
      at guessing the `nat_degree` of `e`.  Heuristics for `guess_degree`:
      * `0, 1, C a`,      guess `0`,
      * `polynomial.X`,   guess `1`,
      *  `bit0/1 f, -f`,  guess `guess_degree f`,
      * `f + g, f - g`,   guess `max (guess_degree f) (guess_degree g)`,
      * `f * g`,          guess `guess_degree f + guess_degree g`,
      * `f ^ n`,          guess `guess_degree f * n`,
      * `monomial n r`,   guess `n`,
      * `f` not as above, guess `f.nat_degree`.

      The guessed degree should coincide with the behaviour of `resolve_sum_step`:
      `resolve_sum_step` cannot solve a goal `f.nat_degree ≤ d` if `guess_degree f < d`.
       -/
    unsafe
  def
    guess_degree
    : expr → tactic expr
    | q( Zero.zero ) => pure q( 0 )
      | q( One.one ) => pure q( 0 )
      | q( - $ ( f ) ) => guess_degree f
      | app q( ⇑ C ) x => pure q( 0 )
      | q( X ) => pure q( 1 )
      | q( bit0 $ ( a ) ) => guess_degree a
      | q( bit1 $ ( a ) ) => guess_degree a
      |
        q( $ ( a ) + $ ( b ) )
        =>
        do
          let [ da , db ] ← [ a , b ] . mapM guess_degree
            pure <| expr.mk_app q( ( max : ℕ → ℕ → ℕ ) ) [ da , db ]
      |
        q( $ ( a ) - $ ( b ) )
        =>
        do
          let [ da , db ] ← [ a , b ] . mapM guess_degree
            pure <| expr.mk_app q( ( max : ℕ → ℕ → ℕ ) ) [ da , db ]
      |
        q( $ ( a ) * $ ( b ) )
        =>
        do
          let [ da , db ] ← [ a , b ] . mapM guess_degree
            pure <| expr.mk_app q( ( ( · + · ) : ℕ → ℕ → ℕ ) ) [ da , db ]
      |
        q( $ ( a ) ^ $ ( b ) )
        =>
        do let da ← guess_degree a pure <| expr.mk_app q( ( ( · * · ) : ℕ → ℕ → ℕ ) ) [ da , b ]
      | app q( ⇑ ( monomial $ ( n ) ) ) x => pure n
      |
        e
        =>
        do
          let q( @ Polynomial $ ( R ) $ ( inst ) ) ← infer_type e
            let pe ← to_expr ` `( @ natDegree $ ( R ) $ ( inst ) ) true false
            pure <| expr.mk_app pe [ e ]
#align tactic.compute_degree.guess_degree tactic.compute_degree.guess_degree

-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      `resolve_sum_step` assumes that the current goal is of the form `f.nat_degree ≤ d`, failing
      otherwise.  It tries to make progress on the goal by progressing into `f` if `f` is
      * a sum, difference, opposite, product, or a power;
      * a monomial;
      * `C a`;
      * `0, 1` or `bit0 a, bit1 a` (to deal with numerals).

      The side-goals produced by `resolve_sum_step` are either again of the same shape `f'.nat_degree ≤ d`
      or of the form `m ≤ n`, where `m n : ℕ`.

      If `d` is less than `guess_degree f`, this tactic will create unsolvable goals.
      -/
    unsafe
  def
    resolve_sum_step
    : tactic Unit
    :=
      do
        let t ← target >>= instantiate_mvars
          let
            q( natDegree $ ( tl ) ≤ $ ( tr ) )
              ←
              whnf t reducible
              | throwError "Goal is not of the form `f.nat_degree ≤ d`"
          match
            tl
            with
            |
                q( $ ( tl1 ) + $ ( tl2 ) )
                =>
                refine ` `( ( natDegree_add_le_iff_left _ _ _ ) . mpr _ )
              | q( $ ( tl1 ) - $ ( tl2 ) ) => refine ` `( ( natDegree_sub_le_iff_left _ ) . mpr _ )
              |
                q( $ ( tl1 ) * $ ( tl2 ) )
                =>
                do
                  let [ d1 , d2 ] ← [ tl1 , tl2 ] . mapM guess_degree
                    refine
                      `
                        `(
                          natDegree_mul_le . trans
                            <|
                            ( add_le_add _ _ ) . trans ( _ : $ ( d1 ) + $ ( d2 ) ≤ $ ( tr ) )
                          )
              | q( - $ ( f ) ) => refine ` `( ( natDegree_neg _ ) . le . trans _ )
              | q( X ^ $ ( n ) ) => refine ` `( ( natDegree_X_pow_le $ ( n ) ) . trans _ )
              |
                app q( ⇑ ( @ monomial $ ( R ) $ ( inst ) $ ( n ) ) ) x
                =>
                refine ` `( ( natDegree_monomial_le $ ( x ) ) . trans _ )
              |
                app q( ⇑ C ) x
                =>
                refine ` `( ( natDegree_C $ ( x ) ) . le . trans ( Nat.zero_le $ ( tr ) ) )
              | q( X ) => refine ` `( natDegree_X_le . trans _ )
              | q( Zero.zero ) => refine ` `( natDegree_zero . le . trans ( Nat.zero_le _ ) )
              | q( One.one ) => refine ` `( natDegree_one . le . trans ( Nat.zero_le _ ) )
              | q( bit0 $ ( a ) ) => refine ` `( ( natDegree_bit0 $ ( a ) ) . trans _ )
              | q( bit1 $ ( a ) ) => refine ` `( ( natDegree_bit1 $ ( a ) ) . trans _ )
              |
                q( $ ( tl1 ) ^ $ ( n ) )
                =>
                do
                  refine ` `( natDegree_pow_le . trans _ )
                    refine
                      `
                        `(
                          dite
                            ( $ ( n ) = 0 )
                              (
                                fun
                                  n0
                                    : $ ( n ) = 0
                                    =>
                                    by simp only [ n0 , MulZeroClass.zero_mul , zero_le ]
                                )
                              _
                          )
                    let n0 ← get_unused_name "n0" >>= intro
                    refine
                      `
                        `(
                          ( mul_comm _ _ ) . le . trans
                            ( ( Nat.le_div_iff_mul_le' ( Nat.pos_of_ne_zero $ ( n0 ) ) ) . mp _ )
                          )
                    let
                      lem1
                        ←
                        to_expr ` `( Nat.mul_div_cancel _ ( Nat.pos_of_ne_zero $ ( n0 ) ) ) tt ff
                    let lem2 ← to_expr ` `( Nat.div_self ( Nat.pos_of_ne_zero $ ( n0 ) ) ) tt ff
                    focus1
                        (
                          refine ` `( ( $ ( n0 ) rfl ) . elim )
                            <|>
                            rewrite_target lem1 <|> rewrite_target lem2
                          )
                      <|>
                      skip
              | e => throwError "'{ ← e }' is not supported"
#align tactic.compute_degree.resolve_sum_step tactic.compute_degree.resolve_sum_step

/- ./././Mathport/Syntax/Translate/Expr.lean:330:4: warning: unsupported (TODO): `[tacs] -/
/-- `norm_assum` simply tries `norm_num` and `assumption`.
It is used to try to discharge as many as possible of the side-goals of `compute_degree_le`.
Several side-goals are of the form `m ≤ n`, for natural numbers `m, n` or of the form `c ≠ 0`,
with `c` a coefficient of the polynomial `f` in question. -/
unsafe def norm_assum : tactic Unit :=
  try sorry >> try assumption
#align tactic.compute_degree.norm_assum tactic.compute_degree.norm_assum

-- failed to format: unknown constant 'term.pseudo.antiquot'
/--
      `eval_guessing n e` takes a natural number `n` and an expression `e` and gives an
      estimate for the evaluation of `eval_expr' ℕ e`.  It is tailor made for estimating degrees of
      polynomials.

      It decomposes `e` recursively as a sequence of additions, multiplications and `max`.
      On the atoms of the process, `eval_guessing` tries to use `eval_expr' ℕ`, resorting to using
      `n` if `eval_expr' ℕ` fails.

      For use with degree of polynomials, we mostly use `n = 0`. -/
    unsafe
  def
    eval_guessing
    ( n : ℕ ) : expr → tactic ℕ
    | q( $ ( a ) + $ ( b ) ) => ( · + · ) <$> eval_guessing a <*> eval_guessing b
      | q( $ ( a ) * $ ( b ) ) => ( · * · ) <$> eval_guessing a <*> eval_guessing b
      | q( max $ ( a ) $ ( b ) ) => max <$> eval_guessing a <*> eval_guessing b
      | e => eval_expr' ℕ e <|> pure n
#align tactic.compute_degree.eval_guessing tactic.compute_degree.eval_guessing

/-- A general description of `compute_degree_le_aux` is in the doc-string of `compute_degree`.
The difference between the two is that `compute_degree_le_aux` makes no effort to close side-goals,
nor fails if the goal does not change. -/
unsafe def compute_degree_le_aux : tactic Unit := do
  try <| refine ``(degree_le_natDegree.trans (WithBot.coe_le_coe.mpr _))
  let q(natDegree $(tl) ≤ $(tr)) ← target |
    fail "Goal is not of the form\n`f.nat_degree ≤ d` or `f.degree ≤ d`"
  let expected_deg ← guess_degree tl >>= eval_guessing 0
  let deg_bound ← eval_expr' ℕ tr <|> pure expected_deg
  if deg_bound < expected_deg then
      fail
        s! "the given polynomial has a term of expected degree
          at least '{expected_deg}'"
    else repeat <| resolve_sum_step
#align tactic.compute_degree.compute_degree_le_aux tactic.compute_degree.compute_degree_le_aux

end ComputeDegree

namespace Interactive

open ComputeDegree Polynomial

/-- `compute_degree_le` tries to solve a goal of the form `f.nat_degree ≤ d` or `f.degree ≤ d`,
where `f : R[X]` and `d : ℕ` or `d : with_bot ℕ`.

If the given degree `d` is smaller than the one that the tactic computes,
then the tactic suggests the degree that it computed.

Examples:

```lean
open polynomial
open_locale polynomial

variables {R : Type*} [semiring R] {a b c d e : R}

example {F} [ring F] {a : F} {n : ℕ} (h : n ≤ 10) :
  nat_degree (X ^ n + C a * X ^ 10 : F[X]) ≤ 10 :=
by compute_degree_le

example : nat_degree (7 * X : R[X]) ≤ 1 :=
by compute_degree_le

example {p : R[X]} {n : ℕ} {p0 : p.nat_degree = 0} :
 (p ^ n).nat_degree ≤ 0 :=
by compute_degree_le
```
-/
unsafe def compute_degree_le : tactic Unit :=
  focus1 do
    check_target_changes compute_degree_le_aux
    try <| any_goals' norm_assum
#align tactic.interactive.compute_degree_le tactic.interactive.compute_degree_le

add_tactic_doc
  { Name := "compute_degree_le"
    category := DocCategory.tactic
    declNames := [`tactic.interactive.compute_degree_le]
    tags := ["arithmetic", "finishing"] }

end Interactive

end Tactic


import game.limits.L01defs
import game.limits.seq_cauchyBdd

namespace xena -- hide

notation `|` x `|` := abs x -- hide

/-
Relationship convergent/Cauchy sequences.

Work in progress.
-/

/- Lemma
A convergent sequence of real numbers is a Cauchy sequence.
Prove "if and only if": WIP.
-/
lemma conv_iff_cauchy (a : ℕ → ℝ) :
    is_convergent a →  is_Cauchy a :=
begin
  --split,
  -- left-right implication: just doing convergent -> Cauchy here
  -- for the other direction should prove boundedness of Cauchy first
  intros h e he,
  set e2 := e / 2 with hde2,
  have he2 : 0 < e2, from half_pos he,
  cases h with α hα,
  have H := hα e2 he2,
  cases H with N hN,
  use N,
  intros m n hmn,
  have hm := hN m hmn.1,
  have hn := hN n hmn.2,
  have h1 : a m - a n = (a m - α) + (α - a n), ring,
  have h2 : | (a m - α) + (α - a n) | ≤ | a m - α | + | α - a n |,
    exact abs_add (a m - α) (α - a n),
  rw h1,
  have g1 : |a n - α| = |α - a n|,
    have g11 : a n - α = - ( α - a n), norm_num,
    rw g11, exact abs_neg _,
  rw g1 at hn,
  linarith,
  -- right-left implication
  --intro H,
  --sorry,

end

end xena -- hide

-- begin hide
--example (a b c d : ℝ) (h1 : a ≤ b + c) : a - b ≤ c  := by library_search
--example ( a : ℝ ) : |a| = | - a | := by library_search
-- end hide


/-
Copyright (c) 2021 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebraic_geometry.prime_spectrum
import Mathlib.ring_theory.polynomial.basic
import Mathlib.PostPort

universes u_1

namespace Mathlib

/-!
The morphism `Spec R[x] --> Spec R` induced by the natural inclusion `R --> R[x]` is an open map.
-/

namespace algebraic_geometry

namespace polynomial

/-- Given a polynomial `f ∈ R[x]`, `image_of_Df` is the subset of `Spec R` where at least one
of the coefficients of `f` does not vanish.  Lemma `image_of_Df_eq_comap_C_compl_zero_locus`
proves that `image_of_Df` is the image of `(zero_locus {f})ᶜ` under the morphism
`comap C : Spec R[x] → Spec R`. -/
def image_of_Df {R : Type u_1} [comm_ring R] (f : polynomial R) : set (prime_spectrum R) :=
  set_of fun (p : prime_spectrum R) => ∃ (i : ℕ), ¬polynomial.coeff f i ∈ prime_spectrum.as_ideal p

theorem is_open_image_of_Df {R : Type u_1} [comm_ring R] {f : polynomial R} : is_open (image_of_Df f) := sorry

/-- If a point of `Spec R[x]` is not contained in the vanishing set of `f`, then its image in
`Spec R` is contained in the open set where at least one of the coefficients of `f` is non-zero.
This lemma is a reformulation of `exists_coeff_not_mem_C_inverse`. -/
theorem comap_C_mem_image_of_Df {R : Type u_1} [comm_ring R] {f : polynomial R} {I : prime_spectrum (polynomial R)} (H : I ∈ (prime_spectrum.zero_locus (singleton f)ᶜ)) : prime_spectrum.comap polynomial.C I ∈ image_of_Df f :=
  polynomial.exists_coeff_not_mem_C_inverse (iff.mp prime_spectrum.mem_compl_zero_locus_iff_not_mem H)

/-- The open set `image_of_Df f` coincides with the image of `basic_open f` under the
morphism `C⁺ : Spec R[x] → Spec R`. -/
theorem image_of_Df_eq_comap_C_compl_zero_locus {R : Type u_1} [comm_ring R] {f : polynomial R} : image_of_Df f = prime_spectrum.comap polynomial.C '' (prime_spectrum.zero_locus (singleton f)ᶜ) := sorry

/--  The morphism `C⁺ : Spec R[x] → Spec R` is open. -/
theorem is_open_map_comap_C {R : Type u_1} [comm_ring R] : is_open_map (prime_spectrum.comap polynomial.C) := sorry


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

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 2 : `true` and `false`

We learn about the `true` and `false` propositions.

## Tactics you will need

To solve the levels on this sheet you will need to know all previous
tactics, plus the following two new ones. Check out their explanations
in the course book. Or just try them out and hover over them to see
if you can understand what's going on.

* `triv`
* `exfalso`

-/

-- Throughout this sheet, `P`, `Q` and `R` will denote propositions.

variables (P Q R : Prop)

example : true :=
begin
  triv,
end

example : true → true :=
begin
  intro h, triv,
end

example : false → true :=
begin
  intro q,exfalso,triv,
end

example : false → false :=
begin
  triv,
end

example : (true → false) → false :=
begin
  intro q,exfalso,apply q,triv,
end

example : false → P :=
begin
  intro q,exfalso,triv,
end

example : true → false → true → false → true → false :=
begin
  intros q w e r t,triv,
end

example : P → ((P → false) → false) :=
begin
  intros p pf,apply pf,assumption,
end

example : (P → false) → P → Q :=
begin
  intros pf p,exfalso,apply pf,assumption,
end

example : (true → false) → P :=
begin
  intro q, exfalso,apply q,triv,
end


/-
This file defines natural sequences, here defined as functions ℕ → ℕ
Also defined here are the comparisons =, ≠, <, ≤ and #
-/

import data.nat.basic

def nat_seq := ℕ → ℕ

notation `𝒩` := nat_seq

namespace nat_seq

def zero : 𝒩 := λ n, 0

def eq (a b : 𝒩) : Prop := ∀ n : ℕ, a n = b n

infix `='`:50 := eq

lemma eq_iff {a b : 𝒩} : a = b ↔ a =' b := function.funext_iff

def ne (a b : 𝒩) : Prop := ¬ a =' b

infix `≠'`:50 := ne

def lt (a b : 𝒩) : Prop := ∃ n : ℕ, (∀ i : ℕ, i < n → a i = b i) ∧ a n < b n

infix `<` := lt

def le (a b : 𝒩) : Prop := ∀ n : ℕ, (∀ i : ℕ, i < n → a i = b i) → a n ≤ b n

infix `≤` := le

theorem le_of_eq (a b : 𝒩) (h : a =' b) : a ≤ b :=
begin
    intro n,
    intro hn,
    rw h,
end

lemma imp_eq_iff_imp_eq (a b : 𝒩) (n: ℕ) :
    (∀ i : ℕ, i < n → a i = b i) ↔ (∀ i : ℕ, i < n → b i = a i) :=
begin
    split,
    repeat {intro h, intro i, intro hi, symmetry, exact h i hi},
end

lemma imp_eq_trans (a b c : 𝒩) (n : ℕ)
    (h₁ : ∀ i : ℕ, i < n → a i = b i)
    (h₂ : ∀ i : ℕ, i < n → b i = c i) :
    ∀ i : ℕ, i < n → a i = c i :=
begin
    intro i,
    intro hi,
    have aibi := h₁ i hi,
    rw aibi,
    exact h₂ i hi,
end

@[trans] theorem eq_trans (a b c : 𝒩) : a =' b → b =' c → a =' c :=
begin
    intros ab bc n,
    rw ab n,
    exact bc n,
end

@[symm] theorem eq_symm {a b: 𝒩} : a =' b ↔ b =' a :=
begin
    split,
    repeat {intros h n, symmetry, exact h n},
end

@[refl] theorem eq_refl {a : 𝒩} : a =' a :=
begin
    intro n,
    refl,
end

@[symm] theorem ne_symm (a b : 𝒩) : a ≠' b ↔ b ≠' a :=
begin
    repeat {rw ne},
    rw eq_symm,
end

lemma lt_eq_lt_le (a b : 𝒩) (n m : ℕ)
        (h1 : ∀ i : ℕ, i < n → a i = b i) (h2 : a m < b m) :
        n ≤ m :=
begin
    cases le_or_gt n m with nlem ngtm,
    {-- case: n ≤ m
        exact nlem,
    },
    {-- case: m < n
        exfalso,
        rw gt_iff_lt at ngtm,
        have aibi := h1 m ngtm,
        rw eq_iff_le_not_lt at aibi,
        exact (and.elim_right aibi) h2,
    }
end

lemma lt_eq_ne_le (a b : 𝒩) (n m : ℕ)
        (h1 : ∀ i : ℕ, i < n → a i = b i) (h2 : a m ≠ b m) :
        n ≤ m :=
begin
    rw ne_iff_lt_or_gt at h2,
    cases h2 with hlt hgt,
    {-- case: a m < b m
        exact lt_eq_lt_le a b n m h1 hlt,
    },
    {
        rw gt_iff_lt at hgt,
        rw imp_eq_iff_imp_eq at h1,
        exact lt_eq_lt_le b a n m h1 hgt,
    }
end

--The following lemma immediately follows from lt_eq_ne_le and n = m ↔ (n ≤ m ∨ m ≤ n)
--We will use this in reckless.lean to prove weak_LEM_implies_LLPO
lemma first_zero_eq (a : 𝒩) (n m : ℕ) (hn1 : ∀ i : ℕ, i < n → a i = 0) (hn2 : a n ≠ 0)
        (hm1 : ∀ i : ℕ, i < m → a i = 0) (hm2 : a m ≠ 0) :
        n = m :=
begin
    rw eq_iff_le_not_lt,
    split,
    {-- need to prove: n ≤ m
        apply lt_eq_ne_le a zero n m hn1 hm2,
    },
    {-- need to prove: ¬n < m
        apply not_lt_of_le,
        apply lt_eq_ne_le a zero m n hm1 hn2,
    }
end

theorem le_of_lt (a b : 𝒩) (less: a < b) : a ≤ b :=
begin
    rw le,
    rw lt at less,
    intro n,
    intro h,
    cases less with d hd,
    cases hd with p q,
    have hnd := lt_eq_lt_le a b n d h q,
    rw le_iff_eq_or_lt at hnd,
    cases hnd with ndeq ndlt,
    { --case n = d
        rw ← ndeq at q,
        exact nat.le_of_lt q,
    },
    { --case n < d
        apply nat.le_of_eq,
        apply p,
        exact ndlt,
    }
end

@[trans] theorem lt_trans (a b c : 𝒩) : a < b → b < c → a < c :=
begin
    intros hab hbc,
    cases hab with n hn,
    cases hbc with m hm,
    cases hn with p₁ p₂,
    cases hm with q₁ q₂,
    use min n m,
    split,
    {--need to prove: ∀ (i : ℕ), i < min n m → a i = c i
        intros i hi,
        rw lt_min_iff at hi,
        rw p₁ i hi.elim_left,
        exact q₁ i hi.elim_right,
    },
    {--need to prove: a (min n m) < c (min n m)
        cases nat.lt_trichotomy n m with nltm h,
        {-- n < m
            rw min_eq_left (nat.le_of_lt nltm),
            rw ← q₁ n nltm,
            exact p₂,
        },
        {
            cases h with neqm mltn,
            {-- n = m
                rw neqm at *,
                rw min_self,
                exact nat.lt_trans p₂ q₂,
            },
            {-- m < n
                rw min_eq_right (nat.le_of_lt mltn),
                rw p₁ m mltn,
                exact q₂,
            }
        }
    }
end

-- Doing a finite amount of comparisons is allowed
lemma all_eq_or_exists_neq (a b : 𝒩) (n : ℕ):
    (∀ i : ℕ, i < n → a i = b i) ∨ (∃ i : ℕ, i < n ∧ (∀ j : ℕ, j < i → a j = b j) ∧ a i ≠ b i) :=
begin
    induction n with d hd,
    {-- case: n = 0
        left,
        intro i,
        intro hi,
        exfalso,
        rw ← not_le at hi,
        exact hi (zero_le i),
    },
    {-- case: succ(n)
        cases hd with all_eq exists_neq,
        {-- hypothesis: ∀ i < d, a i = b i
            have tri := lt_trichotomy (a d) (b d),
            rw or_comm at tri,
            rw or_assoc at tri,
            cases tri with aeqb anb,
            {-- case: a d = b d
                left,
                intro i,
                intro hi,
                rw nat.lt_succ_iff_lt_or_eq at hi,
                cases hi with iltd ieqd,
                exact all_eq i iltd,
                rw ieqd,
                exact aeqb,
            },
            {-- case: a d ≠ b d
                right,
                use d,
                split,
                exact nat.lt_succ_self d,
                split,
                exact all_eq,
                rwa [ne_iff_lt_or_gt, or.comm, gt_iff_lt],
            }
        },
        {-- hypothesis: ∃ i < d, (∀ j < i, a j = b j) ∧ a i ≠ b i
            right,
            cases exists_neq with i hi,
            use i,
            split,
            exact nat.lt_succ_of_lt (and.elim_left hi),
            exact and.elim_right hi,
        }
    }
end

lemma nat_lt_cotrans (a b : ℕ) (h : a < b) : ∀ c : ℕ, a < c ∨ c < b :=
begin
    intro c,
    induction c with d hd,
    {-- case: c = d = 0
        right, -- need to prove: 0 < b
        have ha := nat.eq_zero_or_pos a,
        cases ha with hal har,
        rw hal at h, exact h,
        rw gt_iff_lt at har,
        exact nat.lt_trans har h,
    },
    {-- case: c = succ(d)
        cases hd with ad db,
        {-- case: a < d
            left,
            exact nat.lt_trans ad (nat.lt_succ_self d),
        },
        {-- case d < b
            cases db with i hi,
            {-- b = nat.succ d
                left,
                exact h,
            },
            {-- b > nat.succ d
                right,
                rw nat.lt_succ_iff_lt_or_eq,
                rw or.comm,
                rw ← le_iff_eq_or_lt,
                exact hi,
            }
        }
    }
end

theorem lt_cotrans (a b : 𝒩) (h : a < b) : ∀ c : 𝒩, a < c ∨ c < b :=
begin
    intro c,
    rw lt at h,
    cases h with n hn,
    cases hn with hnl hnr,
    cases all_eq_or_exists_neq a c n with all_eq exists_neq,
    {-- hypothesis all_eq: ∀ i < n, a i = c i
        have hlt := nat_lt_cotrans (a n) (b n) hnr,
        have ltcn := hlt (c n),
        cases ltcn with ancn cnbn,
        {-- hypothesis ancn: a n < c n
            left, -- need to prove: a < c
            rw lt,
            use n,
            exact and.intro all_eq ancn,
        },
        {-- hypothesis cnbn: c n < b n
            right, -- need to prove: c < b
            rw lt,
            use n,
            split,
            {-- need to prove: ∀ i < n, c i = b i
                rw imp_eq_iff_imp_eq a c n at all_eq,
                exact imp_eq_trans c a b n all_eq hnl,
            },
            {-- need to prove: c n < b n
                exact cnbn,
            }
        }
    },
    {-- hypothesis exists_neq: ∃ i < n, a i ≠ c i
        cases exists_neq with i hi,
        cases hi with hil hi2,
        cases hi2 with him hir,
        rw ne_iff_lt_or_gt at hir,
        cases hir with ailtci aigtci,
        {-- hypothesis ailtci: a i < c i
            left, -- need to prove: a < c
            rw lt,
            use i,
            split,
            exact him,
            exact ailtci,
        },
        {-- hypothesis aigtci: i > c i
            rw gt_iff_lt at aigtci,
            right, -- need to prove: c < b
            rw lt,
            use i,
            split,
            {-- need to prove: ∀ j < i, c j = b j
                intro j,
                intro hj,
                rw ← him j hj,
                have jltn := nat.lt_trans hj hil,
                exact hnl j jltn,
            },
            {-- need to prove: c i < b i
                rw hnl i hil at aigtci,
                exact aigtci,
            }
        }
    }
end

theorem le_iff_not_lt (a b : 𝒩) : a ≤ b ↔ ¬ b < a :=
begin
    split,
    {-- need to prove: a ≤ b → ¬ b < a
        intro h,
        intro ex,
        cases ex with n hn,
        have g := h n,
        cases hn with ind blta,
        rw imp_eq_iff_imp_eq b a n at ind,
        have aleb := g ind,
        rw nat.lt_iff_le_not_le at blta,
        exact and.elim_right blta aleb,
    },
    {-- need to prove: ¬ b < a → a ≤ b
        intros h n hi,
        cases le_or_gt (a n) (b n) with hle hgt,
        exact hle, -- case: a ≤ b
        exfalso, -- case: b < a
        rw gt_iff_lt at hgt,
        apply h,
        use n,
        split,
        {-- need to prove: ∀ i < n, b i = a i
            rw imp_eq_iff_imp_eq b a n,
            exact hi,
        }, -- need to prove: b n < a n
        exact hgt,
    }
end

-- The following theorem now easily follows from le_iff_not_lt and lt_cotrans
theorem le_trans (a b c : 𝒩) : a ≤ b → b ≤ c → a ≤ c :=
begin
    intro h₁,
    intro h₂,
    rw le_iff_not_lt at *,
    intro h₃,
    have ltorlt := lt_cotrans c a h₃ b,
    cases ltorlt with cb ba,
    exact h₂ cb,
    exact h₁ ba,
end

theorem le_stable (a b : 𝒩) : ¬¬a ≤ b → a ≤ b :=
begin
    rw le_iff_not_lt,
    exact not_of_not_not_not,
end

theorem eq_of_le_le {a b : 𝒩} (hab : a ≤ b) (hba : b ≤ a) : a =' b :=
begin
    intro n,
    apply nat.strong_induction_on n,
    intros d hd,
    rw le at *,
    have hle := hab d hd,
    have hge : b d ≤ a d, by
    {
        apply hba,
        intros i hi,
        symmetry,
        exact hd i hi,
    },
    exact le_antisymm hle hge,
end

def apart (a b : 𝒩) : Prop := ∃ n, a n ≠ b n

infix `#` := apart

-- If two natural sequences are apart from eachother, they are not equal
theorem ne_of_apart (a b : 𝒩) : a # b → a ≠' b :=
begin
    intros r h,
    cases r with n hn,
    apply hn,
    apply h,
end

theorem eq_iff_not_apart (a b : 𝒩) : a =' b ↔ ¬ a # b :=
begin

    split,
    {-- a = b → ¬ a # b
        intro h,
        intro g,
        cases g with n hn,
        exact hn (h n),
    },
    {-- ¬ a # b → a = b
        intro h,
        intro n,
        rwa [apart, not_exists] at h,
        have g := h n,
        rwa [ne_iff_lt_or_gt, not_or_distrib] at g,
        cases lt_trichotomy (a n) (b n) with l r,
        {-- case: a n < b n, can't happen because ¬ a n < b n
            exfalso,
            exact (and.elim_left g) l,
        },
        cases r with rl rr,
        {-- case: a n = b n, trivial
            exact rl,
        },
        {-- case: b n < a n, can't happen because ¬ a n > b n
            exfalso,
            exact (and.elim_right g) rr,
        }
    }
end

theorem eq_stable (a b : 𝒩) : ¬¬ a =' b → a =' b :=
begin
    rw eq_iff_not_apart,
    exact not_of_not_not_not,
end

theorem apart_iff_lt_or_lt (a b : 𝒩) : a # b ↔ a < b ∨ b < a :=
begin
    split,
    {-- need to prove: a # b → a < b ∨ b < a
        intro ab,
        cases ab with n hn,
        have h := all_eq_or_exists_neq a b n,
        cases h with all_eq exists_neq,
        {-- case: ∀ i < n → a i = b i
            rw ne_iff_lt_or_gt at hn,
            cases hn with ab ba,
            {-- case: a n < b n
                left,
                use n,
                exact and.intro all_eq ab,
            },
            {-- case: a n > b n
                right,
                use n,
                rw gt_iff_lt at ba,
                split,
                rw imp_eq_iff_imp_eq b a n,
                exact all_eq,
                exact ba,
            }
        },
        {-- case: ∃ i < n, (∀ j < i, a j = b j) ∧ a i ≠ b i
            cases exists_neq with i hi,
            cases hi with iltn r,
            cases r with ajbj aineqbi,
            rw ne_iff_lt_or_gt at aineqbi,
            cases aineqbi with aibi biai,
            {-- case: a i < b i
                left,
                use i,
                exact and.intro ajbj aibi,
            },
            {-- case: b i < a i
                right,
                use i,
                split,
                rw imp_eq_iff_imp_eq b a i,
                exact ajbj,
                rw gt_iff_lt at biai,
                exact biai,
            }
        }
    },
    {-- need to prove: a < b ∨ b < a → a # b
        intro aborba,
        cases aborba with ab ba,
        {-- case: a < b
            cases ab with n hn,
            use n,
            rw ne_iff_lt_or_gt,
            left,
            exact and.elim_right hn,
        },
        {-- case: b < a
            cases ba with n hn,
            use n,
            rw ne_iff_lt_or_gt,
            right,
            exact and.elim_right hn,
        }
    }
end

-- The following theorem now easily follows from combining lt_cotrans and apart_iff_lt_or_lt
theorem apart_cotrans (a b : 𝒩) (h : a # b) : ∀ c : 𝒩, a # c ∨ c # b :=
begin
    intro c,
    repeat {rw apart_iff_lt_or_lt at *},
    cases h with ab ba,
    {-- case: a < b
        cases lt_cotrans a b ab c with ac cb,
        {-- case: a < c
            left,
            left,
            exact ac,
        },
        {-- case: c < b
            right,
            left,
            exact cb,
        }
    },
    {-- case: b < a
        cases lt_cotrans b a ba c with bc ca,
        {-- case: b < c
            right,
            right,
            exact bc,
        },
        {-- case: c < a
            left,
            right,
            exact ca,
        }
    }
end

@[symm] theorem apart_symm (a b : 𝒩) : a # b ↔ b # a :=
begin
    split,
    repeat {
        intro h,
        cases h with n hn,
        use n,
        symmetry,
        exact hn,
    },
end

-- 0 is the smallest sequence
lemma zero_le (a : 𝒩) : zero ≤ a :=
begin
    intros n h,
    rw zero,
    simp,
end

lemma apart_zero_lt (a : 𝒩) (h : a # zero) : zero < a :=
begin
    rw apart_iff_lt_or_lt at h,
    cases h with alt agt,
    {-- case: a < 0, impossible
        exfalso,
        have h₁ := zero_le a,
        rw le_iff_not_lt at h₁,
        exact h₁ alt,
    },
    {-- case: 0 < a, trivial
        exact agt,
    }
end

/-
There are uncountably (defined positively) many natural sequences.
The proof of this theorem is Cantor's Diagonal argument
-/
theorem uncountable (f : ℕ → 𝒩) : ∃ a : 𝒩, ∀ n : ℕ, a # (f n) :=
begin
    use λ n : ℕ, (f n n) + 1,
    intro n,
    use n,
    exact nat.succ_ne_self (f n n),
end

end nat_seq


import tactic
variables P Q : Prop

open_locale classical

-- BEGIN
example (P Q : Prop) : (P → Q) ↔ ¬ P ∨ Q :=
begin
  split,
    intro hpq,
      by_cases h : P,
        right,
          exact hpq h,
        left,
          exact h,
    intros hnpq hp,
      cases hnpq with hnp hq,
        contradiction,
        exact hq,
end

/- using the cases tactic -/
example (P Q : Prop) : (P → Q) ↔ ¬ P ∨ Q :=
begin
  split,
    intro hpq,
      by_contra h',
      push_neg at h',
      cases h' with h'p h'nq,
      apply h'nq (hpq (h'p)),
    intros hnpq hp,
      cases hnpq with hnp hq,
      contradiction,
      exact hq,
end
-- END


/-
Copyright (c) 2019 Kevin Kappelmann. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Kappelmann, Kyle Miller, Mario Carneiro

! This file was ported from Lean 3 source module data.nat.fib
! leanprover-community/mathlib commit 92ca63f0fb391a9ca5f22d2409a6080e786d99f7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Init.Data.Nat.Lemmas
import Mathlib.Init.Data.Nat.Bitwise
import Mathlib.Data.Nat.GCD.Basic
import Mathlib.Logic.Function.Iterate
import Mathlib.Data.Finset.NatAntidiagonal
import Mathlib.Algebra.BigOperators.Basic
import Mathlib.Tactic.Ring
import Mathlib.Tactic.WLOG
import Mathlib.Tactic.Zify

/-!
# Fibonacci Numbers

This file defines the fibonacci series, proves results about it and introduces
methods to compute it quickly.
-/

/-!
# The Fibonacci Sequence

## Summary

Definition of the Fibonacci sequence `F₀ = 0, F₁ = 1, Fₙ₊₂ = Fₙ + Fₙ₊₁`.

## Main Definitions

- `Nat.fib` returns the stream of Fibonacci numbers.

## Main Statements

- `Nat.fib_add_two`: shows that `fib` indeed satisfies the Fibonacci recurrence `Fₙ₊₂ = Fₙ + Fₙ₊₁.`.
- `Nat.fib_gcd`: `fib n` is a strong divisibility sequence.
- `Nat.fib_succ_eq_sum_choose`: `fib` is given by the sum of `Nat.choose` along an antidiagonal.
- `Nat.fib_succ_eq_succ_sum`: shows that `F₀ + F₁ + ⋯ + Fₙ = Fₙ₊₂ - 1`.
- `Nat.fib_two_mul` and `nat.fib_two_mul_add_one` are the basis for an efficient algorithm to
  compute `fib` (see `Nat.fastFib`). There are `bit0`/`bit1` variants of these can be used to
  simplify `fib` expressions: `simp only [nat.fib_bit0, nat.fib_bit1, nat.fib_bit0_succ,
  nat.fib_bit1_succ, nat.fib_one, nat.fib_two]`.

## Implementation Notes

For efficiency purposes, the sequence is defined using `Stream.iterate`.

## Tags

fib, fibonacci
-/

open BigOperators

namespace Nat

/-- Implementation of the fibonacci sequence satisfying
`fib 0 = 0, fib 1 = 1, fib (n + 2) = fib n + fib (n + 1)`.

*Note:* We use a stream iterator for better performance when compared to the naive recursive
implementation.
-/

-- Porting note: Lean cannot find pp_nodot at the time of this port.
-- @[pp_nodot]
def fib (n : ℕ) : ℕ :=
  (((fun p : ℕ × ℕ => (p.snd, p.fst + p.snd))^[n]) (0, 1)).fst
#align nat.fib Nat.fib

@[simp]
theorem fib_zero : fib 0 = 0 :=
  rfl
#align nat.fib_zero Nat.fib_zero

@[simp]
theorem fib_one : fib 1 = 1 :=
  rfl
#align nat.fib_one Nat.fib_one

@[simp]

/-- Shows that `fib` indeed satisfies the Fibonacci recurrence `Fₙ₊₂ = Fₙ + Fₙ₊₁.` -/
theorem fib_add_two {n : ℕ} : fib (n + 2) = fib n + fib (n + 1) := by
  simp [fib, Function.iterate_succ_apply']
#align nat.fib_add_two Nat.fib_add_two

theorem fib_le_fib_succ {n : ℕ} : fib n ≤ fib (n + 1) := by cases n <;> simp [fib_add_two]
#align nat.fib_le_fib_succ Nat.fib_le_fib_succ

@[mono]
theorem fib_mono : Monotone fib :=
  monotone_nat_of_le_succ fun _ => fib_le_fib_succ
#align nat.fib_mono Nat.fib_mono

theorem fib_pos {n : ℕ} (n_pos : 0 < n) : 0 < fib n :=
  calc
    0 < fib 1 := by decide
    _ ≤ fib n := fib_mono n_pos

#align nat.fib_pos Nat.fib_pos

theorem fib_add_two_sub_fib_add_one {n : ℕ} : fib (n + 2) - fib (n + 1) = fib n := by
  rw [fib_add_two, add_tsub_cancel_right]
#align nat.fib_add_two_sub_fib_add_one Nat.fib_add_two_sub_fib_add_one

theorem fib_lt_fib_succ {n : ℕ} (hn : 2 ≤ n) : fib n < fib (n + 1) :=
  by
  rcases exists_add_of_le hn with ⟨n, rfl⟩
  rw [← tsub_pos_iff_lt, add_comm 2, fib_add_two_sub_fib_add_one]
  apply fib_pos (succ_pos n)
#align nat.fib_lt_fib_succ Nat.fib_lt_fib_succ

/-- `fib (n + 2)` is strictly monotone. -/
theorem fib_add_two_strictMono : StrictMono fun n => fib (n + 2) :=
  by
  refine' strictMono_nat_of_lt_succ fun n => _
  rw [add_right_comm]
  exact fib_lt_fib_succ (self_le_add_left _ _)
#align nat.fib_add_two_strict_mono Nat.fib_add_two_strictMono

theorem le_fib_self {n : ℕ} (five_le_n : 5 ≤ n) : n ≤ fib n :=
  by
  induction' five_le_n with n five_le_n IH
  ·-- 5 ≤ fib 5
    rfl
  · -- n + 1 ≤ fib (n + 1) for 5 ≤ n
    rw [succ_le_iff]
    calc
      n ≤ fib n := IH
      _ < fib (n + 1) := fib_lt_fib_succ (le_trans (by decide) five_le_n)

#align nat.le_fib_self Nat.le_fib_self

/-- Subsequent Fibonacci numbers are coprime,
  see https://proofwiki.org/wiki/Consecutive_Fibonacci_Numbers_are_Coprime -/
theorem fib_coprime_fib_succ (n : ℕ) : Nat.coprime (fib n) (fib (n + 1)) :=
  by
  induction' n with n ih
  · simp
  · rw [fib_add_two]
    simp only [coprime_add_self_right]
    simp [coprime, ih.symm]
#align nat.fib_coprime_fib_succ Nat.fib_coprime_fib_succ

/-- See https://proofwiki.org/wiki/Fibonacci_Number_in_terms_of_Smaller_Fibonacci_Numbers -/
theorem fib_add (m n : ℕ) : fib (m + n + 1) = fib m * fib n + fib (m + 1) * fib (n + 1) :=
  by
  induction' n with n ih generalizing m
  · simp
  · intros
    specialize ih (m + 1)
    rw [add_assoc m 1 n, add_comm 1 n] at ih
    simp only [fib_add_two, ih]
    ring
#align nat.fib_add Nat.fib_add

theorem fib_two_mul (n : ℕ) : fib (2 * n) = fib n * (2 * fib (n + 1) - fib n) :=
  by
  cases n
  · simp
  · rw [Nat.succ_eq_add_one, two_mul, ← add_assoc, fib_add, fib_add_two, two_mul]
    simp only [← add_assoc, add_tsub_cancel_right]
    ring
#align nat.fib_two_mul Nat.fib_two_mul

theorem fib_two_mul_add_one (n : ℕ) : fib (2 * n + 1) = fib (n + 1) ^ 2 + fib n ^ 2 :=
  by
  rw [two_mul, fib_add]
  ring
#align nat.fib_two_mul_add_one Nat.fib_two_mul_add_one

section deprecated

set_option linter.deprecated false

theorem fib_bit0 (n : ℕ) : fib (bit0 n) = fib n * (2 * fib (n + 1) - fib n) := by
  rw [bit0_eq_two_mul, fib_two_mul]
#align nat.fib_bit0 Nat.fib_bit0

theorem fib_bit1 (n : ℕ) : fib (bit1 n) = fib (n + 1) ^ 2 + fib n ^ 2 := by
  rw [Nat.bit1_eq_succ_bit0, bit0_eq_two_mul, fib_two_mul_add_one]
#align nat.fib_bit1 Nat.fib_bit1

theorem fib_bit0_succ (n : ℕ) : fib (bit0 n + 1) = fib (n + 1) ^ 2 + fib n ^ 2 :=
  fib_bit1 n
#align nat.fib_bit0_succ Nat.fib_bit0_succ

-- porting note: A bunch of issues similar to [this zulip thread](https://github.com/leanprover-community/mathlib4/pull/1576) with `zify`
theorem fib_bit1_succ (n : ℕ) : fib (bit1 n + 1) = fib (n + 1) * (2 * fib n + fib (n + 1)) := by
  rw [Nat.bit1_eq_succ_bit0, fib_add_two, fib_bit0, fib_bit0_succ]
  have : fib n ≤ 2 * fib (n + 1) :=
    le_trans (fib_le_fib_succ) (mul_comm 2 _ ▸ le_mul_of_pos_right two_pos)
  zify [this]
  ring_nf
#align nat.fib_bit1_succ Nat.fib_bit1_succ

end deprecated

/-- Computes `(nat.fib n, nat.fib (n + 1))` using the binary representation of `n`.
Supports `nat.fast_fib`. -/
def fastFibAux : ℕ → ℕ × ℕ :=
  Nat.binaryRec (fib 0, fib 1) fun b _ p =>
    if b then (p.2 ^ 2 + p.1 ^ 2, p.2 * (2 * p.1 + p.2))
    else (p.1 * (2 * p.2 - p.1), p.2 ^ 2 + p.1 ^ 2)
#align nat.fast_fib_aux Nat.fastFibAux

/-- Computes `nat.fib n` using the binary representation of `n`.
Proved to be equal to `nat.fib` in `nat.fast_fib_eq`. -/
def fastFib (n : ℕ) : ℕ :=
  (fastFibAux n).1
#align nat.fast_fib Nat.fastFib

theorem fast_fib_aux_bit_ff (n : ℕ) :
    fastFibAux (bit false n) =
      let p := fastFibAux n
      (p.1 * (2 * p.2 - p.1), p.2 ^ 2 + p.1 ^ 2) :=
  by
  rw [fastFibAux, binaryRec_eq]
  · rfl
  · simp
#align nat.fast_fib_aux_bit_ff Nat.fast_fib_aux_bit_ff

theorem fast_fib_aux_bit_tt (n : ℕ) :
    fastFibAux (bit true n) =
      let p := fastFibAux n
      (p.2 ^ 2 + p.1 ^ 2, p.2 * (2 * p.1 + p.2)) :=
  by
  rw [fastFibAux, binaryRec_eq]
  · rfl
  · simp
#align nat.fast_fib_aux_bit_tt Nat.fast_fib_aux_bit_tt

theorem fast_fib_aux_eq (n : ℕ) : fastFibAux n = (fib n, fib (n + 1)) :=
  by
  apply Nat.binaryRec _ (fun b n' ih => _) n
  · simp [fastFibAux]
  · intro b
    intro n'
    intro ih
    cases b <;>
          simp only [fast_fib_aux_bit_ff, fast_fib_aux_bit_tt, congr_arg Prod.fst ih,
            congr_arg Prod.snd ih, Prod.mk.inj_iff] <;>
          simp [bit, fib_bit0, fib_bit1, fib_bit0_succ, fib_bit1_succ]
#align nat.fast_fib_aux_eq Nat.fast_fib_aux_eq

theorem fast_fib_eq (n : ℕ) : fastFib n = fib n := by rw [fastFib, fast_fib_aux_eq]
#align nat.fast_fib_eq Nat.fast_fib_eq

theorem gcd_fib_add_self (m n : ℕ) : gcd (fib m) (fib (n + m)) = gcd (fib m) (fib n) := by
  cases' Nat.eq_zero_or_pos n with h h
  · rw [h]
    simp
  replace h := Nat.succ_pred_eq_of_pos h; rw [← h, succ_eq_add_one]
  calc
    gcd (fib m) (fib (n.pred + 1 + m)) =
        gcd (fib m) (fib n.pred * fib m + fib (n.pred + 1) * fib (m + 1)) :=
      by
        rw [← fib_add n.pred _]
        ring_nf
    _ = gcd (fib m) (fib (n.pred + 1) * fib (m + 1)) :=
      by
        rw [add_comm, gcd_add_mul_right_right (fib m) _ (fib n.pred)]
    _ = gcd (fib m) (fib (n.pred + 1)) :=
      coprime.gcd_mul_right_cancel_right (fib (n.pred + 1)) (coprime.symm (fib_coprime_fib_succ m))

#align nat.gcd_fib_add_self Nat.gcd_fib_add_self

theorem gcd_fib_add_mul_self (m n : ℕ) : ∀ k, gcd (fib m) (fib (n + k * m)) = gcd (fib m) (fib n)
  | 0 => by simp
  | k + 1 => by
    rw [←gcd_fib_add_mul_self m n k,
      add_mul,
      ← add_assoc,
      one_mul,
      gcd_fib_add_self _ _]
#align nat.gcd_fib_add_mul_self Nat.gcd_fib_add_mul_self

/-- `fib n` is a strong divisibility sequence,
  see https://proofwiki.org/wiki/GCD_of_Fibonacci_Numbers -/
theorem fib_gcd (m n : ℕ) : fib (gcd m n) = gcd (fib m) (fib n) := by
  induction m, n using Nat.gcd.induction with
  | H0 => simp
  | H1 m n _ h' =>
    rw [← gcd_rec m n] at h'
    conv_rhs => rw [← mod_add_div' n m]
    rwa [gcd_fib_add_mul_self m (n % m) (n / m), gcd_comm (fib m) _]
#align nat.fib_gcd Nat.fib_gcd

theorem fib_dvd (m n : ℕ) (h : m ∣ n) : fib m ∣ fib n := by
  rwa [gcd_eq_left_iff_dvd, ← fib_gcd, gcd_eq_left_iff_dvd.mp]
#align nat.fib_dvd Nat.fib_dvd

theorem fib_succ_eq_sum_choose :
    ∀ n : ℕ, fib (n + 1) = ∑ p in Finset.Nat.antidiagonal n, choose p.1 p.2 :=
  two_step_induction rfl rfl fun n h1 h2 =>
    by
    rw [fib_add_two, h1, h2, Finset.Nat.antidiagonal_succ_succ', Finset.Nat.antidiagonal_succ']
    simp [choose_succ_succ, Finset.sum_add_distrib, add_left_comm]
#align nat.fib_succ_eq_sum_choose Nat.fib_succ_eq_sum_choose

theorem fib_succ_eq_succ_sum (n : ℕ) : fib (n + 1) = (∑ k in Finset.range n, fib k) + 1 :=
  by
  induction' n with n ih
  · simp
  ·
    calc
      fib (n + 2) = fib n + fib (n + 1) := fib_add_two
      _ = (fib n + ∑ k in Finset.range n, fib k) + 1 := by rw [ih, add_assoc]
      _ = (∑ k in Finset.range (n + 1), fib k) + 1 := by simp [Finset.range_add_one]

#align nat.fib_succ_eq_succ_sum Nat.fib_succ_eq_succ_sum

end Nat

namespace NormNum

open Tactic Nat

/-! ### `norm_num` plugin for `fib`

The `norm_num` plugin uses a strategy parallel to that of `nat.fast_fib`, but it instead
produces proofs of what `nat.fib` evaluates to.
-/
/-
expected ')'
-/

section deprecated

set_option linter.deprecated false

/-- Auxiliary definition for `prove_fib` plugin. -/
def IsFibAux (n a b : ℕ) :=
  fib n = a ∧ fib (n + 1) = b
#align norm_num.is_fib_aux NormNum.IsFibAux

theorem is_fib_aux_one : IsFibAux 1 1 1 :=
  ⟨fib_one, fib_two⟩
#align norm_num.is_fib_aux_one NormNum.is_fib_aux_one

theorem is_fib_aux_bit0 {n a b c a2 b2 a' b' : ℕ} (H : IsFibAux n a b) (h1 : a + c = bit0 b)
    (h2 : a * c = a') (h3 : a * a = a2) (h4 : b * b = b2) (h5 : a2 + b2 = b') :
    IsFibAux (bit0 n) a' b' :=
  ⟨by
    rw [fib_bit0, H.1, H.2, ← bit0_eq_two_mul,
      show bit0 b - a = c by rw [← h1, Nat.add_sub_cancel_left], h2],
    by rw [fib_bit0_succ, H.1, H.2, pow_two, pow_two, h3, h4, add_comm, h5]⟩
#align norm_num.is_fib_aux_bit0 NormNum.is_fib_aux_bit0

theorem is_fib_aux_bit1 {n a b c a2 b2 a' b' : ℕ} (H : IsFibAux n a b) (h1 : a * a = a2)
    (h2 : b * b = b2) (h3 : a2 + b2 = a') (h4 : bit0 a + b = c) (h5 : b * c = b') :
    IsFibAux (bit1 n) a' b' :=
  ⟨by rw [fib_bit1, H.1, H.2, pow_two, pow_two, h1, h2, add_comm, h3], by
    rw [fib_bit1_succ, H.1, H.2, ← bit0_eq_two_mul, h4, h5]⟩
#align norm_num.is_fib_aux_bit1 NormNum.is_fib_aux_bit1

theorem is_fib_aux_bit0_done {n a b c a' : ℕ} (H : IsFibAux n a b) (h1 : a + c = bit0 b)
    (h2 : a * c = a') : fib (bit0 n) = a' :=
  (is_fib_aux_bit0 H h1 h2 rfl rfl rfl).1
#align norm_num.is_fib_aux_bit0_done NormNum.is_fib_aux_bit0_done

theorem is_fib_aux_bit1_done {n a b a2 b2 a' : ℕ} (H : IsFibAux n a b) (h1 : a * a = a2)
    (h2 : b * b = b2) (h3 : a2 + b2 = a') : fib (bit1 n) = a' :=
  (is_fib_aux_bit1 H h1 h2 h3 rfl rfl).1
#align norm_num.is_fib_aux_bit1_done NormNum.is_fib_aux_bit1_done

end deprecated
-- Porting note: This part of the file is tactic related
/-
/-- `prove_fib_aux ic n` returns `(ic', a, b, ⊢ is_fib_aux n a b)`, where `n` is a numeral. -/
unsafe def prove_fib_aux (ic : instance_cache) : expr → tactic (instance_cache × expr × expr × expr)
  | e =>
    match match_numeral e with
    | match_numeral_result.one => pure (ic, q((1 : ℕ)), q((1 : ℕ)), q(is_fib_aux_one))
    | match_numeral_result.bit0 e => do
      let (ic, a, b, H) ← prove_fib_aux e
      let na ← a.toNat
      let nb ← b.toNat
      let (ic, c) ← ic.ofNat (2 * nb - na)
      let (ic, h1) ← prove_add_nat ic a c (q((bit0 : ℕ → ℕ)).mk_app [b])
      let (ic, a', h2) ← prove_mul_nat ic a c
      let (ic, a2, h3) ← prove_mul_nat ic a a
      let (ic, b2, h4) ← prove_mul_nat ic b b
      let (ic, b', h5) ← prove_add_nat' ic a2 b2
      pure
          (ic, a', b',
            q(@is_fib_aux_bit0).mk_app [e, a, b, c, a2, b2, a', b', H, h1, h2, h3, h4, h5])
    | match_numeral_result.bit1 e => do
      let (ic, a, b, H) ← prove_fib_aux e
      let na ← a.toNat
      let nb ← b.toNat
      let (ic, c) ← ic.ofNat (2 * na + nb)
      let (ic, a2, h1) ← prove_mul_nat ic a a
      let (ic, b2, h2) ← prove_mul_nat ic b b
      let (ic, a', h3) ← prove_add_nat' ic a2 b2
      let (ic, h4) ← prove_add_nat ic (q((bit0 : ℕ → ℕ)).mk_app [a]) b c
      let (ic, b', h5) ← prove_mul_nat ic b c
      pure
          (ic, a', b',
            q(@is_fib_aux_bit1).mk_app [e, a, b, c, a2, b2, a', b', H, h1, h2, h3, h4, h5])
    | _ => failed
#align norm_num.prove_fib_aux NormNum.prove_fib_aux

/-- A `norm_num` plugin for `fib n` when `n` is a numeral.
Uses the binary representation of `n` like `nat.fast_fib`. -/
unsafe def prove_fib (ic : instance_cache) (e : expr) : tactic (instance_cache × expr × expr) :=
  match match_numeral e with
  | match_numeral_result.zero => pure (ic, q((0 : ℕ)), q(fib_zero))
  | match_numeral_result.one => pure (ic, q((1 : ℕ)), q(fib_one))
  | match_numeral_result.bit0 e => do
    let (ic, a, b, H) ← prove_fib_aux ic e
    let na ← a.toNat
    let nb ← b.toNat
    let (ic, c) ← ic.ofNat (2 * nb - na)
    let (ic, h1) ← prove_add_nat ic a c (q((bit0 : ℕ → ℕ)).mk_app [b])
    let (ic, a', h2) ← prove_mul_nat ic a c
    pure (ic, a', q(@is_fib_aux_bit0_done).mk_app [e, a, b, c, a', H, h1, h2])
  | match_numeral_result.bit1 e => do
    let (ic, a, b, H) ← prove_fib_aux ic e
    let (ic, a2, h1) ← prove_mul_nat ic a a
    let (ic, b2, h2) ← prove_mul_nat ic b b
    let (ic, a', h3) ← prove_add_nat' ic a2 b2
    pure (ic, a', q(@is_fib_aux_bit1_done).mk_app [e, a, b, a2, b2, a', H, h1, h2, h3])
  | _ => failed
#align norm_num.prove_fib NormNum.prove_fib

/-- A `norm_num` plugin for `fib n` when `n` is a numeral.
/-
unknown identifier ''
-/
Uses the binary representation of `n` like `Nat.fastFib`. -/
@[norm_num]
unsafe def eval_fib : expr → tactic (expr × expr)
  | (fib $(en)) => do
    let n ← en.toNat
    match n with
      | 0 => pure (q((0 : ℕ)), q(fib_zero))
      | 1 => pure (q((1 : ℕ)), q(fib_one))
      | 2 => pure (q((1 : ℕ)), q(fib_two))
      | _ => do
        let c ← mk_instance_cache q(ℕ)
        Prod.snd <$> prove_fib c en
  | _ => failed
#align norm_num.eval_fib NormNum.eval_fib
-/
end NormNum